Skip to content

fix(exaforce): let LLM verdict override static severity floor - #15

Open
will-exaforce wants to merge 1 commit into
mainfrom
wbeasley/meta-floor-semantic
Open

fix(exaforce): let LLM verdict override static severity floor#15
will-exaforce wants to merge 1 commit into
mainfrom
wbeasley/meta-floor-semantic

Conversation

@will-exaforce

Copy link
Copy Markdown

Summary

  • The meta-analyzer LLM now decides the fate of every static finding, including CRITICAL/HIGH — upstream's severity floor no longer keeps regex hits the LLM rejected, which was surfacing noise (PE3, P1, YR4, TP4, E2, RA1, …) on benign skills.
  • The floor is kept for LLM-backed findings only: the three semantic analyzers (SQP-*, SDI-*, SSD-*) and TP4, which mcp_tool_poisoning derives from a chat_completion reply. Upstream's floor was mostly shielding those first-pass LLM verdicts from a second LLM pass; removing it wholesale let the meta-analyzer veto CRITICAL findings on real droppers and exec() payloads.
  • Policy is selectable via SKILLSPECTOR_META_SEVERITY_FLOOR=none|semantic|upstream (default semantic), read on every apply_filter call so it can be flipped at runtime for in-process A/Bs as well as per benchmark run.
  • Implemented as a drift-guarded runtime patch in src/skillspector/exaforce/; no upstream-tracked file is modified.

Details

Why the floor is not simply removed. Every analyzer's findings flow into the meta-analyzer, whose prompt labels them all "static analysis findings". Corpus-wide on MalSkillBench the semantic rules produce ~2× as many HIGH/CRITICAL findings as all static rules combined, so upstream's floor (src/skillspector/nodes/meta_analyzer.py:376, applied at :455) was primarily an LLM-vs-LLM tiebreak. Same-day controlled re-scan of the 94 borderline units a full 900-unit none run got wrong, two replicates each (188 unit-scans per arm), nvidia.nemotron-super-3-120b:

floor mode TP FP correct
upstream 68 10 72
semantic (this PR's default) 65 7 72
none 54 3 65

Spot-checked none losses included a base64 PowerShell download-and-execute and Fernet-decrypted exec() in setup.py, flagged CRITICAL at confidence ≥ 0.9 by the semantic analyzers and then dropped by the meta-analyzer. Spot-checked semantic drops of static RA1/AS1 hits were 11/12 regex misfires on benign text ("never modify your own rules", tutorial heredocs), including on malware units whose payload lived elsewhere.

Patch. apply() in src/skillspector/exaforce/_filter_patches.py wraps LLMMetaAnalyzer.apply_filter once at import. Each call resolves the mode and dispatches: upstream calls through; none shadows self._HIGH_SEVERITY_FLOOR with an empty set for one call; semantic runs upstream's filter twice — floor on for LLM-backed findings, floor off for static — then restores input order by finding_id. Drift guards raise PatchDriftError at import if the floor's value changes, apply_filter disappears, or upstream stops reading the floor via self. (the shadowing depends on that access path). Finding carries no source-analyzer field, so is_llm_finding matches normalized rule ids (LLM_RULE_PREFIXES + LLM_RULE_IDS); the benchmark corpus shows rare LLM-emitted variants like ssd-2, hence the case-insensitive strip. A warning is logged when a batch returns zero verdicts for a file that had findings, since under none/semantic that drops the file's static findings where upstream kept CRITICAL/HIGH.

Tests. tests/exaforce/test_patches.py runs one subprocess per mode against a mixed static/LLM batch (explicit denial, omission, confirmation, plus an omitted TP4), and one that flips the env var at runtime and checks instance state is restored. Four upstream tests in TestApplyFilterSeverityFloor now fail by design (they assert the static floor), on top of the four schema-pruning tests that already did; docs/superpowers/EXPECTED_TEST_FAILURES.md is updated. Per fork policy those upstream tests are left untouched — no deselect/xfail — so CI stays red by design.

Tests: 1917 passed, 8 failed by design (uv run pytest); ruff check, ruff format --check, and mypy clean on the new module.

Implementation Plan

Context. Fork policy is "trust the LLM over static". Upstream's CRITICAL/HIGH floor in the meta-analyzer keeps static findings the LLM rejected, producing noise in production. A first attempt emptied the floor entirely; a 900-unit benchmark showed −24 TP / +8 TN, and investigation traced the recall loss to the meta-analyzer overruling the semantic analyzers rather than to static rules.

Approach. Keep the floor only where it arbitrates between two LLMs; drop it wherever a static rule disagrees with the LLM. Expose the three policies as an env-selected mode so the benchmark can run controlled arms.

# Layer File Change
1 Patch src/skillspector/exaforce/_filter_patches.py New: mode resolution, two-pass apply_filter wrapper, drift guards
2 Wiring src/skillspector/exaforce/__init__.py Register _filter_patches.apply()
3 Tests tests/exaforce/test_patches.py Per-mode subprocess tests + runtime-toggle test
4 Docs docs/superpowers/EXPECTED_TEST_FAILURES.md Record the 4 new by-design upstream failures

Verification. Fork tests 14/14; mypy clean on the new module; a fresh-context /code-review pass produced 8 findings, all addressed (LLM-backed TP4 coverage, per-call mode resolution, source-access drift guard, empty-verdict warning, mypy) or explicitly declined (xfail of upstream tests — conflicts with fork policy). Full suite 1917 passed / 8 by-design upstream failures. Same-day benchmark A/B/C on --from-run <head> --failures-only, two replicates per arm (runs 864ccf78be3b, a5c5a221a8c6, f5aaf9771d20, 2310deadaad9, 729b41e62cfc, 838e2a2288cc in benchmark/benchmark.db). Still outstanding: a same-day full 900-unit semantic vs upstream pair before treating the magnitude as settled.

Upstream's meta-analyzer keeps CRITICAL/HIGH findings the LLM rejected,
tagged llm-unconfirmed. In production that surfaces regex noise the LLM
already dismissed. Emptying the floor entirely cost recall: the floor was
mostly shielding the semantic analyzers' LLM findings from a second LLM
pass, and the meta-analyzer vetoed CRITICAL findings on real droppers.

Keep the floor only for LLM-backed findings (SQP/SDI/SSD, TP4); every
static finding now follows the meta-analyzer verdict. Mode is selectable
per call via SKILLSPECTOR_META_SEVERITY_FLOOR=none|semantic|upstream
(default semantic). Same-day A/B/C on 188 borderline unit-scans:
upstream 68 TP/10 FP, semantic 65 TP/7 FP, none 54 TP/3 FP.

Four upstream TestApplyFilterSeverityFloor tests now fail by design;
recorded in docs/superpowers/EXPECTED_TEST_FAILURES.md, left untouched
to avoid upstream-sync conflicts.
@will-exaforce
will-exaforce requested a review from smoy September 2, 2026 12:42
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