Skip to content

Releases: systeminit/swamp

swamp 20260226.152033.0-sha.bd7ab0fd

26 Feb 15:21
Immutable release. Only release title and notes can be modified.
bd7ab0f

Choose a tag to compare

What's Changed

  • fix: resolve race condition in concurrent vault put (#487) (#488)

Summary

Fixes the race condition in refreshSecretsIndex that caused intermittent ENOTEMPTY errors when multiple vault put processes ran concurrently against the same vault.

Root cause

refreshSecretsIndex used a destructive remove+recreate pattern: it removed the entire vaults/{name}/secrets/ directory with Deno.remove({ recursive: true }) and rebuilt it from scratch on every vault put. When two processes collided, one would write symlinks into the directory while the other was trying to remove it, triggering ENOTEMPTY (os error 66).

This was the only index method using this pattern — indexModel and indexWorkflow both use idempotent ensureDir + atomic createSymlink without ever removing their parent directory.

Fix

Replaced the remove+recreate with an incremental sync that matches the existing model/workflow pattern:

  1. Migration guard — only remove logicalSecretsDir if lstat shows it's an old-style symlink (not a directory), preserving backward compatibility
  2. ensureDir() — idempotent directory creation, safe for concurrent calls
  3. Build desired set — read actual .enc files to determine which keys should have symlinks
  4. Create/update symlinks — via existing atomic createSymlink() (temp file + rename)
  5. Remove stale entries — iterate logical dir and remove any symlinks not in the desired set, with NotFound tolerance for concurrent removals

This is convergent: any number of concurrent executions produce the same final state without interfering with each other.

Tests added

4 new test cases for refreshSecretsIndex:

  • Basic secrets indexing.enc files get symlinks created
  • Incremental add — adding a new secret preserves existing symlinks
  • Stale cleanup — removing a .enc file and re-indexing removes the stale symlink
  • Migration — old-style symlink gets replaced with a directory of individual symlinks

Manual verification

Ran the exact reproduction scenario from the issue — 5 rounds of 5 concurrent vault put operations against a fresh repo using the compiled binary. All 25 operations completed successfully with all keys present, zero ENOTEMPTY errors.

Closes #487

Test plan

  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt — formatting passes
  • deno run test — all 2166 tests pass
  • deno run compile — binary compiles
  • Manual concurrent vault put stress test (5 rounds x 5 concurrent processes) — no errors

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.152033.0-sha.bd7ab0fd/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.152033.0-sha.bd7ab0fd/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.152033.0-sha.bd7ab0fd/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.152033.0-sha.bd7ab0fd/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.144740.0-sha.27ef5933

26 Feb 14:48
Immutable release. Only release title and notes can be modified.
27ef593

Choose a tag to compare

What's Changed

  • feat: add contract, property-based, and architectural fitness tests (#486)

Summary

Adds three new categories of tests (13 files, 77 tests) that catch classes of bugs our existing unit and integration tests cannot:

  • Architectural fitness tests enforce DDD boundary rules automatically — no more accidental layering violations or circular dependencies between bounded contexts
  • Contract tests verify behavioral invariants that cross-context consumers depend on — catches drift in shared interfaces before it causes runtime failures
  • Property-based tests verify domain aggregate invariants hold across randomly generated inputs — catches edge cases that hand-picked examples miss

Why these tests matter

Our existing 2,085 tests are all unit or integration tests with hand-crafted inputs. This leaves three gaps:

  1. Architectural erosion is invisible. A developer can add a domain → infrastructure import and nothing fails. Over time, the clean DDD layering degrades. The architectural fitness tests catch this immediately with ratchets — the current violation count is locked in, and any new violation fails CI.

  2. Cross-context contracts drift silently. When ModelResolver.buildContext() changes what fields it exposes, expression consumers break at runtime, not at compile time. Contract tests lock down the behavioral interface consumers depend on.

  3. Aggregate invariants aren't stress-tested. We test Definition.create() with 3-4 names, but never with randomly generated strings containing path traversal characters, unicode, or empty segments. Property tests run 100 random inputs per property, catching edge cases we'd never think to write by hand.

Architectural fitness tests (2 files)

Rule Enforcement
No new circular dependencies between bounded contexts Ratchet at 4 known mutual deps (data↔models, definitions↔models, expressions↔models, expressions↔workflows)
Domain must not import infrastructure Ratchet at 13 known violations
Presentation must not import infrastructure Ratchet at 26 known violations
Domain must not import CLI or presentation Hard rule (0 violations)
Infrastructure must not import CLI Hard rule (0 violations)
Production code must not import test files Hard rule (0 violations)

The ratchet pattern means: fixing an existing violation makes the count go down (test still passes). Adding a new violation makes the count go up (test fails). No allow-lists to maintain.

Contract tests (5 files, 42 tests)

Each test verifies a behavioral invariant that consumers across bounded contexts depend on, with zero overlap with existing unit tests:

  • EventBus (event_bus_contract_test.ts): Batch preserves publication order across event types, handler errors don't break subsequent handlers, type-specific and wildcard handlers both fire, batch error propagation cleans up state, post-batch events deliver immediately, unsubscribe is idempotent, selective unsubscribe only removes target handler
  • Definition (definition_contract_test.ts): getMethodArguments() returns isolated copies (mutation-safe), nonexistent method returns empty object, withUpgradedGlobalArguments preserves identity fields, complex JSON Schema inputs survive serialization round-trip, globalArguments are immutable after create, setMethodArguments fully replaces
  • Data (data_contract_test.ts): GC schema rejects zero/negative/float/zero-duration values, ownership schema enforces enum + non-empty ref, optional workflow fields behavior, withNewVersion preserves all inherited fields, toData() returns tag copies (not shared references), isOwnedBy ignores optional fields
  • Workflow (workflow_contract_test.ts): Job ordering preserved through serialization round-trip, addJob appends in call order, create allows empty jobs but fromData rejects them, dependency structure survives round-trip, multi-step order preservation, Job.create rejects empty steps, tag isolation through fromData
  • ModelResolver (model_resolver_contract_test.ts): resolveModel throws ModelNotFoundError for invalid name/UUID (not null), finds models by name and UUID, buildContext always includes env namespace, indexes models by both name and UUID, ModelData.input has stable interface (id, name, version, tags, globalArguments), self reference has correct fields, updateDefinitionInContext mutates context correctly

Property-based tests (5 files, 28 tests)

Uses fast-check to generate random inputs and verify invariants hold universally:

  • ModelType (model_type_property_test.ts): Normalization is idempotent, always lowercase, no consecutive separators, no leading/trailing separators, equality by normalized form, empty input rejected
  • Definition (definition_property_test.ts): Path traversal characters always rejected, version always positive, serialization round-trips, ID is always UUID, hash is deterministic, hash is content-sensitive
  • Data (data_property_test.ts): Path traversal rejected, version positive, tags always include 'type', ownership check correctness, serialization round-trips, withNewVersion preserves identity
  • DataMetadata (data_metadata_property_test.ts): Zero durations become "workflow", leading zeros become "workflow", non-zero durations pass through, named lifetimes pass through
  • Workflow (workflow_property_test.ts): Empty jobs rejected by schema, duplicate job names rejected, version positive, serialization round-trips, getJob lookup works

Interesting findings during implementation

The contract tests revealed two real architectural facts worth documenting:

  • Data.tags and Workflow.tags return direct references, not defensive copies. Mutating the returned object mutates the entity. This differs from Definition, which uses private fields + copy getters. The contract tests now document this actual behavior.
  • Workflow.toData() passes tags by reference too, so fromData() is the isolation boundary (it goes through WorkflowSchema.parse which copies).

Files changed (14)

File Type Tests
deno.json Config Added fast-check dependency
deno.lock Lockfile Updated
integration/architecture_boundary_test.ts Arch fitness 5
integration/ddd_layer_rules_test.ts Arch fitness 2
src/domain/events/event_bus_contract_test.ts Contract 7
src/domain/definitions/definition_contract_test.ts Contract 6
src/domain/data/data_contract_test.ts Contract 13
src/domain/workflows/workflow_contract_test.ts Contract 7
src/domain/expressions/model_resolver_contract_test.ts Contract 9
src/domain/models/model_type_property_test.ts Property 7
src/domain/definitions/definition_property_test.ts Property 6
src/domain/data/data_property_test.ts Property 6
src/domain/data/data_metadata_property_test.ts Property 4
src/domain/workflows/workflow_property_test.ts Property 5

Test plan

  • deno check — passes
  • deno lint — passes
  • deno fmt — passes
  • deno run test — 2,162 tests pass (77 new), 0 failures
  • deno run compile — binary compiles

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.144740.0-sha.27ef5933/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.144740.0-sha.27ef5933/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.144740.0-sha.27ef5933/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.144740.0-sha.27ef5933/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.140557.0-sha.017139c4

26 Feb 14:06
Immutable release. Only release title and notes can be modified.
017139c

Choose a tag to compare

What's Changed

  • feat: add dependency auditing CI gate with OSV-Scanner (#484)

Summary

Adds an automated dependency auditing job to the CI pipeline that catches
known vulnerabilities before they reach main. This is especially important
for a codebase that is entirely authored by AI agents, where no human is
manually reviewing changelogs or security advisories for each dependency
update.

Why this matters for an AI-authored codebase

AI agents are excellent at writing code but operate without awareness of the
broader security landscape of their dependencies. When an agent adds or
updates a dependency, it has no way to know whether that version has a
published CVE, has been deprecated due to a security incident, or pulls in
a transitive dependency with known vulnerabilities. This creates a blind spot
that compounds over time:

  • Agents pick versions based on documentation and training data, not
    real-time vulnerability databases
  • Automated dependency updates (from agents or Dependabot) can introduce
    vulnerable transitive dependencies without anyone noticing
  • No human in the loop means no one is reading npm advisories, GitHub
    security alerts, or package changelogs
  • Supply chain attacks targeting popular npm packages are increasingly
    common — a compromised transitive dependency could be pulled in without
    any agent or reviewer noticing

This CI gate acts as an automated security reviewer that fills the gap between
AI-generated code and the real-world vulnerability landscape.

Why a custom Deno script instead of existing tools

None of the established vulnerability scanners support deno.lock:

Tool Deno Support
deno audit Does not exist (no native command)
osv-scanner (Google) Does not recognize deno.lock format
trivy (Aqua Security) Does not support deno.lock
actions/dependency-review-action Requires GitHub dependency graph, which doesn't index deno.lock
npm audit Only works with package-lock.json, requires npm setup

So we wrote scripts/audit_deps.ts — a lightweight Deno script that:

  1. Reads deno.lock and extracts all 240 npm packages with their resolved versions
  2. Batch-queries the OSV.dev API (the same vulnerability database that osv-scanner, Deps.dev, and GitHub Advisory Database use)
  3. Reports any findings with advisory IDs, CVE references, and descriptions
  4. Exits non-zero if any vulnerabilities are found

This approach needs no external tools — it runs with deno run using only
--allow-read and --allow-net=api.osv.dev (minimal permissions).

What's included

New deps-audit CI job (runs in parallel with test for zero added
latency on the happy path):

  1. Vulnerability scanning via the OSV.dev API — scans all npm packages in
    deno.lock against the OSV database. This is a hard gate — any known
    vulnerability fails the pipeline.

  2. Outdated dependency reporting via deno outdated — reports which
    dependencies have newer versions available as a GitHub Actions warning
    annotation. Intentionally non-blocking since upstream releases would
    otherwise break CI on unrelated PRs.

Pipeline integration:

  • Runs in parallel with the test job (no sequential dependency)
  • Both claude-review and auto-merge now require deps-audit to pass
  • No PR can reach main with a known vulnerability in its dependency tree

Local development:

  • deno run audit runs the same vulnerability scan + deno outdated locally

Already finding real issues

Running the script against our current deno.lock immediately found:

Found vulnerabilities in 1 package(s):

  jws@3.2.2
    - GHSA-869p-cjfg-cm3x: No description available

This is a transitive dependency: @azure/identity@azure/msal-node
jsonwebtokenjws@3.2.2. An upstream fix is needed from the Azure SDK.

Test Plan

  • deno fmt --check passes
  • deno lint passes
  • deno run test passes (2085 tests)
  • Script runs locally and correctly identifies known vulnerabilities
  • CI runs the new deps-audit job successfully on this PR

Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.140557.0-sha.017139c4/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.140557.0-sha.017139c4/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.140557.0-sha.017139c4/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.140557.0-sha.017139c4/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.020959.0-sha.ee058080

26 Feb 02:10
Immutable release. Only release title and notes can be modified.
ee05808

Choose a tag to compare

What's Changed

  • fix: redact vault secrets from stdout/stderr output (#482)

Summary

Fixes #478 — when a model method references a vault secret via vault.get(...) and the command runs, the resolved secret value appeared in plaintext in:

  1. Console output — real-time streaming via logger.info(line)
  2. Data artifacts — stdout/stderr/command in result.json persisted to .swamp/data/
  3. Log file artifacts — output log written to .swamp/data/.../log/
  4. Run log files.swamp/outputs/*.log files
  5. Error messages — catch block in shell_model.ts

The SecretRedactor existed and was populated during vault resolution, but was only wired to the RunFileSink (the .swamp/outputs/*.log files). It never reached the shell model or the process executor.

The fix

Thread the SecretRedactor through MethodContext so models can apply redaction to all output before persistence and streaming.

Changes:

  • Add redactor?: SecretRedactor to MethodContext interface
  • Add redactor?: SecretRedactor to ProcessExecutorOptions — redacts lines before passing to logger in streaming mode
  • In shell_model.ts — pass context.redactor to executeProcess(), redact stdout, stderr, and command fields before storing in result attributes, redact error messages in catch block
  • In model_method_run.ts — pass the redactor in the context object to executionService.executeWorkflow()
  • In execution_service.ts — pass ctx.secretRedactor as redactor in the context for workflow step execution

What users see

Before this fix, running a model with a vault secret:

$ swamp model method run secret-test execute
... Executing method "execute"
... my-super-secret-password-12345    ← secret in plaintext

After this fix:

$ swamp model method run secret-test execute
... Executing method "execute"
... ***                                ← redacted

The redaction applies everywhere the secret could appear:

Output channel Before After
Console streaming my-super-secret-password-12345 ***
result.json stdout my-super-secret-password-12345 ***
result.json command echo 'my-super-secret-password-12345' echo '***'
Log data artifact my-super-secret-password-12345 ***
Run log file my-super-secret-password-12345 ***
JSON mode (--json) my-super-secret-password-12345 ***
Error messages my-super-secret-password-12345 ***

User impact

  • No breaking changes for normal usage. Commands that don't use vault secrets behave identically.
  • The command field in result data now shows the redacted command (e.g., curl -H 'Authorization: Bearer ***' https://api.example.com) instead of the resolved secret. Users who need to see which vault/key was referenced can check the definition YAML, which preserves the original vault.get() expression.
  • In practice, the *** replacement in the command field has minimal impact — the command structure remains visible and the secret is hidden, which is the desired behavior for audit logs.

End-to-end CLI verification

Tested with the compiled binary in a fresh swamp repo:

  1. Created a local_encryption vault with secret my-super-secret-password-12345
  2. Created a command/shell model with run: "echo '${{ vault.get(test-vault, my-api-key) }}'"
  3. Ran swamp model method run secret-test execute
  4. Verified console output shows ***
  5. Checked all persisted artifacts:
    • result.json: {"command":"echo '***'","stdout":"***","stderr":"", ...}
    • log artifact: [stdout]\n***
    • run log: ... ***
  6. Ran grep -r "my-super-secret-password-12345" .swamp/zero matches, no plaintext secret in any persisted artifact
  7. Verified --json mode also shows redacted values

Files changed (9)

Domain model:

  • src/domain/models/model.ts — add redactor to MethodContext
  • src/domain/models/command/shell/shell_model.ts — apply redaction to stdout, stderr, command, and error messages

Infrastructure:

  • src/infrastructure/process/process_executor.ts — add redactor to options, redact streamed lines before logger

CLI / Workflow threading:

  • src/cli/commands/model_method_run.ts — pass redactor in context
  • src/domain/workflows/execution_service.ts — pass secretRedactor as redactor in workflow step context

Tests (6 new):

  • src/domain/models/command/shell/shell_model_test.ts — 5 new tests (stdout, stderr, command, log file, error message redaction)
  • src/infrastructure/process/process_executor_test.ts — 1 new test (streamed line redaction)

Design docs:

  • design/vaults.md — updated Expression Security section
  • design/models.md — mention redactor in MethodContext description

Test plan

  • deno check — passes
  • deno lint — passes
  • deno fmt — passes
  • deno run test — 2085 tests pass
  • deno run compile — binary compiles
  • End-to-end CLI test with compiled binary confirms no secret leakage

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.020959.0-sha.ee058080/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.020959.0-sha.ee058080/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.020959.0-sha.ee058080/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.020959.0-sha.ee058080/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.014830.0-sha.a97f0ed8

26 Feb 01:49
Immutable release. Only release title and notes can be modified.
a97f0ed

Choose a tag to compare

What's Changed

  • fix: prevent symlink path traversal in file writes (#481)

Summary

Fixes #479 — symlink path traversal allowing writes outside the repository.

When .swamp/ subdirectories (e.g. outputs, data, secrets) are replaced with symlinks pointing outside the repository, swamp follows the symlink and writes sensitive data (resolved secrets, computation results) to the attacker-controlled location.

The vulnerability

The existing assertPathContained() uses resolve() which only normalizes paths lexically — it does not follow symlinks. A symlinked directory passes the check even when it points outside .swamp/.

The fix

A new shared assertSafePath() utility uses Deno.realPath() to resolve symlinks before verifying path containment. It's integrated at every write location across the codebase (17 files, 34 call sites).

Attack scenario: before vs after

Attack setup: .swamp/outputs is replaced with a symlink → /tmp/evil

Before the fix — no symlink-aware check exists:

write path: /repo/.swamp/outputs/aws-ec2/create/file.yaml
actual write: /tmp/evil/aws-ec2/create/file.yaml  ← data exfiltrated

Naive fix (wrong boundary) — using the subdirectory as boundary:

boundary = realPath("/repo/.swamp/outputs") = /tmp/evil   ← follows symlink!
path     = realPath("/repo/.swamp/outputs/file.yaml") = /tmp/evil/file.yaml
check: "/tmp/evil/file.yaml".startsWith("/tmp/evil/") → true → PASSES ✗

Both sides resolve through the same symlink, making the check a no-op.

Correct fix (parent boundary) — using .swamp/ as boundary:

boundary = realPath("/repo/.swamp") = /repo/.swamp        ← real directory
path     = realPath("/repo/.swamp/outputs/file.yaml") = /tmp/evil/file.yaml
check: "/tmp/evil/file.yaml".startsWith("/repo/.swamp/") → false → PathTraversalError ✓

User impact

  • No breaking changes for normal usage. All paths that stay within the repository work exactly as before.
  • If a symlink-based escape is detected, a clear PathTraversalError is thrown with the path, boundary, and resolved target in the message.
  • The existing lexical assertPathContained() checks in UnifiedDataRepository and YamlVaultConfigRepository are kept as defense-in-depth.

Plan vs implementation deviations

The original plan specified subdirectory-level boundaries (e.g. .swamp/outputs, .swamp/data, .swamp/secrets). During implementation review, this was identified as incorrect — using the potentially-symlinked directory as its own boundary makes the check ineffective. All boundaries were raised to the .swamp/ directory (or repo root for the index service).

Component Plan boundary Actual boundary Why
All persistence repos (7 files) swampPath(repoDir, SWAMP_SUBDIRS.xxx) swampPath(repoDir) Subdirectory could be the symlink
UnifiedDataRepository swampPath(repoDir, SWAMP_SUBDIRS.data) swampPath(repoDir) Same
YamlOutputRepository swampPath(repoDir, SWAMP_SUBDIRS.outputs) swampPath(repoDir) Same
LocalEncryptionVaultProvider swampPath(baseDir, SWAMP_SUBDIRS.secrets) swampPath(baseDir) Same
UserModelLoader swampPath(repoDir, SWAMP_SUBDIRS.bundles) join(repoDir, SWAMP_DATA_DIR) Same
RunFileSink callers Subdirectory boundaries swampPath(repoDir) Same
SymlinkRepoIndexService modelsBaseDir / workflowsBaseDir / vaultsBaseDir this.repoDir Parent dirs could be symlinks

Additional deviation: the plan didn't mention the execution_service.ts call site for RunFileSink.register(), which was also protected.

Files changed (17)

New:

  • src/infrastructure/persistence/safe_path.tsPathTraversalError + assertSafePath()
  • src/infrastructure/persistence/safe_path_test.ts — 9 test cases

Modified (15):

  • src/infrastructure/persistence/unified_data_repository.ts — 5 checks (save, append, allocate, symlink)
  • src/infrastructure/persistence/yaml_output_repository.ts
  • src/infrastructure/persistence/yaml_definition_repository.ts
  • src/infrastructure/persistence/yaml_evaluated_definition_repository.ts
  • src/infrastructure/persistence/yaml_workflow_repository.ts
  • src/infrastructure/persistence/yaml_evaluated_workflow_repository.ts
  • src/infrastructure/persistence/yaml_workflow_run_repository.ts
  • src/infrastructure/persistence/yaml_vault_config_repository.ts
  • src/infrastructure/persistence/json_telemetry_repository.ts
  • src/infrastructure/logging/run_file_sink.ts — optional boundary param
  • src/cli/commands/model_method_run.ts — passes boundary to sink
  • src/domain/workflows/execution_service.ts — passes boundary to sink
  • src/domain/vaults/local_encryption_vault_provider.ts — checks in put/ensureDir
  • src/domain/models/user_model_loader.ts — checks in bundleWithCache
  • src/infrastructure/repo/symlink_repo_index_service.ts — replaced private method with shared utility

Test Plan

  • deno fmt --check — passes
  • deno lint — passes
  • deno check — passes
  • deno run test — 2079 tests pass (138 steps)
  • deno run compile — binary compiles
  • New unit tests cover: path within boundary, symlink escape, non-existent paths, internal symlinks, boundary-as-symlink attack, error details

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.014830.0-sha.a97f0ed8/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.014830.0-sha.a97f0ed8/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.014830.0-sha.a97f0ed8/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.014830.0-sha.a97f0ed8/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260226.005850.0-sha.972b02bf

26 Feb 00:59
Immutable release. Only release title and notes can be modified.
972b02b

Choose a tag to compare

What's Changed

  • fix: reject path traversal in definition names (#480)

Summary

Fixes: #477

Defense in depth: swamp model create currently accepts absolute paths and path traversal sequences (e.g., /etc/passwd, ../../../foo) as model names without error. While definition names are not currently used to construct file paths (UUIDs are used instead), this is a validation gap that could become a security issue if naming conventions change in the future.

This PR adds Zod validation to DefinitionSchema that rejects .., /, \, and null bytes in definition names, mirroring the existing pattern already present in DataMetadataSchema.

Changes

  • src/domain/definitions/definition.ts — Added .refine() validation to the name field in DefinitionSchema that rejects path traversal characters
  • src/domain/definitions/definition_test.ts — Added 4 test cases covering forward slashes, backslashes, .. path traversal, and null bytes

Test plan

  • deno check — type checking passes
  • deno lint — linting passes
  • deno fmt — formatting passes
  • deno run test src/domain/definitions/definition_test.ts — all 40 tests pass (4 new)
  • deno run test — full test suite passes (2078 tests)

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.005850.0-sha.972b02bf/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.005850.0-sha.972b02bf/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.005850.0-sha.972b02bf/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260226.005850.0-sha.972b02bf/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260225.204918.0-sha.8f6b52ca

25 Feb 20:50
Immutable release. Only release title and notes can be modified.
8f6b52c

Choose a tag to compare

What's Changed

  • feat: associate telemetry with authenticated users (#476)

Summary

  • Send authenticated user's API key as Authorization: Bearer header on telemetry flush requests, enabling server-side user resolution in Mixpanel
  • distinct_id remains the anonymous UUID — no identity data added to the event payload
  • Auth credentials loaded from ~/.config/swamp/auth.json at init; silently skipped if absent or unreadable

Closes #475

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 18 telemetry tests pass (4 new)
  • Manual: run a swamp command with auth.json present, confirm flush request includes Authorization header
  • Manual: run without auth.json, confirm no Authorization header

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.204918.0-sha.8f6b52ca/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.204918.0-sha.8f6b52ca/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.204918.0-sha.8f6b52ca/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.204918.0-sha.8f6b52ca/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260225.161946.0-sha.d11bb4f9

25 Feb 16:20
Immutable release. Only release title and notes can be modified.
d11bb4f

Choose a tag to compare

What's Changed

  • fix: make .gitignore management opt-in via --include-gitignore flag (#474)

Summary

Follows up on #461, which added automatic .gitignore managed section
during swamp repo init and swamp repo upgrade.

Problem: Automatically modifying .gitignore is surprising behavior.
Established repos have their own gitignore conventions, and many users
track their .swamp/ or .claude/ directories intentionally. Writing to
.gitignore without being asked violates the principle of least surprise
and can create unwanted noise in diffs/PRs.

Solution: Make gitignore management opt-in via a --include-gitignore
CLI flag, with the preference persisted in .swamp.yaml so subsequent
upgrades honor the choice without requiring the flag again.

Behavior

Command Behavior
swamp init Does NOT manage .gitignore (default off)
swamp init --include-gitignore Manages .gitignore, persists gitignoreManaged: true in marker
swamp upgrade Manages .gitignore ONLY if marker has gitignoreManaged: true
swamp upgrade --include-gitignore Opts in, manages .gitignore, persists preference
swamp upgrade --no-include-gitignore Opts out, persists gitignoreManaged: false, skips gitignore

Changes

  • repo_marker_repository.ts — Added gitignoreManaged?: boolean to RepoMarkerData
  • repo_service.ts — Added "skipped" to GitignoreAction type; init() skips gitignore by default, manages when includeGitignore: true; upgrade() respects persisted marker preference with CLI override
  • repo_init.ts — Added --include-gitignore option to init, upgrade, and repo commands
  • repo_service_test.ts — Updated 15 existing tests, added 5 new tests covering opt-in/opt-out/persistence behavior

Why this matters

The managed section machinery from #461 is preserved — when a user opts in, they get the same sentinel-marker-based section management with legacy migration, tool-specific entries, and safe upgrades. The only change is that users must explicitly ask for it, which respects repos that:

  • Already have comprehensive .gitignore files
  • Intentionally track .claude/ or .agents/ directories
  • Use CI workflows that would be disrupted by unexpected .gitignore changes

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 2054 tests pass (50 repo service tests, including 5 new ones)
  • deno run compile succeeds
  • Manual: swamp repo init does NOT create/modify .gitignore
  • Manual: swamp repo init --include-gitignore creates managed section
  • Manual: swamp repo upgrade after opt-in preserves gitignore management
  • Manual: swamp repo upgrade --no-include-gitignore opts out and persists

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.161946.0-sha.d11bb4f9/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.161946.0-sha.d11bb4f9/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.161946.0-sha.d11bb4f9/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.161946.0-sha.d11bb4f9/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260225.155850.0-sha.9faeb31c

25 Feb 15:59
Immutable release. Only release title and notes can be modified.
9faeb31

Choose a tag to compare

What's Changed

  • feat: show SHA-256 integrity check status in update output (#473)

Summary

  • Adds an info line "SHA-256 integrity check passed" to the swamp update output after a successful update

After this change, the output looks like:

15:48:34.397   info    update    swamp updated successfully!
15:48:34.400   info    update    "20260206.200442.0-sha." → "20260225.153610.0-sha.c4405aab"
15:48:34.401   info    update    SHA-256 integrity check passed

This gives users confidence that the checksum verification from #472 actually ran. If verification had failed, the update would have aborted with an error before reaching this point.

Test plan

  • deno run test src/presentation/output/update_output_test.ts — all 8 tests pass

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.155850.0-sha.9faeb31c/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.155850.0-sha.9faeb31c/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.155850.0-sha.9faeb31c/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.155850.0-sha.9faeb31c/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

swamp 20260225.153610.0-sha.c4405aab

25 Feb 15:37
Immutable release. Only release title and notes can be modified.
c4405aa

Choose a tag to compare

What's Changed

  • fix: verify SHA-256 checksum before installing self-update binaries (#472)

Summary

Fixes #469 — adds SHA-256 integrity verification to swamp update, addressing CWE-494 (Download of Code Without Integrity Check).

Previously, swamp update downloaded a tarball from the CDN and installed it with zero integrity verification. An attacker who controls the download path (CDN/S3 compromise, DNS hijack) could replace the binary with arbitrary code that then has access to vault secrets, API keys, and full shell access.

What Changed

New: src/domain/update/integrity.ts

Domain-layer functions for integrity verification (pure, no I/O):

  • validateRedirectUrl(url) — ensures redirect URLs point to https://artifacts.systeminit.com; rejects non-HTTPS, untrusted hosts, and malformed URLs
  • checksumUrlFromTarballUrl(url) — derives the checksum URL by appending .sha256 to the tarball URL
  • parseChecksumFile(content) — parses standard sha256sum format (<hex> <filename>) and extracts the hex digest
  • verifyChecksum(expected, actual) — hard-fail comparison that throws UserError on mismatch

Modified: src/domain/update/update_service.ts

  • Extended UpdateChecker interface with fetchChecksum(tarballUrl) method and expectedChecksum parameter on downloadAndInstall()
  • UpdateService.check() now validates the redirect URL before comparing versions
  • UpdateService.update() now: validates redirect URL → fetches checksum → downloads tarball with verification → installs

Modified: src/infrastructure/update/http_update_checker.ts

  • New fetchChecksum() method: fetches {tarballUrl}.sha256 from CDN, parses it
  • downloadAndInstall() now: downloads tarball → reads it back → computes SHA-256 using existing computeChecksum utility → verifies against expected checksum → only then extracts and installs

New: src/domain/update/integrity_test.ts

13 unit tests covering all integrity functions.

Modified: src/domain/update/update_service_test.ts

Updated mock checker for new interface; added tests for redirect validation and checksum orchestration flow.

Why This Is Correct

  1. Verification happens before extraction — the tarball is fully downloaded and written to disk, then read back and hashed. Only after the checksum matches does tar -xzf run. A tampered tarball is never extracted.

  2. Redirect URL validation — prevents redirect-based attacks where an attacker could point the stable URL redirect to a malicious host. Only https://artifacts.systeminit.com is trusted.

  3. Hard fail, no fallback — if the checksum doesn't match or the .sha256 file can't be fetched, the update aborts with a clear error. There is no "continue anyway" path that an attacker could exploit.

  4. Reuses existing computeChecksum from src/domain/models/checksum.ts — no new crypto code, same battle-tested SHA-256 implementation used elsewhere in the codebase.

  5. Clean DDD separation — pure validation/parsing logic lives in the domain layer (integrity.ts), I/O lives in the infrastructure adapter (http_update_checker.ts), and orchestration lives in the domain service (update_service.ts).

Dependency

This PR requires systeminit/swamp-uat#38 to be deployed first. That PR modifies the upload script to:

  • Verify raw binary checksums against GitHub Release before creating tarballs
  • Compute SHA-256 of each tarball and upload a .sha256 file alongside it to S3

Without the swamp-uat change, the .sha256 files won't exist on the CDN and swamp update will fail with "Failed to fetch checksum: HTTP 404".

User Impact

  • No breaking change for current users — existing swamp binaries don't call fetchChecksum or verify checksums, so they continue working as before.
  • After updating to this version — all subsequent swamp update calls will verify integrity. If a checksum mismatch is detected, users will see a clear error: "Checksum verification failed. The downloaded file may have been tampered with."
  • No new flags or configuration — verification is always on, which is the correct default for a security feature.

Test plan

  • deno check passes
  • deno lint passes
  • deno fmt passes
  • All 49 update-related tests pass (13 new integrity tests + 18 service tests + 18 existing)
  • After deploying swamp-uat#38: run swamp update and verify checksum verification succeeds end-to-end

🤖 Generated with Claude Code


Installation

macOS (Apple Silicon):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.153610.0-sha.c4405aab/swamp-darwin-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

macOS (Intel):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.153610.0-sha.c4405aab/swamp-darwin-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (x86_64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.153610.0-sha.c4405aab/swamp-linux-x86_64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/

Linux (aarch64):

curl -L https://github.com/systeminit/swamp/releases/download/v20260225.153610.0-sha.c4405aab/swamp-linux-aarch64 -o swamp
chmod +x swamp && sudo mv swamp /usr/local/bin/