feat(ci): a chart must run as the uid its image owns files as - #427
Merged
Conversation
The MCP chart pinned `runAsUser: 1000` against an image that creates
`evolith` at 1001 and chowns the corpus to it. A securityContext
overrides the image's USER, so the process landed on the base image's
`node` user: `policy.wasm` is mode 600 owned by 1001, OPA got EACCES,
dispatch requires both engines to allow, and every `tools/call` returned
FORBIDDEN — 51 tools advertised, none executable.
That value is correct today because it was corrected by hand two days
ago, and nothing was watching it. This is the part that replaces the
hand: a hand-corrected value with no guard is one edit from the same
outage, and the next person to meet it meets it in a cluster.
Nothing here could have caught it before. The test harness runs the MCP
server IN-PROCESS — no container, no securityContext — so the defect was
unreachable from every suite in the repository and only a live deploy
showed it.
All four pinned fields are compared, not just `runAsUser`: `runAsGroup`
and `fsGroup` too, because getting three right and one wrong reproduces
the same failure through a different door — `fsGroup` alone decides who
owns mounted volumes. A chart pinning NOTHING is reported rather than
passed: it inherits the image's USER, which is right today and unanchored
tomorrow.
Watched failing, not declared able to:
- reintroducing the exact 1000/1001 shape → 2 divergences, exit 1;
- moving ONLY fsGroup → 1 divergence, exit 1;
- 10 unit tests over the predicate, including a green case so a
reject-everything predicate cannot masquerade as thorough.
The first version failed for its own reason and not the repository's: its
`adduser` regex required every token before `-u` to be a flag, but the
username is POSITIONAL (`adduser -S evolith -u 1001 -G evolith`), so it
read all three real Dockerfiles as unparseable. Caught by running it; the
shape is pinned by a test.
Guards: 42 classifies it (74 total, none unprotected), 43 sees it turn
red on the empty fixture (50/50), governance suite 17/17, gitleaks clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📊 Bilingual Coverage ImpactPR Changes
Repository Coverage
✅ Good: All EN changes have ES counterparts. Generated by GitHub Actions |
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.
The MCP chart pinned
runAsUser: 1000against an image that createsevolithat 1001 and chowns the corpus to it. AsecurityContextoverrides the image's USER, so the process landed on the base image'snodeuser:policy.wasmis mode 600 owned by 1001. Dispatch requires both engines to allow, OPA erroring fail-closes, and everytools/callreturned FORBIDDEN — 51 tools advertised, none executable.Why a guard and not just the fix
That value is correct today because it was corrected by hand two days ago, and nothing was watching it. A hand-corrected value with no guard is one edit from the same outage, and the next person to meet it meets it in a cluster.
Nothing here could have caught it before. The test harness runs the MCP server in-process — no container, no securityContext — so the defect was unreachable from every suite in the repository. Only a live deploy showed it.
What it checks
All four pinned fields, not just
runAsUser:runAsGroupandfsGrouptoo, because getting three right and one wrong reproduces the same failure through a different door (fsGroupalone decides who owns mounted volumes). A chart pinning nothing is reported, not passed — it inherits the image's USER, which is right today and unanchored tomorrow.Watched failing, not declared able to
fsGroupThe first version failed for its own reason and not the repository's: its
adduserregex required every token before-uto be a flag, but the username is positional (adduser -S evolith -u 1001 -G evolith), so it read all three real Dockerfiles as unparseable. Caught by running it; the shape is now pinned by a test.42-validate-guard-denominators43-validate-guard-negative-fixturesci-runner.mjs governancegitleaks dir .🤖 Generated with Claude Code