Skip to content

fix(security-audit): pass the advisory report through a file - #18

Open
arcuru-bot wants to merge 1 commit into
arcuru:mainfrom
arcuru-bot:fix/security-audit-report-file
Open

arcuru-bot wants to merge 1 commit into
arcuru:mainfrom
arcuru-bot:fix/security-audit-report-file

Conversation

@arcuru-bot

Copy link
Copy Markdown
Contributor

The bug

Deps: Security Audit on eidetica main went red on 2026-08-19 (run 32222183430). Two things are in that run: a genuine advisory finding, and this defect. The step that is supposed to open or update the tracking issue never started:

ISSUE_NUMBER: 39
##[error]An error occurred trying to start process '/usr/bin/bash' with working directory
'/home/runner/work/eidetica/eidetica'. Argument list too long

The reporting step received the whole report as the AUDIT_OUTPUT environment variable. Because the audit step redirected stderr as well (2>&1), that capture also swallowed the entire Nix dev-shell build log. Measured from the failing run: 242,259 bytes captured, of which the cargo-deny report is about 8 KB — the rest is copying path ..., building ..., and a run of 502 retries against a substituter.

A single environment string over MAX_ARG_STRLEN (128 KiB) makes execve fail with E2BIG, so the runner could not start bash at all. Reproduced directly:

$ AUDIT_OUTPUT="$(cat report-from-the-failing-run.txt)" bash -c 'echo hi'
argument list too long: bash

So the reporting path breaks in exactly the case it exists to handle. A clean audit never exercises it, which is why this survived until a real advisory landed.

The fix

  • Build the dev shell in its own step (nix develop --command true), so the Nix build log is no longer part of what the audit step captures. On a warm store the second nix develop is quiet, and the captured report is just cargo-deny's output.
  • Write the report to a file under RUNNER_TEMP and pass it with gh issue edit --body-file / gh issue create --body-file. No copy of the report goes through argv or the environment, so the size of the report can no longer decide whether the step starts. This also removes the GITHUB_OUTPUT heredoc, whose AUDIT_EOF delimiter was injectable from report content.
  • Bound the body under the API cap. A GitHub issue body is capped at 65536 characters, so a report that still exceeds it keeps its tail — where cargo-deny prints the advisories and the advisories FAILED verdict — with a note linking the run log. This is a safety valve, not the normal path: with the dev-shell log gone the real report is ~8 KB and is posted whole.

Verification

Extracted the reporting step and ran it against the actual 242 KB capture from the failing run, with a stubbed gh:

STUB gh issue edit 39 --body-file /tmp/.../security-advisory-issue.md
  body-file size: 50416 bytes
script exit=0

The generated body ends with the full smallstr / yrs advisory block and advisories FAILED, i.e. the payload survives even in the pathological case that used to kill the step.

actionlint (with shellcheck) over all of .github/workflows/*.yml: exit 0.

Blast radius

⚠️ This PR modifies a GitHub Actions workflow: .github/workflows/security-audit.yml. It is a reusable workflow, so the change goes live for callers only when they bump their pinned SHA. Current consumers include arcuru/eidetica, arcuru/chaz, arcuru/pokem and arcuru/headjack; each needs a separate pin bump to pick this up.

Not in scope

  • The smallstr / yrs advisory itself.
  • flake-update.yml and cargo-update.yml capture nix ... 2>&1 into a step output the same way and interpolate it into a PR body. They have not hit the limit, but they are the same shape; tracked separately.

When cargo-deny reports an advisory the issue-reporting step died before
it started, with:

  An error occurred trying to start process '/usr/bin/bash' ...
  Argument list too long

The report was captured into a step output and re-exported as the
AUDIT_OUTPUT environment variable of the reporting step. Because the
audit step redirected stderr as well, that capture also swallowed the
whole Nix dev-shell build log — 242 KB on a cold cache, against roughly
8 KB of actual cargo-deny report. A single environment string over
128 KiB exceeds the per-string execve limit, so the runner could not
exec bash at all, and the step that exists to file the advisory failed
in exactly the case it exists for. A clean audit never reached it.

Two changes:

- Build the dev shell in its own step, so the Nix build log is no longer
  part of what gets captured.
- Write the report to a file under RUNNER_TEMP and hand it to
  `gh issue edit`/`gh issue create` with --body-file, so no copy of it
  passes through argv or the environment.

A GitHub issue body is still capped at 65536 characters, so an
unexpectedly large report keeps its tail — where cargo-deny prints the
advisories and its verdict — and links the run log for the rest, rather
than failing the API call.
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