Skip to content

threat-detection: GITHUB_STEP_SUMMARY is unwritable in the AWF sandbox, aborting the v0.4.0 detector with exit 2 #50659

Description

@davidslater

Created by GitHub Ace · View Session

Summary

With threat-detect v0.4.0, the external detection step aborts with exit 2 before the engine ever runs. Upstream #747 added an artifact-inventory table written to GITHUB_STEP_SUMMARY, and per spec TD-20c a failed write is a config error. Inside the AWF sandbox that write always fails, so:

stepsummary.WriteArtifactInventory → open failure
main.go → reason=config_error → exit 2
(no detection_result.json produced)
conclude → ERR_SYSTEM: Detection result file not found

Engine-agnostic, which is why all three upstream standalone smokes fail identically. Upstream has committed a resiliency fix on their side, but the wiring gap is ours and the inventory table is silently lost until it is fixed here.

This is the blocker for #50644 (the v0.4.0 pin bump) in practice.

Root cause

The detection step inherits GITHUB_STEP_SUMMARY=/home/runner/work/_temp/_runner_file_commands/step_summary_<uuid>. From sandbox/firewall/audit/docker-compose.redacted.yml on a real failing run:

environment:
  GITHUB_STEP_SUMMARY: /home/runner/work/_temp/_runner_file_commands/step_summary_7e854bcb-…
volumes:
  - /tmp:/tmp:rw
  - /home/runner/work/<repo>/<repo>:…:rw
  - /tmp/awf-…-chroot-home:/host/home/runner:rw   # empty home overlay

Two facts:

  1. /home/runner/work/_temp is not mounted, and /home/runner is overlaid by an empty home volume. The runner's file-command path resolves into an empty dir inside the chroot → ENOENT.

    This is deliberate AWF isolation and must stay that way. If _runner_file_commands were mounted rw, a sandboxed agent could write GITHUB_ENV / GITHUB_OUTPUT / GITHUB_STEP_SUMMARY and inject directly into the host workflow. Mounting it would be a privilege-escalation hole. Do not "fix" this by adding a mount.

  2. gh-aw's step-level GITHUB_STEP_SUMMARY override never reaches the process. The runner re-injects the real file-command variable for run: steps after step-level env: is applied, and AWF forwards the actual process env via --env-all. So the container sees the runner path despite the lock file setting the override.

Confirmed in this repo

The compiled Execute threat detection with AWF step in .github/workflows/ab-testing-advisor.lock.yml does carry the override:

GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md

(inherited from the copilot engine env block via extractStepEnvLines), and the AWF invocation uses --env-all with --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw. The override is present and ineffective, exactly as described.

A second, independent gap: even if the override did work, /tmp/gh-aw/agent-step-summary.md is the agent's summary path. The detection job neither touches it nor appends it — I checked the compiled detection: job and there is no Append agent step summary step and no touch. So the detector's inventory table would be written into a file nobody ever reads. Both halves need fixing.

Proposed fix

Mirror what the agent job already does (touch + append_agent_step_summary.sh), but with a detection-specific path inside the already-rw-mounted detection dir:

  1. Add a ThreatDetectionStepSummaryPath constant = /tmp/gh-aw/threat-detection/step-summary.md.
  2. touch it in the host-side PathSetup before the AWF invocation (same pattern as claude_engine.go:399).
  3. Pass --step-summary /tmp/gh-aw/threat-detection/step-summary.md explicitly on the threat-detect command line, rather than relying on env. A flag is the correct mechanism here — the whole problem is that env for this variable is not controllable from a run: step, so any env-based fix is fragile by construction.
  4. Add a post-execution step that appends the file to the real $GITHUB_STEP_SUMMARY on the host, after redaction, no-op when empty.
  5. Add the path to buildUploadDetectionArtifactStep so it survives even if the append step is skipped.

The --step-summary flag exists as of upstream #739 and is supported by both threat-detect and threat-detect conclude.

Alternative considered and rejected: explicitly unsetting GITHUB_STEP_SUMMARY for the sandboxed step. That stops the exit-2 abort but permanently discards the inventory table and the detector's real prompt/verdict blocks, which is most of the value of #50653.

Note on conclude

threat-detect conclude runs on the host, not in the sandbox, so its --step-summary default ($GITHUB_STEP_SUMMARY) works correctly and needs no change. Worth an explicit test so this asymmetry is not "cleaned up" later by someone assuming both need the flag.

Related finding — the agent job's redirect is likely also ineffective

The same mechanism applies to the agent job. Every engine sets GITHUB_STEP_SUMMARY: AgentStepSummaryPath in its step env (claude_engine.go:480, copilot_engine_execution.go:532, codex_engine.go:366, gemini_engine.go:305, pi_engine.go:419) and append_agent_step_summary.sh appends /tmp/gh-aw/agent-step-summary.md afterwards.

If the runner re-injects the file-command variable there too, then agents have been writing to the real summary path — i.e. into the empty chroot overlay, failing silently — and agent-step-summary.md has been empty all along. Nobody noticed because, unlike the detector, no agent treats the write failure as an error.

This should be verified as part of this issue: check whether /tmp/gh-aw/agent-step-summary.md is non-empty on any recent agent run that produced step-summary output. If it is consistently empty, the same flag-or-wrapper fix is needed on the agent path, and it is worth splitting into its own issue since it touches all five engines.

Acceptance criteria

  • External detection runs on v0.4.0 no longer exit 2 on the step-summary write.
  • The detector's artifact-inventory table, rendered prompt, and verdict block appear in the job step summary.
  • --step-summary passed explicitly to a path inside the rw-mounted ThreatDetectionDir; no new AWF mount added, and _runner_file_commands remains unmounted.
  • Detection summary file touched before execution and appended after, no-op when empty.
  • Path added to the detection artifact upload.
  • Test asserting conclude is not given the sandbox path (it runs on the host).
  • Verified whether the agent job's AgentStepSummaryPath redirect is also ineffective; follow-up issue filed if so.
  • make recompile.

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions