encrypt-container-secrets: state the verbatim key mapping; make A-3 hash-based - #31
Merged
phoenix-server merged 3 commits intoSep 17, 2026
Merged
Conversation
…3 could not pass Both defects in #26, found while implementing v0.2.1 for real. ## 1. The verbatim key-name mapping was undocumented `modules/env-secrets.md` said the container "hands the values to the application as environment variables" without ever saying that `sops exec-env` injects each store key **verbatim** — the key name *is* the variable name, with no mapping layer and no rename. That is exactly the case a migration breaks. A value that compose used to map (`environment: AUTH_PASSWORD: ${MOUSEHOLE_PASS}`) keeps its store key `MOUSEHOLE_PASS`, the container starts, and the application crash-loops with "authentication is not configured" because it reads `AUTH_PASSWORD`. Nothing in the boot path errors, which is what makes it expensive. Nothing caught it either: the Phase 7 environment check counted matching environment lines, and a wrongly-named variable simply does not appear in that count. Now: - `modules/env-secrets.md` opens with the rule and a concrete before/after (compose mapping → store key), the failure symptom, and the remedy under Failure Behavior. - `SCHEMATIC.md` carries the rule as a Preservation List constraint (pitfalls) and as a dotenv-store contract point. - Phase 7 step 2 compares **names** instead of counting lines: the store's keys (decrypted host-side, `sops_` metadata filtered), the application process's environment names (found by the `SOPS_AGE_KEY_FILE` marker the wrapper exports, not by a guessed PID), and the names the application reads. A `MISSING <name>` is the misnamed-key signature, and the Phase 7 failure modes now list it first. - Q-2's default names that marker-based discovery. ## 2. A-3 could not pass as written ```sh docker exec <service> sh -c 'grep -rl AGE-SECRET-KEY-1 / 2>/dev/null' ``` It matches the dedicated key file that is *supposed* to be mounted, so the expected result was ambiguous, and it walks `/proc`, where it does not terminate inside a container. A-3 is now a content-hash procedure: the mounted key is identified by sha256 against the host's dedicated key (must match) and against the master key (must not), and the master key's bytes are searched for by digest over the container's own filesystems — `/proc`, `/sys` and `/dev` pruned, `-size` filtered before anything is hashed. The same unbounded pattern was swept out of Phase 7 step 4 and A-4: both are now scoped to the writable locations, with `docker diff` alongside so a write outside the scanned directories is still visible. `version: 0.2.1 → 0.2.2`, `updated: 2026-09-17`, and a Decisions entry recording both fixes. Refs #26
Deploying agentic-schematics with
|
| Latest commit: |
e140882
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://027a2ed7.agentic-schematics.pages.dev |
| Branch Preview URL: | https://fix-encrypt-container-secret.agentic-schematics.pages.dev |
…hat produced them The Decisions entry narrated two defects and cited the issue that tracked them, and the A-3 provenance paragraph told the story of the test it replaced. Both now state what is true: the store key name is the variable name, and A-3 decides by content hash rather than by a substring that also matches the key which is supposed to be mounted. Everything a builder needs is kept — the traps (a count of environment lines cannot see a wrongly-named variable; a substring scan matches the mounted key and descends into /proc, where it does not terminate) and the verification provenance (verified in alpine:3: 0 with no copy present, 0 for a same-size file with different content, 1 when a copy is planted, each under a second). Prose only: no command, threshold, order, or check logic changed.
…se, as the state a reader migrates from rather than a 'used to'
cameri
approved these changes
Sep 17, 2026
phoenix-server
deleted the
fix/encrypt-container-secrets-key-mapping-and-a3
branch
September 17, 2026 23:23
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 #26.
Problem
The verbatim key-name mapping was undocumented.
sops exec-envinjects each store key verbatim — the key name is the variable name — andmodules/env-secrets.mdnever said so. An implementer migrating a value out of a composeenvironment:mapping (AUTH_PASSWORD: ${MOUSEHOLE_PASS}) keeps the store keyMOUSEHOLE_PASS, the container starts, and the application crash-loops with "authentication is not configured" because it readsAUTH_PASSWORD. Nothing detected it either: the Phase 7 environment check counted matching environment lines, so a wrongly-named variable was invisible to it.A-3 could not pass as written.
docker exec <service> sh -c 'grep -rl AGE-SECRET-KEY-1 / 2>/dev/null'matches the dedicated key file that is supposed to be mounted, so its expected result was ambiguous rather than pass/fail — and it walks/proc, where it does not terminate inside a container (observed: still running after 20 s, with the intended key file already matched).Change
modules/env-secrets.md: the verbatim rule with a concrete before/after (compose mapping → store key), the symptom, and the remedy in Failure Behavior; the Outputs and Idempotency notes now say names, not counts.SCHEMATIC.md: the rule as a Preservation List constraint (the pitfalls list) and as a dotenv-store contract point. Phase 7 step 2 compares names — the store's keys (decrypted host-side,sops_metadata filtered), the application process's environment names (found by theSOPS_AGE_KEY_FILEmarker the wrapper exports, not a guessed PID), and the names the application reads;MISSING <name>is the misnamed-key signature, now first in the Phase 7 failure modes. Q-2's default names that discovery method./proc,/sys,/devpruned,-sizefiltered before anything is hashed, so it terminates in under a second.docker diffalongside so a write outside them is still visible.version: 0.2.1 → 0.2.2(content change to a published spec),updated: 2026-09-17, plus a Decisions entry recording both fixes.Verification
Both command blocks were run verbatim against real containers (
alpine:3, busyboxfind/sha256sum/grep), one started with the wrapper's marker variable and a mounted key file:00(hash, not size or substring, decides)1mounted key == dedicated key,mounted key != master keyLEGACY_PASS, application readsAUTH_PASSWORD)MISSING AUTH_PASSWORD/procafter 20 s, intended key file already matchedEach A-3 scan finished in under a second; the whole run (container create → both scans → identity checks → cleanup) took 1.4 s. Containers were created for the test and removed afterwards.
No other schematic is touched, and no pin changes: nothing in the catalog pins this schematic, and every pin is read at its own commit, so this commit cannot invalidate one (the validator's 21 pins verify on this branch).
Not in this PR: the frontmatter/description parity work (#23) and the ten-principles list (#24).
marketplace.jsonstill reads "SOPS and age" where this spec's frontmatter says "SOPS + age" — pre-existing, #23's scope, left alone.