-
Notifications
You must be signed in to change notification settings - Fork 0
exec.Runner: child stderr persists to the log file, and both streams are uncapped #512
Copy link
Copy link
Open
Labels
area:forgectlComponent: forgectl binaryComponent: forgectl binaryarea:securitySubject: security. Severity lives on impact:*Subject: security. Severity lives on impact:*impact:lowCosmetic or local inconvenience; obvious workaround; nobody blockedCosmetic or local inconvenience; obvious workaround; nobody blockedkind:choreUpkeep with no intended behavior change: deps, CI, cleanupUpkeep with no intended behavior change: deps, CI, cleanuplikelihood:lowNeeds unusual preconditions; not expected within the horizon (30 days)Needs unusual preconditions; not expected within the horizon (30 days)
Description
Activity
Metadata
Metadata
Assignees
Labels
area:forgectlComponent: forgectl binaryComponent: forgectl binaryarea:securitySubject: security. Severity lives on impact:*Subject: security. Severity lives on impact:*impact:lowCosmetic or local inconvenience; obvious workaround; nobody blockedCosmetic or local inconvenience; obvious workaround; nobody blockedkind:choreUpkeep with no intended behavior change: deps, CI, cleanupUpkeep with no intended behavior change: deps, CI, cleanuplikelihood:lowNeeds unusual preconditions; not expected within the horizon (30 days)Needs unusual preconditions; not expected within the horizon (30 days)
Context
Found during a security review of
internal/env's file-containment control, runahead of extending it to a second file format (#498). Neither finding is
reachable as a vulnerability today — they are both about what happens when a new
caller arrives — but the extension in #498 is exactly that new caller, so they
are worth closing before something depends on them.
1.
runAndWrappersists child stderr to the log file atErrorlevelinternal/exec/exec.go:147and:218:Errorlevel survives any configured log level, andconfig.SetupLogger(
internal/config/config.go:929) can point the handler at a file on disk. Thesame stderr also rides
CommandError.Error()into fang's rendered output.Today's
Runnercallers are tmux, sesh, and brew, so nothing sensitive reachesit. The hazard is structural: any future caller whose child prints sensitive
material on stderr turns a debug aid into a durable on-disk record of it, with
nothing at the call site to suggest that happened.
exec.SensitiveRunneralready exists for precisely this and logs only metadata.The gap is that
Runneris the obvious, ergonomic choice and carries no warningat the point of use.
Suggested fix: document the sink on the
Runnerinterface itself — one linenaming
SensitiveRunneras the seam for a command whose output may carry asecret. A doc comment is the right altitude here; narrowing
Runnerwould churnevery existing caller for no present gain.
2. Unbounded child output capture
internal/exec/exec.go:141captures stderr into astrings.Builderwith noceiling, and stdout via
cmd.Output(), likewise uncapped. A child that emitsunbounded output grows the parent's heap until it is killed.
This is measurable rather than hypothetical:
sopsre-invokes its editorforever when the editor hands it invalid YAML, and a bounded probe on sops
3.13.3 produced 8.4 MB of stderr across 36,851 editor invocations in about three
minutes before it was killed — still going. A
Runnercaller driving that wouldaccumulate all of it.
SensitiveRunneralready enforcesMaxOutputBytes(64 KiB) and kills onoverflow, so the pattern to copy is in the tree.
Suggested fix: wrap both streams in a capped writer in
runAndWrap,reporting truncation rather than silently discarding.
SensitiveRunner'sBoundedOutputcarries a completeness flag for the same reason — a caller thatparses output must be able to tell a whole stream from a prefix.
Out of scope here
Two sibling findings from the same review are filed separately because they sit
in
internal/envrather thaninternal/exec.