Skip to content

fix(vuln-scanner): bound trufflehog git-history scan, no more phantom-success runs - #1030

Merged
aaronjmars merged 6 commits into
aeonfun:mainfrom
Svector-anu:fix/trufflehog-git-history-timeout
Sep 5, 2026
Merged

fix(vuln-scanner): bound trufflehog git-history scan, no more phantom-success runs#1030
aaronjmars merged 6 commits into
aeonfun:mainfrom
Svector-anu:fix/trufflehog-git-history-timeout

Conversation

@Svector-anu

Copy link
Copy Markdown
Contributor

Live-observed bug, not theoretical: on paypal/paypal-checkout-components (200 commits, ~369MB packed history), trufflehog git file://. --only-verified had no time bound and ate the entire 30-minute turn budget by itself. The agent wrote a "still running, will resume automatically" placeholder as its final output instead of a report — except a workflow_dispatch run has no resume mechanism. The workflow step itself reported success anyway (nothing errored — the agent just ran out of budget), and no ledger entry was ever written. Silent, false success. Had to be caught by hand and re-dispatched.

What changed

  1. Wrapped the git-history scan in timeout 300. A timeout now degrades to an honest, recorded fail — same as an install failure — instead of hanging until the whole run's turn budget is gone.
  2. sources.txt now tracks trufflehog-git separately from the filesystem pass. They can genuinely diverge (filesystem clean and fast, git-history timing out on a large packed repo, or vice versa) — collapsing both into one trufflehog= line hid whichever one actually failed.
  3. New explicit rule in A7 ("there is no resume"). If the agent is running low on turns, it should finish the report now with whatever scanners actually completed and mark the rest fail — a workflow_dispatch run is one shot with no continuation, and a "will resume" placeholder is not truthful about what a single run can do.

Verification

Already shipped and live-tested on the fork (Svector-anu/svectors-lab, commit 5927884):

  • Confirmed working immediately after: the next dispatch (a smaller repo) completed both trufflehog passes cleanly within budget — no phantom completion.
  • Confirmed the timeout path itself: a subsequent dispatch on the same large-history repo produced a clean, honest 30-minute outer timeout instead of a silent phantom success, validating that the bound is actually needed (without it, one scanner step alone consumed the entire run budget).

Scoped to exactly this fix — no other changes.

…-success runs

Live-observed on paypal/paypal-checkout-components (200 commits, ~369MB
packed history): the unbounded trufflehog git file://. --only-verified
call ate the whole turn budget, and the agent wrote a "still running,
will resume automatically" placeholder as its final output instead of
a report. The workflow step itself reported success anyway (nothing in
the step errored - the agent just ran out of budget), and no ledger
entry was ever written. Had to be caught by hand and re-dispatched.

Three changes:
1. Wrap the git-history trufflehog call in `timeout 300`; a timeout
   now degrades to an honest, recorded fail (same as an install
   failure) instead of hanging until the agent's turn budget runs out.
2. sources.txt now records trufflehog-git separately from the
   filesystem pass - they can genuinely diverge (one clean and fast,
   the other timing out on a large packed repo), and collapsing both
   into one trufflehog= line hid whichever one actually failed.
3. New explicit rule in A7: there is no resume on a workflow_dispatch
   run. If turns are running low, finish the report now with whatever
   completed and mark the rest fail - writing a promise to pick back
   up later is not true of this run path and produces exactly the
   silent-success-with-no-output failure mode this fixes.

Already shipped and verified on the fork (Svector-anu/svectors-lab,
commit 5927884) - confirmed working on a live dispatch immediately
after: the next scan on a smaller repo completed both trufflehog
passes cleanly within budget, and a subsequent dispatch on the same
large-history repo produced a clean, honest timeout instead of a
silent phantom success.
@aaronjmars

Copy link
Copy Markdown
Collaborator

Thanks for this - the diagnosis is right (an unbounded trufflehog git file://. over a large packed history can burn the whole turn budget, after which the harness records a phantom success with no report written), and splitting trufflehog-git= out from the filesystem trufflehog= line in sources.txt is a real observability win.

Two things block merge as written:

  1. timeout breaks the tool allowlist, so this silently disables the git-history scan in the fleet's real run path instead of bounding it. The skill runs non-interactively via claude -p under scripts/skill_mode.sh, whose allowlist entry is Bash(...trufflehog:*). Claude Code's Bash matcher is literal-head-token: Bash(trufflehog:*) matches a command whose first token is trufflehog. After this change the command's first token is timeout, so it no longer matches and is denied ("requires approval") with no human to approve. Net effect: the git-history pass produces no output and gets recorded as fail/skip, i.e. the secret scan is removed rather than bounded - the opposite of the goal. (Your own SKILL.md flags this exact matching pitfall in the Arm A / Execute section re: absolute-path invocations.) The fix needs Bash(timeout:*) added to the WRITE_TOOLS allowlist in scripts/skill_mode.sh as part of this PR. Worth also handling the gtimeout fallback on macOS, and consider timeout -k so an unresponsive process is hard-killed.

  2. eyebrowlock.json is stale, so verify (ci-skill-integrity) is red. The recorded hash for skills/vuln-scanner/SKILL.md no longer matches the edited file. Please regenerate and commit the lockfile: eyebrow scan --path . --lockfile eyebrowlock.json.

Minor: the filesystem trufflehog call keeps || true while the git call now relies on the block not running under set -e - fine today, just slightly divergent. And when trufflehog is absent entirely, the git pass records fail rather than skipped (matches the existing filesystem line, so consistent).

Once Bash(timeout:*) is in the allowlist and the lockfile is regenerated, this is good to go. Verified against harness-adapter/run-harness and scripts/skill_mode.sh on main.

@Svector-anu

Copy link
Copy Markdown
Contributor Author

thanks, both blockers are addressed in 08c6a4a.

  • added Bash(timeout:*) and Bash(gtimeout:*) to the write tier only. these are general command wrappers, so they remain excluded from read-only.
  • regenerated with the checksummed eyebrow v0.4.2 release used by ci, retaining only the vuln-scanner artifact update. unrelated lock entries are unchanged.
  • extended the existing test_skill_mode.sh suite, which ci-tests.yml already invokes: 24 assertions pass. removing only the new allowlist line makes the suite exit 1 with write tier missing timeout wrapper and write tier missing gtimeout wrapper; restoring it returns all pass.
  • bash -n and git diff --check pass; eyebrow verify --ci exits 0. four pre-existing advisory content drifts remain untouched.

the earlier pr body's 30-minute outer timeout is not proof that the five-minute inner timeout executed. i have not independently reproduced that live scan and do not claim it here. this update fixes the permission and integrity blockers; the optional macos command selection and hard-kill grace are not implemented by merely allowing gtimeout.

fresh ci and aeon's scoped review are being checked separately before claiming this is ready. no merge performed.

# repo, or vice versa) and collapsing both into one trufflehog= line hides
# whichever one actually failed.
if [ "${TRUFFLEHOG_GIT_RC:-1}" = 124 ]; then
echo "trufflehog-git=timeout" >> /tmp/vuln-scan/sources.txt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ISSUE] trufflehog-git=timeout is not represented in any of the prescribed A7/A8/log status formats (which still expose only trufflehog=<ok|fail>), and timeout is outside their declared ok|fail vocabulary — why it matters: a clean filesystem pass can still be reported as trufflehog=ok while the timed-out history pass disappears from the durable report and operator notification, preserving the misleading-success failure this change is intended to fix.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: discussion-needed
The git-history timeout is bounded, but its failure state is not wired through the skill’s user-visible output contracts.

Findings (mirrored as inline comments):

  • [ISSUE] skills/vuln-scanner/SKILL.md:200 — trufflehog-git=timeout is omitted from the prescribed report, notification, and log formats, so a clean filesystem scan can mask a failed history scan in durable output.

…fy/log location

