feat: add @swamp/container-image extension (#794) #122

Merged
stack72 merged 14 commits from issue-794-container-image into main 2026-07-20 23:38:43 +00:00
Owner

Summary

New @swamp/container-image extension — runtime-agnostic container image
lifecycle management via Docker, Podman, or Apple Containers.

  • 6 methods: validate, build, run, login, push, multi-platform-build
  • 3 runtimes: Docker, Podman, Apple Containers — selected via globalArguments.binary
  • Image export: build and export as OCI or Docker archive via exportFormat/exportPath
  • Security: login password uses z.meta({ sensitive: true }) + --password-stdin, never in argv or resources
  • Insecure registries: tlsVerify option on login/push (Podman: --tls-verify=false, Apple Containers: --scheme http)
  • Pre-flight checks: runtime availability, multi-platform support (blocks Apple Containers with clear error)
  • CI fan-out: path filter, check/lockfile jobs, wired into claude-review and adversarial-review needs

Runtime Support Matrix

Method Docker Podman Apple Containers
validate docker info podman info container system status
build docker build podman build container build
run docker run --rm podman run --rm container run --rm
login docker login podman login container registry login
push docker push podman push container image push
multi-platform-build docker buildx build podman build --platform + push Not supported (host arch)

Test plan

  • 76 unit tests — schema validation, argv construction for all 3 runtimes, operation-level with mock executor, login password security, export format routing, pre-flight checks, tlsVerify
  • E2E: build + run on Docker, Podman, Apple Containers — stdout captured
  • E2E: push to local registry (Docker + Podman) with digest capture
  • E2E: multi-platform-build + push to local registry (Docker)
  • E2E: export as OCI and Docker archive (Docker, Podman, Apple Containers)
  • E2E: validate method against all 3 runtimes (including daemon-down detection)
  • E2E: vault + env var expression resolution for login password
  • E2E: password redaction in reports
  • Dry-run publish: 22.6KB archive, adversarial review 15/15 pass
  • deno check / deno lint / deno fmt --check / deno install --frozen all green
  • CI matrix fires for container-image-check and container-image-lockfile jobs

Closes #794

## Summary New `@swamp/container-image` extension — runtime-agnostic container image lifecycle management via Docker, Podman, or Apple Containers. - **6 methods**: `validate`, `build`, `run`, `login`, `push`, `multi-platform-build` - **3 runtimes**: Docker, Podman, Apple Containers — selected via `globalArguments.binary` - **Image export**: build and export as OCI or Docker archive via `exportFormat`/`exportPath` - **Security**: login password uses `z.meta({ sensitive: true })` + `--password-stdin`, never in argv or resources - **Insecure registries**: `tlsVerify` option on login/push (Podman: `--tls-verify=false`, Apple Containers: `--scheme http`) - **Pre-flight checks**: runtime availability, multi-platform support (blocks Apple Containers with clear error) - **CI fan-out**: path filter, check/lockfile jobs, wired into claude-review and adversarial-review needs ### Runtime Support Matrix | Method | Docker | Podman | Apple Containers | | ---------------------- | --------------------- | -------------------------------- | -------------------------- | | `validate` | `docker info` | `podman info` | `container system status` | | `build` | `docker build` | `podman build` | `container build` | | `run` | `docker run --rm` | `podman run --rm` | `container run --rm` | | `login` | `docker login` | `podman login` | `container registry login` | | `push` | `docker push` | `podman push` | `container image push` | | `multi-platform-build` | `docker buildx build` | `podman build --platform` + push | Not supported (host arch) | ## Test plan - [x] 76 unit tests — schema validation, argv construction for all 3 runtimes, operation-level with mock executor, login password security, export format routing, pre-flight checks, tlsVerify - [x] E2E: build + run on Docker, Podman, Apple Containers — stdout captured - [x] E2E: push to local registry (Docker + Podman) with digest capture - [x] E2E: multi-platform-build + push to local registry (Docker) - [x] E2E: export as OCI and Docker archive (Docker, Podman, Apple Containers) - [x] E2E: validate method against all 3 runtimes (including daemon-down detection) - [x] E2E: vault + env var expression resolution for login password - [x] E2E: password redaction in reports - [x] Dry-run publish: 22.6KB archive, adversarial review 15/15 pass - [x] deno check / deno lint / deno fmt --check / deno install --frozen all green - [ ] CI matrix fires for container-image-check and container-image-lockfile jobs Closes #794
Author
Owner

CI Security Review

Medium

  1. Missing container-image-check and container-image-lockfile in merge-gate needs (.forgejo/workflows/ci.yml:1131-1164)

    The new container-image-check and container-image-lockfile jobs were correctly added to the needs lists of claude-review (lines 759-760) and claude-adversarial-review (lines 898-899), but they are not listed in the merge-gate job's needs (lines 1131-1164). Every other extension type (vault-*, datastore-*, ssh-*, kubernetes-*, workflows-*, cve-*, software-factory-*) has its check and lockfile jobs in merge-gate's needs — container-image is the sole exception.

    Impact: If container-image-check or container-image-lockfile fails, the merge-gate will still pass. The claude-review job depends on them, but with an !failure() condition — a failed dependency causes the review to be skipped, not failed. The merge-gate checks contains(needs.*.result, 'failure'), which treats skipped as passing. This means container-image code changes with broken checks or tests can be merged.

    Fix: Add container-image-check and container-image-lockfile to the merge-gate job's needs list, consistent with all other extension types:

    merge-gate:
      needs:
        [
          ...
          software-factory-check,
          software-factory-lockfile,
          container-image-check,    # add
          container-image-lockfile,  # add
          model-check,
          ...
        ]
    

Notes (no findings)

  • Prompt injection: No changes to LLM-facing prompts or data flows. The only changes to review jobs are adding the new container-image jobs to their needs arrays.
  • Expression injection: The new jobs use ${{ matrix.task }} in run: blocks, but the matrix values are statically defined as [check, lint, fmt, test] — not user-controlled. This pattern is consistent with all other extension check jobs.
  • Dangerous triggers: No change to workflow triggers. The workflow uses pull_request (not pull_request_target), which is the safe pattern.
  • Supply chain: New jobs use actions/checkout@v6 and denoland/setup-deno@v2, both trusted publishers with tag-only pins, consistent with the rest of the workflow.
  • Permissions: New jobs inherit the workflow-level contents: read — no additional permissions requested. Correct and minimal.
  • Secret exposure: New jobs do not reference or consume any secrets.
  • Auto-merge: No changes to auto-merge logic beyond the merge-gate gap noted above.

Verdict

PASS — The changes are a straightforward, well-structured addition of CI jobs for the new container-image extension, following established patterns. The missing merge-gate entries are a medium-severity gap that should be fixed but do not represent an exploitable security vulnerability (the pull_request trigger runs without secrets in the PR context).

## CI Security Review ### Medium 1. **Missing `container-image-check` and `container-image-lockfile` in `merge-gate` needs** (`.forgejo/workflows/ci.yml:1131-1164`) The new `container-image-check` and `container-image-lockfile` jobs were correctly added to the `needs` lists of `claude-review` (lines 759-760) and `claude-adversarial-review` (lines 898-899), but they are **not** listed in the `merge-gate` job's `needs` (lines 1131-1164). Every other extension type (`vault-*`, `datastore-*`, `ssh-*`, `kubernetes-*`, `workflows-*`, `cve-*`, `software-factory-*`) has its check and lockfile jobs in merge-gate's needs — `container-image` is the sole exception. **Impact**: If `container-image-check` or `container-image-lockfile` fails, the merge-gate will still pass. The `claude-review` job depends on them, but with an `!failure()` condition — a failed dependency causes the review to be *skipped*, not *failed*. The merge-gate checks `contains(needs.*.result, 'failure')`, which treats `skipped` as passing. This means container-image code changes with broken checks or tests can be merged. **Fix**: Add `container-image-check` and `container-image-lockfile` to the `merge-gate` job's `needs` list, consistent with all other extension types: ```yaml merge-gate: needs: [ ... software-factory-check, software-factory-lockfile, container-image-check, # add container-image-lockfile, # add model-check, ... ] ``` ### Notes (no findings) - **Prompt injection**: No changes to LLM-facing prompts or data flows. The only changes to review jobs are adding the new container-image jobs to their `needs` arrays. - **Expression injection**: The new jobs use `${{ matrix.task }}` in `run:` blocks, but the matrix values are statically defined as `[check, lint, fmt, test]` — not user-controlled. This pattern is consistent with all other extension check jobs. - **Dangerous triggers**: No change to workflow triggers. The workflow uses `pull_request` (not `pull_request_target`), which is the safe pattern. - **Supply chain**: New jobs use `actions/checkout@v6` and `denoland/setup-deno@v2`, both trusted publishers with tag-only pins, consistent with the rest of the workflow. - **Permissions**: New jobs inherit the workflow-level `contents: read` — no additional permissions requested. Correct and minimal. - **Secret exposure**: New jobs do not reference or consume any secrets. - **Auto-merge**: No changes to auto-merge logic beyond the merge-gate gap noted above. ### Verdict **PASS** — The changes are a straightforward, well-structured addition of CI jobs for the new `container-image` extension, following established patterns. The missing merge-gate entries are a medium-severity gap that should be fixed but do not represent an exploitable security vulnerability (the `pull_request` trigger runs without secrets in the PR context).
Author
Owner

Code Review

Blocking Issues

  1. container-image-check and container-image-lockfile missing from merge-gate needs (ci.yml:1128–1173)

    The new CI jobs container-image-check and container-image-lockfile are correctly wired into claude-review's needs (lines 769–770), but they are absent from the merge-gate job's needs list. Every other extension follows the pattern of listing its check and lockfile jobs directly in the merge gate (e.g. ssh-check, ssh-lockfile, vault-check, etc.). Without these entries, the gate's contains(needs.*.result, 'failure') test cannot observe a failure from these jobs.

    The indirect path through claude-review doesn't rescue this: if container-image-check fails, claude-review evaluates !failure()false and is skipped (result: 'skipped'). The merge gate sees skipped, not failure, and passes. A broken container-image extension can therefore be merged without any gate blocking it.

    Fix: Add container-image-check and container-image-lockfile to the merge gate's needs array alongside the other extension check jobs.

Suggestions

  1. Duplicate tag in podman buildx argv (runner.ts:219,230)

    In buildBuildxArgv for podman, args.tags[0] is added twice: first via the -t loop over all tags (argv.push("-t", tag)) and then again as argv.push("--manifest", args.tags[0]). In podman, --manifest names the manifest list and -t tags individual platform images; both pointing at the same value may be redundant or could produce unexpected behavior (e.g. overwriting the manifest list name with an image tag). This is worth verifying against real podman behavior.

  2. Direct npm: specifier in schemas.ts instead of import map alias (schemas.ts:26)

    import { z } from "npm:zod@4.3.6" bypasses the "zod" alias in deno.json. Using import { z } from "zod" would keep the import consistent with how other files in the repo consume mapped dependencies and would let deno.json be the single source of truth for the pinned version.

  3. claude-adversarial-review doesn't trigger for container-image-only PRs (ci.yml:921)

    The adversarial review job's if condition gates on vaults, datastores, issue-lifecycle, kubernetes, workflows, cve, software-factory, and codegen — but not needs.changes.outputs.container-image. This PR therefore won't receive adversarial review, unlike changes to the other handwritten extension types. If this is intentional, a comment explaining the exclusion would help; otherwise || needs.changes.outputs.container-image == 'true' should be added to the condition.

## Code Review ### Blocking Issues 1. **`container-image-check` and `container-image-lockfile` missing from `merge-gate` `needs`** (`ci.yml:1128–1173`) The new CI jobs `container-image-check` and `container-image-lockfile` are correctly wired into `claude-review`'s `needs` (lines 769–770), but they are absent from the `merge-gate` job's `needs` list. Every other extension follows the pattern of listing its check and lockfile jobs directly in the merge gate (e.g. `ssh-check`, `ssh-lockfile`, `vault-check`, etc.). Without these entries, the gate's `contains(needs.*.result, 'failure')` test cannot observe a failure from these jobs. The indirect path through `claude-review` doesn't rescue this: if `container-image-check` fails, `claude-review` evaluates `!failure()` → `false` and is **skipped** (result: `'skipped'`). The merge gate sees `skipped`, not `failure`, and passes. A broken container-image extension can therefore be merged without any gate blocking it. **Fix:** Add `container-image-check` and `container-image-lockfile` to the merge gate's `needs` array alongside the other extension check jobs. ### Suggestions 1. **Duplicate tag in podman buildx argv** (`runner.ts:219,230`) In `buildBuildxArgv` for podman, `args.tags[0]` is added twice: first via the `-t` loop over all tags (`argv.push("-t", tag)`) and then again as `argv.push("--manifest", args.tags[0])`. In podman, `--manifest` names the manifest list and `-t` tags individual platform images; both pointing at the same value may be redundant or could produce unexpected behavior (e.g. overwriting the manifest list name with an image tag). This is worth verifying against real podman behavior. 2. **Direct `npm:` specifier in `schemas.ts` instead of import map alias** (`schemas.ts:26`) `import { z } from "npm:zod@4.3.6"` bypasses the `"zod"` alias in `deno.json`. Using `import { z } from "zod"` would keep the import consistent with how other files in the repo consume mapped dependencies and would let `deno.json` be the single source of truth for the pinned version. 3. **`claude-adversarial-review` doesn't trigger for container-image-only PRs** (`ci.yml:921`) The adversarial review job's `if` condition gates on vaults, datastores, issue-lifecycle, kubernetes, workflows, cve, software-factory, and codegen — but not `needs.changes.outputs.container-image`. This PR therefore won't receive adversarial review, unlike changes to the other handwritten extension types. If this is intentional, a comment explaining the exclusion would help; otherwise `|| needs.changes.outputs.container-image == 'true'` should be added to the condition.
stack72 force-pushed issue-794-container-image from 8c43dc7114
Some checks failed
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / cve/researcher - lockfile up to date (pull_request) Has been skipped
CI / software-factory - check (pull_request) Has been skipped
CI / software-factory - fmt (pull_request) Has been skipped
CI / software-factory - test (pull_request) Has been skipped
CI / software-factory - lint (pull_request) Has been skipped
CI / software-factory - lockfile up to date (pull_request) Has been skipped
CI / container-image - check (pull_request) Successful in 1m2s
CI / container-image - fmt (pull_request) Successful in 1m9s
CI / container-image - lint (pull_request) Successful in 1m0s
CI / model/digitalocean - check (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / model/digitalocean - lockfile up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been skipped
CI / aws models - sample check (pull_request) Has been skipped
CI / container-image - test (pull_request) Successful in 58s
CI / aws models - lockfiles up to date (pull_request) Has been skipped
CI / gcp models - sample check (pull_request) Has been skipped
CI / container-image - lockfile up to date (pull_request) Successful in 1m5s
CI / gcp models - lockfiles up to date (pull_request) Has been skipped
CI / cloudflare models - sample check (pull_request) Has been skipped
CI / cloudflare models - lockfiles up to date (pull_request) Has been skipped
CI / codegen - check (pull_request) Has been skipped
CI / codegen - fmt (pull_request) Has been skipped
CI / codegen - lint (pull_request) Has been skipped
CI / codegen - lockfile up to date (pull_request) Has been skipped
CI / Adversarial Code Review (pull_request) Has been skipped
CI / CI Security Review (pull_request) Successful in 2m42s
CI / Claude Code Review (pull_request) Failing after 3m44s
CI / Merge Gate (pull_request) Failing after 26s
to ac87de5b9e
Some checks failed
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / cve/researcher - lockfile up to date (pull_request) Has been skipped
CI / software-factory - check (pull_request) Has been skipped
CI / software-factory - fmt (pull_request) Has been skipped
CI / software-factory - lint (pull_request) Has been skipped
CI / software-factory - test (pull_request) Has been skipped
CI / software-factory - lockfile up to date (pull_request) Has been skipped
CI / container-image - check (pull_request) Successful in 1m7s
CI / container-image - lint (pull_request) Successful in 1m16s
CI / model/digitalocean - check (pull_request) Has been skipped
CI / container-image - fmt (pull_request) Successful in 1m19s
CI / container-image - lockfile up to date (pull_request) Successful in 1m17s
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / container-image - test (pull_request) Successful in 1m28s
CI / model/digitalocean - lockfile up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been skipped
CI / aws models - sample check (pull_request) Has been skipped
CI / aws models - lockfiles up to date (pull_request) Has been skipped
CI / gcp models - sample check (pull_request) Has been skipped
CI / gcp models - lockfiles up to date (pull_request) Has been skipped
CI / cloudflare models - sample check (pull_request) Has been skipped
CI / cloudflare models - lockfiles up to date (pull_request) Has been skipped
CI / codegen - check (pull_request) Has been skipped
CI / codegen - fmt (pull_request) Has been skipped
CI / codegen - lint (pull_request) Has been skipped
CI / codegen - lockfile up to date (pull_request) Has been skipped
CI / Adversarial Code Review (pull_request) Has been skipped
CI / CI Security Review (pull_request) Successful in 2m43s
CI / Claude Code Review (pull_request) Failing after 4m24s
CI / Merge Gate (pull_request) Failing after 26s
2026-07-20 22:20:53 +00:00
Compare
Author
Owner

CI Security Review

Summary of Changes

This PR adds CI support for a new container-image extension by:

  1. Adding container-image to the change detection outputs and check_path calls
  2. Adding container-image-check (check/lint/fmt/test matrix) and container-image-lockfile jobs
  3. Adding container-image to the deps-audit find path
  4. Adding the new jobs to the needs lists of claude-review and claude-adversarial-review

Analysis

1. Prompt Injection — No new LLM interactions introduced. The new jobs are standard Deno CI tasks. The only change to LLM-invoking jobs (claude-review, claude-adversarial-review) is adding the new jobs to their needs dependency lists, which does not affect prompt construction or tool scoping.

2. Expression Injection — The new jobs use ${{ matrix.task }} in run: blocks (e.g., if [ "${{ matrix.task }}" = "fmt" ]). The matrix values are hardcoded in the workflow file as [check, lint, fmt, test] and are not attacker-controlled. This follows the same pattern as all other extension check jobs in this workflow.

3. Dangerous Triggers — No trigger changes. Workflow remains on pull_request (not pull_request_target), which runs in the PR context without automatic access to base-repo secrets.

4. Supply Chain — Uses actions/checkout@v6 and denoland/setup-deno@v2, both from trusted publishers (tag-only pins acceptable per policy). No new third-party actions, no curl | bash patterns.

5. Permissions — New jobs inherit contents: read from the workflow-level permission block. No additional permissions requested. This is appropriate for read-only check/lint/test jobs.

6. Secret Exposure — The new jobs do not reference any secrets. No risk of secret leakage.

7. Auto-merge & Trust Boundaries — The new jobs are added to the dependency chains of the review gates, which is the correct pattern to ensure they must pass before merging.

Verdict

PASS — Security-neutral change. The new container-image CI jobs follow the exact same pattern as all other extension check/lockfile jobs in the workflow, introduce no new attack surface, and are properly wired into the merge gate dependency chain.

## CI Security Review ### Summary of Changes This PR adds CI support for a new `container-image` extension by: 1. Adding `container-image` to the change detection outputs and `check_path` calls 2. Adding `container-image-check` (check/lint/fmt/test matrix) and `container-image-lockfile` jobs 3. Adding `container-image` to the `deps-audit` find path 4. Adding the new jobs to the `needs` lists of `claude-review` and `claude-adversarial-review` ### Analysis **1. Prompt Injection** — No new LLM interactions introduced. The new jobs are standard Deno CI tasks. The only change to LLM-invoking jobs (`claude-review`, `claude-adversarial-review`) is adding the new jobs to their `needs` dependency lists, which does not affect prompt construction or tool scoping. **2. Expression Injection** — The new jobs use `${{ matrix.task }}` in `run:` blocks (e.g., `if [ "${{ matrix.task }}" = "fmt" ]`). The matrix values are hardcoded in the workflow file as `[check, lint, fmt, test]` and are not attacker-controlled. This follows the same pattern as all other extension check jobs in this workflow. **3. Dangerous Triggers** — No trigger changes. Workflow remains on `pull_request` (not `pull_request_target`), which runs in the PR context without automatic access to base-repo secrets. **4. Supply Chain** — Uses `actions/checkout@v6` and `denoland/setup-deno@v2`, both from trusted publishers (tag-only pins acceptable per policy). No new third-party actions, no `curl | bash` patterns. **5. Permissions** — New jobs inherit `contents: read` from the workflow-level permission block. No additional permissions requested. This is appropriate for read-only check/lint/test jobs. **6. Secret Exposure** — The new jobs do not reference any secrets. No risk of secret leakage. **7. Auto-merge & Trust Boundaries** — The new jobs are added to the dependency chains of the review gates, which is the correct pattern to ensure they must pass before merging. ### Verdict **PASS** — Security-neutral change. The new `container-image` CI jobs follow the exact same pattern as all other extension check/lockfile jobs in the workflow, introduce no new attack surface, and are properly wired into the merge gate dependency chain.
Author
Owner

Code Review

Blocking Issues

  1. merge-gate doesn't include container-image-check or container-image-lockfile (ci.yml lines 1128–1173)

    The new container-image-check and container-image-lockfile jobs are defined and run, but they are absent from the merge-gate job's needs list. Any failure in those jobs does not block the PR from merging. Every other extension (vault, datastore, ssh, kubernetes, workflows, cve, software-factory) is wired into the gate — container-image must be too.

    Fix: add container-image-check and container-image-lockfile to the needs array of the merge-gate job.

  2. Comma injection in Docker OCI export --output flag (runner.ts line 365)

    `type=oci,dest=${dest}`
    

    Docker's --output parser splits its value on commas into key=value pairs. safeOptionValue blocks newlines and NUL bytes but not commas, so a user-supplied exportPath like out.tar,push=true would produce:

    --output type=oci,dest=out.tar,push=true
    

    Docker parses this as three directives: type=oci, dest=out.tar, push=true, causing an unintended registry push. Other directives (e.g. type=registry,ref=...) could redirect output entirely.

    Fix: add a comma check to safeOptionValue for exportPath, or use Docker's native --output key=value quoting mechanism if available, or assemble the output spec via a separate argv structure where dest is not embedded in a comma-parsed string (Docker also accepts --output type=oci --output dest=./out.tar with separate flags in some versions, but the cleanest fix is adding , to the blocked character set for exportPath).

  3. Stale/incorrect module docstring in checks.ts describes a login-supported check that doesn't exist (checks.ts lines 23–29)

    The module-level JSDoc says:

    Three checks, all computable from globalArgs alone:

    • runtime-available
    • login-supported — rejects Apple Containers (uses Keychain, not stdin)
    • buildx-available

    No checkLoginSupported function is exported, and the extension's checks map in container_image.ts has no login-supported entry. Worse, the description is factually wrong — the implementation does support Apple Containers for login via container registry login (runner.ts line 194). Future maintainers reading this docstring will be misled into believing Apple Containers can't log in, or will expect a check that doesn't exist.

    Fix: remove the login-supported bullet from the docstring (or add the check if the behaviour was intentional). Update the description to match the actual runtime matrix.

Suggestions

  1. Podman multi-platform silently drops tags beyond the first (runner.ts lines 231, 293)

    buildBuildxArgv for podman uses only args.tags[0] for --manifest, and buildPodmanManifestPushArgv only pushes args.tags[0]. If a caller passes tags: ["registry.example.com/app:latest", "registry.example.com/app:v1.2.3"] expecting both to be pushed, the second tag is silently dropped with no error or warning. Consider validating that tags.length === 1 for podman in buildBuildxArgv, or logging a warning in runBuildxBuild.

  2. runValidate has no tests (container_image_test.ts)

    runValidate contains non-trivial logic: it fans out to checkRuntimeAvailable, probeVersion, probeDaemon, and checkBuildxAvailable, then derives a methods map and conditionally throws. probeVersion and probeDaemon are untested private functions. The mock executor seam (setCommandExecutor) used throughout the test file could cover these paths.

  3. claude-adversarial-review skips container-image changes (ci.yml line 921)

    The adversarial review's if condition lists vaults, datastores, issue-lifecycle, kubernetes, workflows, cve, software-factory, and codegen — but not container-image. PRs touching only the container-image extension will not receive an adversarial review, unlike every other non-model extension. Consider adding needs.changes.outputs.container-image == 'true' to the condition.

  4. buildArgs record keys are not validated against safeOptionValue (schemas.ts line 90, runner.ts line 113)

    buildArgs: z.record(z.string(), z.string()).optional()
    

    The value side is plain z.string(), not safeOptionValue. A key or value containing a newline would be passed as --build-arg KEY\nVALUE, which the container runtime would likely reject but the schema layer doesn't catch first. Consistent with the rest of the schema hardening, both keys and values in buildArgs and labels maps could use safeOptionValue.

## Code Review ### Blocking Issues 1. **`merge-gate` doesn't include `container-image-check` or `container-image-lockfile`** (`ci.yml` lines 1128–1173) The new `container-image-check` and `container-image-lockfile` jobs are defined and run, but they are absent from the `merge-gate` job's `needs` list. Any failure in those jobs does not block the PR from merging. Every other extension (vault, datastore, ssh, kubernetes, workflows, cve, software-factory) is wired into the gate — container-image must be too. Fix: add `container-image-check` and `container-image-lockfile` to the `needs` array of the `merge-gate` job. 2. **Comma injection in Docker OCI export `--output` flag** (`runner.ts` line 365) ```ts `type=oci,dest=${dest}` ``` Docker's `--output` parser splits its value on commas into key=value pairs. `safeOptionValue` blocks newlines and NUL bytes but not commas, so a user-supplied `exportPath` like `out.tar,push=true` would produce: ``` --output type=oci,dest=out.tar,push=true ``` Docker parses this as three directives: `type=oci`, `dest=out.tar`, `push=true`, causing an unintended registry push. Other directives (e.g. `type=registry,ref=...`) could redirect output entirely. Fix: add a comma check to `safeOptionValue` for `exportPath`, or use Docker's native `--output` key=value quoting mechanism if available, or assemble the output spec via a separate argv structure where `dest` is not embedded in a comma-parsed string (Docker also accepts `--output type=oci --output dest=./out.tar` with separate flags in some versions, but the cleanest fix is adding `,` to the blocked character set for `exportPath`). 3. **Stale/incorrect module docstring in `checks.ts` describes a `login-supported` check that doesn't exist** (`checks.ts` lines 23–29) The module-level JSDoc says: > Three checks, all computable from globalArgs alone: > - runtime-available > - **login-supported — rejects Apple Containers (uses Keychain, not stdin)** > - buildx-available No `checkLoginSupported` function is exported, and the extension's `checks` map in `container_image.ts` has no `login-supported` entry. Worse, the description is factually wrong — the implementation *does* support Apple Containers for login via `container registry login` (runner.ts line 194). Future maintainers reading this docstring will be misled into believing Apple Containers can't log in, or will expect a check that doesn't exist. Fix: remove the `login-supported` bullet from the docstring (or add the check if the behaviour was intentional). Update the description to match the actual runtime matrix. ### Suggestions 1. **Podman multi-platform silently drops tags beyond the first** (`runner.ts` lines 231, 293) `buildBuildxArgv` for podman uses only `args.tags[0]` for `--manifest`, and `buildPodmanManifestPushArgv` only pushes `args.tags[0]`. If a caller passes `tags: ["registry.example.com/app:latest", "registry.example.com/app:v1.2.3"]` expecting both to be pushed, the second tag is silently dropped with no error or warning. Consider validating that `tags.length === 1` for podman in `buildBuildxArgv`, or logging a warning in `runBuildxBuild`. 2. **`runValidate` has no tests** (`container_image_test.ts`) `runValidate` contains non-trivial logic: it fans out to `checkRuntimeAvailable`, `probeVersion`, `probeDaemon`, and `checkBuildxAvailable`, then derives a `methods` map and conditionally throws. `probeVersion` and `probeDaemon` are untested private functions. The mock executor seam (`setCommandExecutor`) used throughout the test file could cover these paths. 3. **`claude-adversarial-review` skips `container-image` changes** (`ci.yml` line 921) The adversarial review's `if` condition lists vaults, datastores, issue-lifecycle, kubernetes, workflows, cve, software-factory, and codegen — but not `container-image`. PRs touching only the container-image extension will not receive an adversarial review, unlike every other non-model extension. Consider adding `needs.changes.outputs.container-image == 'true'` to the condition. 4. **`buildArgs` record keys are not validated against `safeOptionValue`** (`schemas.ts` line 90, `runner.ts` line 113) ```ts buildArgs: z.record(z.string(), z.string()).optional() ``` The value side is plain `z.string()`, not `safeOptionValue`. A key or value containing a newline would be passed as `--build-arg KEY\nVALUE`, which the container runtime would likely reject but the schema layer doesn't catch first. Consistent with the rest of the schema hardening, both keys and values in `buildArgs` and `labels` maps could use `safeOptionValue`.
fix: address review findings — merge-gate, comma injection, CI, tests
Some checks failed
CI / cve/mini-shai-hulud - check (pull_request) Has been skipped
CI / cve/mini-shai-hulud - fmt (pull_request) Has been skipped
CI / model/digitalocean - lockfile up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been skipped
CI / aws models - sample check (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lint (pull_request) Has been skipped
CI / cve/mini-shai-hulud - test (pull_request) Has been skipped
CI / cve/researcher - check (pull_request) Has been skipped
CI / cve/researcher - fmt (pull_request) Has been skipped
CI / cve/researcher - lint (pull_request) Has been skipped
CI / cve/researcher - test (pull_request) Has been skipped
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / cve/researcher - lockfile up to date (pull_request) Has been skipped
CI / software-factory - fmt (pull_request) Has been skipped
CI / software-factory - check (pull_request) Has been skipped
CI / software-factory - lint (pull_request) Has been skipped
CI / software-factory - test (pull_request) Has been skipped
CI / software-factory - lockfile up to date (pull_request) Has been skipped
CI / container-image - check (pull_request) Successful in 1m7s
CI / container-image - lint (pull_request) Successful in 1m3s
CI / container-image - fmt (pull_request) Successful in 1m5s
CI / container-image - test (pull_request) Failing after 1m10s
CI / model/digitalocean - check (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / container-image - lockfile up to date (pull_request) Successful in 55s
CI / Claude Code Review (pull_request) Has been skipped
CI / Adversarial Code Review (pull_request) Has been skipped
CI / CI Security Review (pull_request) Successful in 2m6s
CI / Merge Gate (pull_request) Failing after 25s
adb40ad4d9
Blockers:
- Add container-image-check/lockfile to merge-gate needs (ci.yml)
- Block commas in exportPath to prevent Docker --output flag injection
- Fix stale docstring in checks.ts (removed login-supported reference)

Suggestions:
- Add container-image to adversarial-review if condition (ci.yml)
- Warn when podman multi-platform-build silently drops tags beyond first
- Add validate method tests (runtime-not-found + real docker probe)
- Harden buildArgs/labels/env records with safeRecord validation

78 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

CI Security Review

Analysis

This PR adds CI jobs for a new container-image extension, following the identical pattern used by all other extensions in this workflow. The changes are:

  1. New container-image change detection (lines 31, 70): Adds output and check_path for the container-image/ directory — follows the same pattern as all other extensions.
  2. New container-image-check job (lines 453–480): Runs check/lint/fmt/test matrix — structurally identical to software-factory-check, ssh-check, etc.
  3. New container-image-lockfile job (lines 482–496): Verifies lockfile — structurally identical to other lockfile jobs.
  4. deps-audit update (line 720): Adds container-image to the find search paths for outdated dependency scanning.
  5. needs list updates (lines 761–762, 900–901, 1148–1149): Adds the new jobs to claude-review, claude-adversarial-review, and merge-gate dependency chains.
  6. Adversarial review condition update (line 921): Adds container-image to the trigger condition so adversarial review runs when container-image files change.

Checklist evaluation:

Check Result
Prompt injection No new LLM invocations. Existing review jobs unchanged except needs lists. No new vectors.
Expression injection ${{ matrix.task }} used in run: block (line 472), but values are hardcoded in the workflow matrix ([check, lint, fmt, test]) — not attacker-controlled. Same pre-existing pattern as all other jobs.
Dangerous triggers No trigger changes. Still pull_request only (not pull_request_target).
Supply chain Uses actions/checkout@v6 and denoland/setup-deno@v2 — same trusted-publisher actions as all existing jobs. No new dependencies.
Permissions New jobs inherit workflow-level contents: read. No escalation.
Secret exposure New jobs use no secrets.
Auto-merge / trust boundaries merge-gate correctly includes both new jobs in its needs list, so they must pass before merge.

Verdict

PASS — Security-neutral change. The new container-image CI jobs are structurally identical to existing extension jobs, introduce no new secrets usage, no new triggers, no new LLM interactions, and are correctly wired into the merge gate.

## CI Security Review ### Analysis This PR adds CI jobs for a new `container-image` extension, following the identical pattern used by all other extensions in this workflow. The changes are: 1. **New `container-image` change detection** (lines 31, 70): Adds output and `check_path` for the `container-image/` directory — follows the same pattern as all other extensions. 2. **New `container-image-check` job** (lines 453–480): Runs check/lint/fmt/test matrix — structurally identical to `software-factory-check`, `ssh-check`, etc. 3. **New `container-image-lockfile` job** (lines 482–496): Verifies lockfile — structurally identical to other lockfile jobs. 4. **`deps-audit` update** (line 720): Adds `container-image` to the `find` search paths for outdated dependency scanning. 5. **`needs` list updates** (lines 761–762, 900–901, 1148–1149): Adds the new jobs to `claude-review`, `claude-adversarial-review`, and `merge-gate` dependency chains. 6. **Adversarial review condition update** (line 921): Adds `container-image` to the trigger condition so adversarial review runs when container-image files change. **Checklist evaluation:** | Check | Result | |-------|--------| | Prompt injection | No new LLM invocations. Existing review jobs unchanged except `needs` lists. No new vectors. | | Expression injection | `${{ matrix.task }}` used in `run:` block (line 472), but values are hardcoded in the workflow matrix (`[check, lint, fmt, test]`) — not attacker-controlled. Same pre-existing pattern as all other jobs. | | Dangerous triggers | No trigger changes. Still `pull_request` only (not `pull_request_target`). | | Supply chain | Uses `actions/checkout@v6` and `denoland/setup-deno@v2` — same trusted-publisher actions as all existing jobs. No new dependencies. | | Permissions | New jobs inherit workflow-level `contents: read`. No escalation. | | Secret exposure | New jobs use no secrets. | | Auto-merge / trust boundaries | `merge-gate` correctly includes both new jobs in its `needs` list, so they must pass before merge. | ### Verdict **PASS** — Security-neutral change. The new `container-image` CI jobs are structurally identical to existing extension jobs, introduce no new secrets usage, no new triggers, no new LLM interactions, and are correctly wired into the merge gate.
fix: make validate tests CI-safe (no real Docker required)
All checks were successful
CI / cve/mini-shai-hulud - check (pull_request) Has been skipped
CI / cve/mini-shai-hulud - fmt (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lint (pull_request) Has been skipped
CI / cve/researcher - check (pull_request) Has been skipped
CI / cve/mini-shai-hulud - test (pull_request) Has been skipped
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been skipped
CI / aws models - sample check (pull_request) Has been skipped
CI / cve/researcher - fmt (pull_request) Has been skipped
CI / cve/researcher - lint (pull_request) Has been skipped
CI / cve/researcher - test (pull_request) Has been skipped
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / cve/researcher - lockfile up to date (pull_request) Has been skipped
CI / software-factory - fmt (pull_request) Has been skipped
CI / software-factory - check (pull_request) Has been skipped
CI / software-factory - lint (pull_request) Has been skipped
CI / software-factory - test (pull_request) Has been skipped
CI / software-factory - lockfile up to date (pull_request) Has been skipped
CI / container-image - fmt (pull_request) Successful in 56s
CI / model/digitalocean - check (pull_request) Has been skipped
CI / container-image - lint (pull_request) Successful in 1m9s
CI / container-image - lockfile up to date (pull_request) Successful in 1m8s
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / container-image - check (pull_request) Successful in 1m22s
CI / container-image - test (pull_request) Successful in 1m14s
CI / model/digitalocean - lockfile up to date (pull_request) Has been skipped
CI / CI Security Review (pull_request) Successful in 2m8s
CI / Adversarial Code Review (pull_request) Successful in 5m3s
CI / Claude Code Review (pull_request) Successful in 4m48s
CI / Merge Gate (pull_request) Successful in 28s
cf8200e926
Validate tests now inject a BinaryProbe that returns false, instead of
relying on a real Docker binary on PATH. Tests pass on CI runners
without Docker installed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

CI Security Review

Summary

This PR adds CI support for a new container-image extension by:

  • Adding change detection for the container-image/ path
  • Adding container-image-check and container-image-lockfile jobs
  • Including container-image in the dependency audit find scope
  • Adding the new jobs to the needs lists of claude-review, claude-adversarial-review, and merge-gate
  • Adding container-image to the adversarial review trigger condition

All changes follow the exact same patterns established by the other extension jobs (ssh, kubernetes, software-factory, etc.).

Checklist Results

  1. Prompt Injection: No new LLM interactions introduced. The existing review steps are unchanged in prompt construction. No findings.

  2. Expression Injection: The new jobs use ${{ matrix.task }} in run: blocks, but the matrix values are hardcoded to [check, lint, fmt, test] — repo-controlled, not attacker-influenced. This matches the pattern used by every other extension check job. No findings.

  3. Dangerous Triggers: No trigger changes. The workflow uses pull_request (not pull_request_target). No findings.

  4. Supply Chain: The new jobs use actions/checkout@v6 and denoland/setup-deno@v2 — both trusted publishers acceptable with tag-only pins per policy. No new third-party actions or remote script execution introduced. No findings.

  5. Permissions: The new jobs inherit the workflow-level contents: read default. No elevated permissions needed or requested. No findings.

  6. Secret Exposure: No secrets are used in the new container-image jobs. No findings.

  7. Auto-merge & Trust Boundaries: No changes to merge gate logic beyond adding the new jobs as dependencies, which strengthens the gate. No findings.

Verdict

PASS — Security-neutral change. The new container-image CI jobs are a mechanical extension of existing patterns with no new attack surface.

## CI Security Review ### Summary This PR adds CI support for a new `container-image` extension by: - Adding change detection for the `container-image/` path - Adding `container-image-check` and `container-image-lockfile` jobs - Including `container-image` in the dependency audit `find` scope - Adding the new jobs to the `needs` lists of `claude-review`, `claude-adversarial-review`, and `merge-gate` - Adding `container-image` to the adversarial review trigger condition All changes follow the exact same patterns established by the other extension jobs (ssh, kubernetes, software-factory, etc.). ### Checklist Results 1. **Prompt Injection**: No new LLM interactions introduced. The existing review steps are unchanged in prompt construction. **No findings.** 2. **Expression Injection**: The new jobs use `${{ matrix.task }}` in `run:` blocks, but the matrix values are hardcoded to `[check, lint, fmt, test]` — repo-controlled, not attacker-influenced. This matches the pattern used by every other extension check job. **No findings.** 3. **Dangerous Triggers**: No trigger changes. The workflow uses `pull_request` (not `pull_request_target`). **No findings.** 4. **Supply Chain**: The new jobs use `actions/checkout@v6` and `denoland/setup-deno@v2` — both trusted publishers acceptable with tag-only pins per policy. No new third-party actions or remote script execution introduced. **No findings.** 5. **Permissions**: The new jobs inherit the workflow-level `contents: read` default. No elevated permissions needed or requested. **No findings.** 6. **Secret Exposure**: No secrets are used in the new container-image jobs. **No findings.** 7. **Auto-merge & Trust Boundaries**: No changes to merge gate logic beyond adding the new jobs as dependencies, which strengthens the gate. **No findings.** ### Verdict **PASS** — Security-neutral change. The new `container-image` CI jobs are a mechanical extension of existing patterns with no new attack surface.
Author
Owner

Adversarial Review

Critical / High

None.

Medium

  1. Silent no-op when exportFormat is set without exportPathcontainer-image/extensions/models/_lib/operations.ts:245

    The guard if (args.exportFormat && args.exportPath) means if a user sets exportFormat to oci but forgets exportPath, the build silently succeeds without exporting. The returned resource will contain exportFormat oci and exportPath undefined, giving no indication that export was skipped.

    Breaking example: A workflow passes exportFormat oci without exportPath. The build succeeds, the resource says exportFormat oci, but no archive file is written. The next workflow step expecting the archive fails with a confusing file-not-found error.

    Suggested fix: Add cross-field validation — either a z.refine on BuildArgsSchema requiring exportPath when exportFormat is set (and vice versa), or a runtime check in runBuild that throws early when only one of the pair is provided.

  2. Docker silently ignores tlsVerify false for login and pushcontainer-image/extensions/models/_lib/runner.ts:197-199 and runner.ts:330

    When binary is docker and tlsVerify is false, no flag is passed to docker login or docker push. Docker does not support a per-command --tls-verify flag — it requires insecure-registries in /etc/docker/daemon.json. The user thinks TLS verification is disabled but it is not.

    Breaking example: User configures binary docker with server my-http-registry:5000 and sets tlsVerify false. Login fails with a TLS error despite the explicit opt-out, because Docker ignores the flag entirely.

    Suggested fix: When binary is docker and tlsVerify is false, log a warning via ctx.logger.warn explaining that Docker requires daemon-level configuration for insecure registries, or document this in the schema description for tlsVerify.

  3. No resource written when build succeeds but export failscontainer-image/extensions/models/_lib/operations.ts:245-267

    If runBuildCommand returns exitCode 0 but runExportCommand fails, the function throws at line 263 without ever calling ctx.writeResource. This is inconsistent with the build-failure path (lines 231-242) which writes a resource before throwing. The build stdout/stderr is lost.

    Breaking example: Build succeeds, but export fails due to a full disk. The user gets an export-failed error but has no resource to inspect the successful build output. In a build-failure case, they would have a resource with the full stdout/stderr.

    Suggested fix: Write the buildResult resource before attempting the export step, so the build output is always captured regardless of export outcome.

Low

  1. probeDaemon and probeVersion bypass the exec test seamcontainer-image/extensions/models/_lib/operations.ts:82-127

    These functions spawn Deno.Command directly instead of going through the exec function in runner.ts. The validate success path (runtime found, daemon probed) is untestable without a real container runtime. Current tests only cover the runtime-not-found path. Not blocking — a testability concern, not a correctness issue.

  2. stdin writer lacks try/finally for closecontainer-image/extensions/models/_lib/runner.ts:90-92

    If writer.write throws, writer.close is never called. The pipe is cleaned up when the child process exits, and the data is always small (passwords, tiny Dockerfiles), so this is unlikely to matter in practice.

Verdict

PASS — The code is solid. Command injection is properly prevented via Deno.Command with array args and schema-level newline/NUL/comma rejection. Passwords never appear in argv or persisted resources. Error handling is mostly consistent, and tests cover the main paths with proper mock cleanup in finally blocks. The medium findings are real UX papercuts (silent no-ops, missing resources) but none are security issues, data corruption, or crash-in-production risks.

## Adversarial Review ### Critical / High None. ### Medium 1. **Silent no-op when exportFormat is set without exportPath** — `container-image/extensions/models/_lib/operations.ts:245` The guard `if (args.exportFormat && args.exportPath)` means if a user sets exportFormat to oci but forgets exportPath, the build silently succeeds without exporting. The returned resource will contain exportFormat oci and exportPath undefined, giving no indication that export was skipped. **Breaking example:** A workflow passes exportFormat oci without exportPath. The build succeeds, the resource says exportFormat oci, but no archive file is written. The next workflow step expecting the archive fails with a confusing file-not-found error. **Suggested fix:** Add cross-field validation — either a z.refine on BuildArgsSchema requiring exportPath when exportFormat is set (and vice versa), or a runtime check in runBuild that throws early when only one of the pair is provided. 2. **Docker silently ignores tlsVerify false for login and push** — `container-image/extensions/models/_lib/runner.ts:197-199` and `runner.ts:330` When binary is docker and tlsVerify is false, no flag is passed to docker login or docker push. Docker does not support a per-command --tls-verify flag — it requires insecure-registries in /etc/docker/daemon.json. The user thinks TLS verification is disabled but it is not. **Breaking example:** User configures binary docker with server my-http-registry:5000 and sets tlsVerify false. Login fails with a TLS error despite the explicit opt-out, because Docker ignores the flag entirely. **Suggested fix:** When binary is docker and tlsVerify is false, log a warning via ctx.logger.warn explaining that Docker requires daemon-level configuration for insecure registries, or document this in the schema description for tlsVerify. 3. **No resource written when build succeeds but export fails** — `container-image/extensions/models/_lib/operations.ts:245-267` If runBuildCommand returns exitCode 0 but runExportCommand fails, the function throws at line 263 without ever calling ctx.writeResource. This is inconsistent with the build-failure path (lines 231-242) which writes a resource before throwing. The build stdout/stderr is lost. **Breaking example:** Build succeeds, but export fails due to a full disk. The user gets an export-failed error but has no resource to inspect the successful build output. In a build-failure case, they would have a resource with the full stdout/stderr. **Suggested fix:** Write the buildResult resource before attempting the export step, so the build output is always captured regardless of export outcome. ### Low 1. **probeDaemon and probeVersion bypass the exec test seam** — `container-image/extensions/models/_lib/operations.ts:82-127` These functions spawn Deno.Command directly instead of going through the exec function in runner.ts. The validate success path (runtime found, daemon probed) is untestable without a real container runtime. Current tests only cover the runtime-not-found path. Not blocking — a testability concern, not a correctness issue. 2. **stdin writer lacks try/finally for close** — `container-image/extensions/models/_lib/runner.ts:90-92` If writer.write throws, writer.close is never called. The pipe is cleaned up when the child process exits, and the data is always small (passwords, tiny Dockerfiles), so this is unlikely to matter in practice. ### Verdict **PASS** — The code is solid. Command injection is properly prevented via Deno.Command with array args and schema-level newline/NUL/comma rejection. Passwords never appear in argv or persisted resources. Error handling is mostly consistent, and tests cover the main paths with proper mock cleanup in finally blocks. The medium findings are real UX papercuts (silent no-ops, missing resources) but none are security issues, data corruption, or crash-in-production risks.
Author
Owner

Code Review

Blocking Issues

None.

Suggestions

  1. probeDaemon and probeVersion bypass the mock executor seam (operations.ts:82-127). Both functions call new Deno.Command(...) directly instead of going through exec(), so the validate success path (runtime found, daemon reachable) cannot be exercised in CI without Docker or Podman actually installed. Every existing validate test uses notFoundProbe (returns false), which short-circuits the if (runtimeFound) block and never reaches either probe. If you want that code path covered, extract them behind the same injectable pattern used by BinaryProbe/BuildxProbe, or accept the gap and add a // tested manually note.

  2. Docker's tlsVerify=false is silently ignored for login and push (runner.ts:197-200, runner.ts:330). Podman and Apple Containers both handle the flag; Docker does not (insecure registries must be configured in daemon.json instead). A user who sets tlsVerify: false with binary: "docker" will get no warning. Adding a ctx.logger.warn(...) — or documenting the limitation in the schema description — would prevent a confusing silent no-op.

  3. build with exportFormat: "oci" and binary: "docker" silently requires buildx (runner.ts:356-365). The OCI export path emits docker buildx build --output type=oci,dest=... -, which fails at runtime if the buildx plugin is absent. The buildx-available pre-flight check only applies to multi-platform-build, not build, so there's no early signal. Worth noting in the exportFormat schema description (e.g. "OCI export with Docker requires the buildx plugin") or adding a runtime check before spawning the command.

  4. README argument tables for login and push omit tlsVerify (README.md:198-202, README.md:163-166). The LoginArgsSchema and PushArgsSchema both have tlsVerify: z.boolean().default(true), but neither table documents it. Users on insecure registries need to know it exists.

  5. @systeminit/swamp-testing is imported in deno.json but unused (deno.json:16). None of the test files import from this package. If it's not needed for this extension type, remove the import to keep the lockfile minimal; if conformance helpers for model extensions are planned, a TODO comment would clarify intent.

## Code Review ### Blocking Issues None. ### Suggestions 1. **`probeDaemon` and `probeVersion` bypass the mock executor seam** (`operations.ts:82-127`). Both functions call `new Deno.Command(...)` directly instead of going through `exec()`, so the validate success path (runtime found, daemon reachable) cannot be exercised in CI without Docker or Podman actually installed. Every existing validate test uses `notFoundProbe` (returns false), which short-circuits the `if (runtimeFound)` block and never reaches either probe. If you want that code path covered, extract them behind the same injectable pattern used by `BinaryProbe`/`BuildxProbe`, or accept the gap and add a `// tested manually` note. 2. **Docker's `tlsVerify=false` is silently ignored for `login` and `push`** (`runner.ts:197-200`, `runner.ts:330`). Podman and Apple Containers both handle the flag; Docker does not (insecure registries must be configured in `daemon.json` instead). A user who sets `tlsVerify: false` with `binary: "docker"` will get no warning. Adding a `ctx.logger.warn(...)` — or documenting the limitation in the schema description — would prevent a confusing silent no-op. 3. **`build` with `exportFormat: "oci"` and `binary: "docker"` silently requires buildx** (`runner.ts:356-365`). The OCI export path emits `docker buildx build --output type=oci,dest=... -`, which fails at runtime if the buildx plugin is absent. The `buildx-available` pre-flight check only applies to `multi-platform-build`, not `build`, so there's no early signal. Worth noting in the `exportFormat` schema description (e.g. "OCI export with Docker requires the buildx plugin") or adding a runtime check before spawning the command. 4. **README argument tables for `login` and `push` omit `tlsVerify`** (`README.md:198-202`, `README.md:163-166`). The `LoginArgsSchema` and `PushArgsSchema` both have `tlsVerify: z.boolean().default(true)`, but neither table documents it. Users on insecure registries need to know it exists. 5. **`@systeminit/swamp-testing` is imported in `deno.json` but unused** (`deno.json:16`). None of the test files import from this package. If it's not needed for this extension type, remove the import to keep the lockfile minimal; if conformance helpers for model extensions are planned, a TODO comment would clarify intent.
stack72 deleted branch issue-794-container-image 2026-07-20 23:38:43 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
swamp-club/swamp-extensions!122
No description provided.