security: pin mitmproxy image chain + rtk installer (Phase 1 of #20) - #100
Open
shejnowicz wants to merge 12 commits into
Open
security: pin mitmproxy image chain + rtk installer (Phase 1 of #20)#100shejnowicz wants to merge 12 commits into
shejnowicz wants to merge 12 commits into
Conversation
Pin the mitmproxy image chain to an exact version (12.2.3): the public image referenced by generated compose-proxy.yml, and the two derived ghcr secret-provider images. Two-file source of truth (CLI constants + images/mitmproxy.env) enforced by a contract test. Weekly cron becomes a latest-only tracking channel; versioned tags publish only from master pushes and are never overwritten. rtk install script pinned to a commit SHA (script-level freeze; binary pin deferred until rtk has stable releases). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ
#20) Introduces SCT_MITMPROXY_VERSION="12.2.3" in constants.bash and threads it through the compose-proxy.yml template placeholder and apply_secret_provider's 1password/protonpass ghcr images, replacing all `:latest` tags on the CLI side. devcontainer.bash now sources constants.bash directly (it wasn't transitively sourced through its own dependency chain, only accidentally via caller ordering in production). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ
…ebuilds latest only (#20) Adds images/mitmproxy.env as the single source of truth for the pinned mitmproxy base image version, consumed by both build workflows and both Dockerfiles, and asserted equal to the CLI-side SCT_MITMPROXY_VERSION by a new contract test. Both Dockerfiles take MITMPROXY_VERSION as a build arg with no default (pin lives in exactly one place) and parameterize their FROM. Both workflows load the pin into $GITHUB_ENV for all non-scheduled triggers, override it to `latest` for the weekly schedule, and reshape their metadata-action tags into three buckets: schedule publishes only `latest` (tracking channel), push/workflow_dispatch publish the pinned version + sha as immutable tags, and pull_request keeps its existing ref tag with no push (unchanged behavior).
The pinned <version> tag was reachable from workflow_dispatch on any ref, so a manual dispatch off a feature branch could overwrite the canonical immutable tag with unmerged code. Gate it to push (already master-only) or workflow_dispatch explicitly on refs/heads/master; off-master dispatch now falls back to a branch-name + sha tag instead, so manual runs are never tagless and never collide with the canonical tag.
Pin the rtk install.sh download URL to a specific commit SHA rather than the master branch, closing the arbitrary-code-via-master attack vector. The script itself downloads a binary internally, so the final binary is not fully pinned until rtk publishes stable releases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ
…rkflows The mitmproxy_version.bats contract test is the only sync enforcement between cli/lib/constants.bash and images/mitmproxy.env, but cli-tests.yml only triggered on cli/** — a bump touching only the env file never ran the test, so image workflows could go green while the CLI-side constant silently drifted out of sync. Also add -m1 to both image workflows' grep of MITMPROXY_VERSION so a duplicate key in the env file can't make the workflow (last-wins) and the contract test (first-wins, -m1) resolve to different values. Fixes #20
The comment claimed rtk "has only dev-* pre-releases" so the binary couldn't be pinned — verified false: `gh api repos/rtk-ai/rtk/releases/latest` returns v0.45.0 with prerelease: false, and that release's target commit is the same b34be37 SHA already pinned for install.sh. rtk's install.sh also supports RTK_VERSION=vX.Y.Z pinning and SHA-256-verifies the downloaded archive against the release's checksums.txt. Add RTK_VERSION=v0.45.0 to the install invocation so both the script (commit SHA) and the binary it fetches (checksum-verified release) are pinned. Extend rtk.bats to assert the emitted block contains RTK_VERSION=v so the binary pin can't silently regress. Also update the README's host-side rtk install instructions to use the same pinned commit + RTK_VERSION instead of pointing at master. Fixes #20
cli/README.md's bump procedure was missing the merge-day race: between merging a version bump and the image workflows publishing the new versioned ghcr tags, `docker compose up` fails loudly with `manifest unknown` for the op/pass providers (provider none is unaffected — it resolves straight to the public mitmproxy/mitmproxy Docker Hub tag). Document waiting for both workflows before standing up or upgrading a secret-provider project. Also fix the bump procedure's image name, which referenced a nonexistent `sandcat-mitmproxy` — the published images are `sandcat-mitmproxy-op` and `sandcat-mitmproxy-pass`. Soften "immutable" to reflect that it's a convention, not a guarantee: a dispatch on master can re-push the same versioned tag, and registry tags aren't cryptographically frozen without digest pinning. Fixes #20
These are working documents from the spec-driven-development process, not project documentation — drop them before merge.
The "Note: Cursor agent support..." paragraph sat between the pinning docs and the init-command reference since before this branch, but inserting the mitmproxy bump procedure directly above it made it read as part of the merge-day-race guidance. Move it into the `sandcat init devcontainer` section, where `--agent cursor` is documented and the note actually applies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ
pass-cli.env sits next to its single consumer's Dockerfile; mitmproxy.env is consumed by BOTH image builds, so it lives in their common parent. Spell out the placement rule so the asymmetry doesn't read as an accident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #20.
Summary
Removes `latest`-tag supply-chain exposure from sandcat's security-critical image chain. The mitmproxy image sees all agent plaintext and holds real secret values — today every `docker compose pull` silently swaps it for whatever upstream published, and the weekly CI cron overwrites the ghcr `latest` tags that generated projects reference.
What's pinned now (to 12.2.3):
Two-file source of truth, contract-enforced: `SCT_MITMPROXY_VERSION` in `cli/lib/constants.bash` (ships with the CLI) + `MITMPROXY_VERSION` in `images/mitmproxy.env` (consumed by CI). A contract test (`cli/test/compat/mitmproxy_version.bats`) fails CI when they diverge — and `cli-tests.yml` now triggers on `images/mitmproxy.env` too, so a one-sided bump can't slip through. Bump = 2 lines, documented in `cli/README.md`.
CI channel policy (traced across all 5 event buckets in review):
Deliberate design choices (for reviewer visibility)
Merge-day sequencing (important)
The ghcr `12.2.3` tags publish only AFTER this merges (the image workflows trigger on this push). Until both workflows go green (~5-25 min), a freshly generated secret-provider project fails `docker compose up` with a loud, retry-safe `manifest unknown`. Provider `none` is unaffected (the public Docker Hub tag exists). Documented in the bump procedure.
Post-merge checklist: confirm both image workflows published `sandcat-mitmproxy-op:12.2.3` and `-pass:12.2.3`, then smoke-test a 1password-provider project.
Test plan
🤖 Generated with Claude Code
https://claude.ai/code/session_01DFCiFbv1Cpr7yzCU8ftvzZ