Review feedback on this PR (aeonfun#1030): trufflehog-git=timeout
was only being written to sources.txt - the three durable, human-facing
outputs (A7's local report, A8's notify message, and the Mode:scan log
line) still only templated a single trufflehog=<ok|fail> field. A
timed-out history scan could sit right next to a clean filesystem
scan's "ok" and never surface in anything an operator actually reads.
Exactly the silent-masking this whole fix exists to prevent, just one
layer further out than the first pass caught.

Added trufflehog-git as its own field in all three locations, with an
explicit note at each: folding it back into trufflehog=ok is not
acceptable.
@Svector-anu

Copy link
Copy Markdown
Contributor Author

Good catch — fixed. trufflehog-git was only reaching sources.txt; the three human-facing outputs (A7 report, A8 notify, Mode:scan log line) still only templated a single trufflehog=<ok|fail> field, so a timed-out history scan could sit invisibly next to a clean filesystem-scan ok. Added it as its own field in all three, with an explicit note against folding it back in. Same commit range on this branch.

Svector-anu added a commit to Svector-anu/svectors-lab that referenced this pull request Sep 5, 2026
…fy/log location

Same gap upstream review caught on aeonfun#1030, applies here too
since this fork's live campaign runs off this file: trufflehog-git was
only reaching sources.txt, not the three human-facing outputs (A7
report, A8 notify, Mode:scan log line), which still templated a single
trufflehog=<ok|fail>. A timed-out history scan could sit invisibly
next to a clean filesystem-scan's "ok".
@Svector-anu

Copy link
Copy Markdown
Contributor Author

aeon's review caught one additional gap on 08c6a4a: the git-history status was absent from the report/notification/log contracts. review receipt: #1030 (review) (run 33976148246, codex native-oauth).

fixed on ea37f77, preserving the concurrent fix in 09e89ae rather than overwriting it. every output now preserves trufflehog-git separately; zero findings with a failed/timed-out pass is a limited audit, not clean coverage. also removed the incorrect claim that every scanner step was already bounded.

added three contract tests and registered them explicitly in ci-tests.yml. all three failed against the old templates, pass with the fix, and removing the log field again fails the corresponding assertion. these are instruction-contract checks, not a claim that a live scanner followed the instructions.

all five checks pass on the updated head, including the actual test run https://github.com/aeonfun/aeon/actions/runs/33976617402 and integrity run https://github.com/aeonfun/aeon/actions/runs/33976617454. aeon re-review is running at https://github.com/Svector-anu/svectors-lab/actions/runs/33976684946. no merge performed.

Comment thread skills/vuln-scanner/SKILL.md Outdated
if [ "${TRUFFLEHOG_GIT_RC:-1}" = 124 ]; then
echo "trufflehog-git=timeout" >> /tmp/vuln-scan/sources.txt
else
echo "trufflehog-git=$([ -s /tmp/vuln-scan/trufflehog-git.json ] && echo ok || echo fail)" >> /tmp/vuln-scan/sources.txt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ISSUE] Determine success from the TruffleHog exit code, not whether its JSON output is non-empty. A successful clean trufflehog git --json scan emits no finding records, so this branch records trufflehog-git=fail and forces a “limited audit” for the normal zero-secret case.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: discussion-needed
The prior reporting gap is fixed, but clean history scans are now classified as failures.

Findings (mirrored as inline comments):

  • [ISSUE] skills/vuln-scanner/SKILL.md:202 — successful clean trufflehog git --json scans emit no finding records, so testing for a non-empty output file records the normal zero-secret case as fail and incorrectly forces a limited-audit verdict.

@Svector-anu

Copy link
Copy Markdown
Contributor Author

aeon's second review caught a real zero-finding classification bug; fixed in 8b4c982.

real reproduction with installed trufflehog 3.97.1 on a one-commit, secret-free local fixture:

trufflehog --no-update git file:///private/tmp/pr1030-clean-fixture --only-verified --json
trufflehog_exit=0
json_bytes=0

the first attempt failed in the tool's auto-updater (cannot move binary); the documented --no-update option let the actual scan complete. no third-party target or live credentials were tested.

