feat(relay): refuse to boot as effective uid 0 in production mode (#78)#85
Merged
Conversation
Adds a deterministic in-process backstop for the CI non-root-build contract: when PYRYCODE_RELAY_PRODUCTION=1 AND syscall.Geteuid() == 0, the relay refuses to start with exit 2 before any listener is opened. A `docker run --user 0` or a missing/overridden USER directive at deploy time would otherwise silently run the internet-facing process as root. Mirrors the sibling pattern from #77: exported sentinel ErrRunningAsRoot, CheckRunningAsRoot(geteuid, getenv) with injected seams for tests, and structured log fields (effective_uid, env_var, fix) at the call site. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
Author
Code Review: #78Decision: PASS FindingsNone. SummaryImplementation tracks the architect's spec verbatim:
Security review (label-gated)Spec contains a complete Security goggles on the diff itself:
Local The double |
Folds the second consumer of the PYRYCODE_RELAY_PRODUCTION contract into the production-mode feature doc (CheckRunningAsRoot + ErrRunningAsRoot), adds per-ticket codebase note for #78, and refreshes the INDEX entry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
What
Adds a deterministic in-process backstop for the CI non-root-build contract: when
PYRYCODE_RELAY_PRODUCTION=1ANDsyscall.Geteuid() == 0, the relay refuses to start with exit 2 before any listener is opened. Adocker run --user 0,securityContext: { runAsUser: 0 }, or a missing/overriddenUSERDockerfile directive at deploy time would otherwise silently run the internet-facing process as root.Three pieces, all minimal:
internal/relay/production.go— new exported sentinelErrRunningAsRootand check functionCheckRunningAsRoot(geteuid func() int, getenv func(string) string) error, sibling toErrInsecureListenInProduction/CheckInsecureListenInProductionfrom relay: refuse to boot with --insecure-listen in production mode #77. ReusesIsProductionMode; no second read ofPYRYCODE_RELAY_PRODUCTION.internal/relay/production_test.go— table-driven matrix (non-prod+uid 0 → nil; prod+uid 1000 → nil; prod+uid 0 → sentinel; prod+nobody-uid 65534 → nil) plus aTestErrRunningAsRoot_IsBranchableparalleling the sibling.fakeGeteuidmirrorsfakeGetenv.cmd/pyrycode-relay/main.go— wired in immediately afterCheckInsecureListenInProductionand beforeCheckCapabilities, with structured log fieldserr,env_var=PYRYCODE_RELAY_PRODUCTION,effective_uid=syscall.Geteuid(), and afixhint naming the two valid resolutions. Exit code 2 matches the sibling block.Issue
Closes #78.
Testing
go test -race ./...— all green (full suite).go vet ./...— clean.go build ./cmd/pyrycode-relay— builds.TestCheckRunningAsRoot_Matrix(4 rows) andTestErrRunningAsRoot_IsBranchable. Tests use the injectedfunc() intseam — no re-exec as root, not.Setenv,t.Parallel()-safe.Architecture compliance
Follows
docs/specs/architecture/78-refuse-boot-as-root-in-production.mdverbatim:ErrRunningAsRoot), check signature (CheckRunningAsRoot(geteuid, getenv)), uid source (injectedfunc() int), and exit code (2) — all as specified.IsProductionModefrom relay: refuse to boot with --insecure-listen in production mode #77 — no duplicate env-var read.CheckEnvConfig→CheckInsecureListenInProduction→CheckRunningAsRoot→CheckCapabilitiespreserved (security review § Adversarial framing).effective_uid+env_var=PYRYCODE_RELAY_PRODUCTIONrequirement;env_varcarries the name, not the value (per relay: refuse to boot with --insecure-listen in production mode #77 precedent and the security review's "Error messages, logs, telemetry" guidance — keeps unvalidated operator strings out of centralised logs).syscall.Geteuidworks on linux and darwin without build tags.🤖 Generated with Claude Code