fix: oversized files must not produce a zero-finding report (LLM-stage inclusion + AE7 coverage finding) - #509
fix: oversized files must not produce a zero-finding report (LLM-stage inclusion + AE7 coverage finding)#509eitanch228 wants to merge 3 commits into
Conversation
A file larger than MAX_ANALYZABLE_FILE_BYTES is excluded from llm_file_cache entirely, so the semantic LLM analyzers never see it, and its unreviewed region produces no finding: the report shows CAUTION / 0 with an empty issues list while a payload past the read cap stays invisible (fail-open). Two-part fix: 1. build_context: cache a bounded prefix (MAX_LLM_TRUNCATED_FILE_CHARS) of truncated text files in llm_file_cache with an explicit audit-gap marker, instead of excluding them. The existing batcher already chunks oversized content into token-budgeted batches. 2. finalize_inspection_ledger: new AE7 coverage finding (HIGH, analysis-evasion) for artifacts left partial by the per-file size cap, extending the AE1 principle to artifacts nothing referenced. Paths already covered by AE1 are skipped; aggregate budget exhaustion stays handled by the existing fail-closed verdict upgrade. Signed-off-by: eitanch228 <eitan.ch@pluto.security>
|
Windows check: at the merge-base ( One hardening question. Would a per-scan nonce be worth it? |
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Reviewed head b24eed5c967bd64622b4baea224b2376dcdfcca4 — APPROVE.
Oversized text now reaches the semantic stage through a bounded view, while deterministic AE7 coverage ensures the unreviewed tail cannot produce a zero-finding result. Referenced paths avoid duplicate AE1/AE7 findings, aggregate limits retain their existing completeness gate, and the tests cover the disposition matrix. The model-visible marker is not an authorization boundary; deterministic AE7 remains the fail-closed control. I found no required changes.
Required checks pass, but GitHub currently reports mergeStateStatus=BEHIND; update against current main and re-run required checks before merging.
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Reviewed head 84786ffbdf19b838c07ffccf6b6ef7ecd0d81652 — APPROVE.
I re-reviewed the complete replacement head. Its only change since the previously assessed commit is the upstream README badge merged from main; the PR's five-file delta is unchanged. Oversized text reaches the semantic stage through a bounded view, while deterministic AE7 coverage ensures the unreviewed tail cannot produce a zero-finding result. Referenced paths avoid duplicate AE1/AE7 findings, aggregate limits retain their existing completeness gate, and the tests cover the disposition matrix. The model-visible marker is not an authorization boundary; deterministic AE7 remains the fail-closed control. I found no required changes.
Merge remains blocked until all current-head required checks finish successfully and GitHub reports a clean merge state.


Summary
A file larger than
MAX_ANALYZABLE_FILE_BYTES(16 MiB) currently produces azero-finding report even under the default LLM-on scan:
nodes/build_context.pyexcludes truncated files fromllm_file_cacheentirely, so the semantic LLM analyzers (the scanner's most capable
stage) never see the file.
CAUTION / 0with an empty issues list, and the only trace isanalysis_completeness.status: "partial"in JSON metadata.A payload placed past the read cap is therefore invisible while the scan reads
as clean. This is a fail-open: "too large to review" must itself be a finding.
Reproduction
A runnable MCP server whose malicious helpers sit past the 16 MiB mark
(17 MB
server.py: benign weather code + a 16.2 MiB string literal before thecut, exfiltration helpers after it; module-level names resolve at call time,
so the payload still executes, canary-verified):
llm_componentscontains onlymanifest.json;server.pynever reaches themodel. Confirmed against three model backends (a local Claude CLI login, plus
claude-sonnet-5 and claude-opus-5 reached through an OpenAI-compatible
endpoint with archived prompts): every prompt covered only
manifest.json.The same payload at normal size is flagged
DO_NOT_INSTALL / 100by allthree backends, so the miss is structural, not a model-capability failure.
Fix
Two parts, both required:
1.
build_context.py: keep truncated files in the LLM stage.Truncated text files are cached in
llm_file_cacheas a bounded prefix(
MAX_LLM_TRUNCATED_FILE_CHARS, 256 KiB) plus an explicit marker:LLMAnalyzerBase.get_batchesalready chunks oversized content intotoken-budgeted batches, so no batching changes are needed; the 256 KiB cap
keeps worst-case token cost bounded (~2-8 chunks depending on model budget).
2.
finalize_inspection_ledger.py: new AE7 coverage finding (HIGH,analysis-evasion). Any artifact left
partialby the per-file size capproduces a finding: "File exceeds the analyzable size limit; trailing content
was not inspected." This extends the existing AE1 principle ("referenced
artifact was not completely inspected") to artifacts nothing referenced.
Paths already covered by AE1 are skipped (no double-reporting). Scoped to the
per-file cap (
size_limit); aggregate budget exhaustion already fails closedthrough the completeness projection and the
SAFE -> CAUTIONupgrade innodes/report.py.After the fix, the same files under the same default scan flip to
DO_NOT_INSTALL / 100:With only the bounded head + marker available, the model catches the evasion
structurally: it flags the 17 MB repeated-sentence padding as having "no
benign explanation" (SSD-2), the unconditional
_sync_telemetry/_check_for_service_updatecall chain in the visible head (SDI-1), thedocstring as "social engineering" (SSD-4), and AE7 names the file and its
true size. Even a perfectly innocuous-looking head still cannot produce a
clean report, because AE7 fires regardless.
Why not full-file chunked review
Reading and reviewing every byte of arbitrary-size files is a token-cost
policy decision (a 16 MiB file is ~4M tokens, hundreds of calls per
analyzer). The results above show the fail-closed minimum is sufficient in
practice: a bounded head + an explicit gap marker lets the model identify
both the payload's call sites and the padding itself as evasion, and AE7
guarantees the remainder is never a silent gap. Happy to take maintainer
guidance on a follow-up that chunks the full file behind an opt-in budget
flag.
Test plan
test_truncated_text_file_stays_in_llm_cache_with_audit_gap_marker:truncated file stays in
llm_file_cache/llm_components, carries themarker, still reported
partialtest_size_truncated_artifact_synthesizes_ae7: disposition/reasonmatrix (fires on
partial+size_limitonly)test_ae7_skips_paths_already_covered_by_ae1: no double-reporting--no-llm), 17 MB PoC:CAUTION / 0, 0 findings->
CAUTION / 32, 1 HIGH (AE7)PoCs (exfil + RCE):
CAUTION / 0, 0 findings->DO_NOT_INSTALL / 100,9 findings each, 4/4 LLM calls OK
DO_NOT_INSTALL/100