both trufflehog passes now classify completion by exit status rather than JSON size. history exit 124 remains timeout; missing/nonzero execution remains fail, including partial output followed by an error. exit capture also survives set -e.

five tests now cover the reporting contract and execute the actual history/filesystem status snippets. mutating the history success branch reproduced trufflehog-git=fail != trufflehog-git=ok; restored, all five pass. integrity verify exits 0. fresh CI/re-review still need to finish for this SHA; no merge.

Svector-anu added a commit to Svector-anu/svectors-lab that referenced this pull request Sep 5, 2026
The trufflehog-git timeout wrapper (skills/vuln-scanner/SKILL.md,
`timeout 300 trufflehog git ...`) invokes a command whose bare name is
`timeout`, not `trufflehog` - the existing Bash(trufflehog:*) grant
doesn't cover it. Without this, the wrapped call is denied at the
sandbox layer ("requires approval"), same failure mode already
documented for the scanner binaries above it.

Same catch and fix upstream review made on aeonfun#1030
(commit 08c6a4a) - applying it here too since this fork runs the live
campaign that actually depends on the timeout bound working.

General command runners, so write tier only - never read-only.

@Svector-anu Svector-anu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: approve-ready — no blockers.

@Svector-anu

Copy link
Copy Markdown
Contributor Author

final verification on 8b4c982: all five ci checks pass, and aeon re-review returned approve-ready with zero blockers, bound to that exact sha. review receipt: #1030 (review) ; completed codex native-oauth run: https://github.com/Svector-anu/svectors-lab/actions/runs/33977036237 . no merge performed.

Svector-anu added a commit to Svector-anu/svectors-lab that referenced this pull request Sep 5, 2026
Ran eyebrow scan (v0.4.2, matching CI's pinned version) after the
trufflehog-timeout SKILL.md edits and the skill_mode.sh timeout/gtimeout
grant landed unaccounted-for in the lockfile - same integrity gap the
upstream review caught on aeonfun#1030 (commit 08c6a4a), applying
the fix here too since this fork runs the live campaign.

vuln-scanner hash now sha256:4eafd7da... - verified independently via
a plain shasum against the live file content before running eyebrow,
matches exactly. pr-review's hash also moved - unrelated drift from
other fleet activity on main since the last lockfile write, picked up
in the same regeneration rather than left stale.

The three flagged findings on vuln-scanner (RCE-PIPE-EXEC / EXEC-PRIMITIVE)
are pre-existing pattern-matches against the skill's own documentation
of legitimate tool-staging commands, not real issues - same class the
upstream commit left untouched. Not addressed here.
@aaronjmars

Copy link
Copy Markdown
Collaborator

Reviewed the full thread and the final diff on 8b4c982. Both original blockers (the timeout allowlist head-token issue and the stale eyebrowlock.json) are resolved and CI-verified, and the aeon re-review is approve-ready with zero blockers. The exit-code reclassification is a nice bonus: it also fixes a real latent bug where a genuinely clean scan (trufflehog exit 0, empty JSON) was previously misreported as fail. Merging.

Non-blocking notes for a future pass, no action needed here:

  • Bash(gtimeout:*) is in the write-tier allowlist but SKILL.md only ever invokes bare timeout, so the gtimeout entry has no matching call site. Vestigial on the ubuntu Actions run path; it would only matter on a macOS runner without coreutils, where bare timeout is absent and there is no gtimeout fallthrough.
  • No timeout -k hard-kill grace, so a scanner that ignores SIGTERM would not be force-killed. Low risk since trufflehog honors SIGTERM (you already flagged this as deferred).
  • The filesystem trufflehog pass is now exit-code based while semgrep still keys off file size ([ -s ... ]). Justified because semgrep emits a non-empty JSON wrapper on a clean run and trufflehog emits nothing, but the two scanners now use divergent success primitives.

Thanks for the careful iteration.

@aaronjmars
aaronjmars merged commit d9ac832 into aeonfun:main Sep 5, 2026
5 checks passed
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.

2 participants