Skip to content

🔒 fix(provider): bound exec stdout, set WaitDelay, capture stderr - #81

Merged
konih merged 1 commit into
mainfrom
lane/aud2-s01-integrate
Aug 18, 2026
Merged

🔒 fix(provider): bound exec stdout, set WaitDelay, capture stderr#81
konih merged 1 commit into
mainfrom
lane/aud2-s01-integrate

Conversation

@konih

@konih konih commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

AUD2-S01 — exec transport trio (REL-01 / REL-02 / REL-07)

Integration of lane/aud2-s01 (review head c7db55e, verdict APPROVE, no P0/P1, no merge conditions), rebased onto origin/main with the CHANGELOG regenerated.

What changes (internal/provider/transport.go)

  • REL-01 — exec stdout is bounded through the existing readBounded/MaxResponseBytes path via a new boundedCapture writer. Over-limit is an error with no bytes, identical to CallHTTP's body semantics, so nothing can parse a truncated document and a runaway provider cannot grow the runner's heap without limit.
  • REL-02cmd.WaitDelay = opts.Timeout, so a provider that forks a grandchild inheriting stdout cannot hold cmd.Run open past the operator's deadline. The resulting exec.ErrWaitDelay classifies as unavailable like any other call error.
  • REL-07 — stderr is captured into its own 4 KiB bounded buffer and folded into the returned error (sentinel preserved for errors.Is/As). It is never concatenated into the returned fact bytes.

CallExec now returns nil bytes on error instead of the partial buffer; ResolveFactsChecked already discards raw on the error branch, so this is strictly more fail-closed.

Verification on the rebased head

  • task check — exit 0
  • go test -race ./internal/provider/... — pass
  • TestExecWaitDelayBoundsOrphanedStdout (wall-clock assertion) — -race -count=5, 5/5 green, ~4.5 s/iteration
  • MaxResponseBytes still declared exactly once in internal/provider, unchanged at 8 << 20
  • Diff is exactly the 3 source files plus the regenerated CHANGELOG.md

Branch note: pushed as lane/aud2-s01-integrate because the rebase + CHANGELOG amend rewrites SHAs already published on lane/aud2-s01, and force-push is not authorized (AGENTS.md hard rule 2). lane/aud2-s01 stays at c7db55e as the review record.

Last of AUD2's four fix lanes.

CallExec had three containment gaps that CallHTTP, twenty lines above in the
same file, does not (audit 2026-08-18; REL-01 is byte-identical across three
audits because no test measured it):

- REL-01 (REQ-AUD2-S01-01/02/03): child stdout went into an unlimited
  bytes.Buffer. It now flows through a boundedCapture that retains at most
  MaxResponseBytes+1 and applies the SAME readBounded verdict as the HTTP body
  read — over-limit is an error with nil bytes, at-limit is legitimate traffic.
  No second limit constant: MaxResponseBytes stays the single response bound.
- REL-02 (REQ-AUD2-S01-04/05): cmd.WaitDelay = opts.Timeout. Killing the child
  does not close a stdout pipe its background grandchildren still hold, so
  cmd.Run blocked past the operator's deadline; the call now returns in ~2x
  Timeout and the resulting error classifies as unavailable.
- REL-07 (REQ-AUD2-S01-06): stderr is captured into its OWN bounded buffer and
  a truncated excerpt is folded into the returned error with %w, so a failing
  provider explains itself instead of yielding a bare "exit status 1". Per
  judgment call (c) it is never merged into the fact bytes.

Behaviour change flagged by judgment call (a): a failed CallExec now returns
nil bytes alongside the error, matching CallHTTP. Every caller was checked —
cmd/assent/provider_host.go:206 wraps it as a CallFunc and ResolveFacts ignores
raw when err != nil (REQ-AUD2-S01-07: any call error -> unavailable).

Mutation-proved (all observed red, then reverted): removing the readBounded
verdict; removing the retention cap while keeping the verdict (black-box tests
stay GREEN — hence the white-box transport_internal_test.go); removing
cmd.WaitDelay (test blocks the full 60s); removing the cmd.Stderr wiring; and
merging stderr into stdout.
@konih
konih merged commit 07b44db into main Aug 18, 2026
5 checks passed
@konih
konih deleted the lane/aud2-s01-integrate branch August 18, 2026 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant