Files
Open_Ultra_SAST/.kiro/specs/pre-push-safety-net/strategy-review-2026-09-28.md
T
Marius CiepluchandClaude Fable 5.1 ec7a5ed90d docs(pre-push-safety-net): record the model-driven pipeline result on v2 and stop
Exploratory, 7 of 17 cases, stopped when the account emptied:
- source-only scope: a model reading every product file puts the vulnerable
  function among the candidates in 15/17 (ranker 1/17, vocabulary 2/17);
- per-file triage + per-file 6-step hunts + two-pass agreement: 12/15 candidate
  cases on the validation set; 4/7 on the full run, two more in one pass of two;
- precision: 5 pgAdmin agreed findings read against source, 3 true (privileged),
  2 false with self-contradicting rationales; agreement cannot catch an error
  both passes make;
- cost: $0.009-0.022 per candidate for two passes.

Four instrument defects fixed on the way (model id, provider routing, JSON-mode
final turn, unbounded tool output) void the earlier hunter number. No gate is
met; qualification needs population v3 and a protocol that states the judge
stage, the pass rule and a config-family question.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
2026-09-29 11:29:06 +02:00

121 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Strategy review after the independent evaluations (2026-09-28)
Status: for maintainer decision. Nothing below changes the approved specs until a direction is chosen.
## 1. What was measured
Two populations of real vulnerabilities that no rule had seen, each with its fix and a benign control, scored by
protocols committed before the first scan (`benchmarks/independent/`).
| | v1 (11 cases, 2026-09-24) | v2 (17 cases, 2026-09-28) |
|---|---|---|
| Analyzer | 13180a1 | a75b9f6 (v1 plus every retune learned from v1) |
| Recall | 1 / 11 | **0 / 17** (Wilson 95% 0–0.18) |
| Sampled precision | 1 / 12 | **0 / 17** (0–0.18) |
| Question completion | near zero | **12%** (13,206 / 108,374) |
| Benign false alerts | — | 3 |
| Run-to-run stability | not measured | 2 of 100 findings differ |
Between v1 and v2 the analyzer received every fix v1 suggested: file-owned graph gaps, `.gitignore` semantics,
Node `vm` sinks, Django sources, quoted-only SQL escapes, fixed-origin redirects, guards and allowlists. On the
v1 cases those retunes raised detection from 1 to 4 of 11. On untouched cases they transferred to nothing.
## 2. Why, per class of cause (observed in v2, from source)
1. **Entry points are framework-shaped, and our sources are token lists.** Request data arrives as
`filter_input(INPUT_GET, …)` and the WordPress REST `$request` (GoPay, Directorist), plugin helpers such as
`get_url_var()` (Groundhogg), FastAPI and NestJS typed or decorated handler parameters (qwed, ReactPress,
LMDeploy, ContextForge), and Koa's `ctx.request` (Budibase). A vocabulary grows one framework at a time, and
each new population brings frameworks it lacks.
2. **Flows cross module boundaries the frontends do not resolve.** JavaScript `require`d modules (FUXA's route
to its storage module), dependency-injected receivers (YesWiki, v1), registry dispatch (OpenCVE, v1), parser
combinators (alerta, v1).
3. **Sinks are matched by name, so precision fails where names collide.** Every sampled v2 finding was false:
a regex `.match()`/`.exec()` read as injection, `header()` with an integer cast, parameterised ORM statements,
client-SDK code, a process's own `argv`, a generated database name.
4. **Scale.** Large repositories complete a few percent of their questions inside a 40-minute budget (pgAdmin
2%, ContextForge 6%, Budibase 1%); one repository crashes the taint batch on every scan (LMDeploy).
Classes 1–3 are breadth problems: each fix is correct and local, and the next repository needs another. That is
the pattern both evaluations show, and the reason more patching on the same design is unlikely to reach M4.
## 3. What we know about the alternative
The tool hunter (a model with `read_file` / `grep_repo` / `find_refs`) was measured once, on 2026-09-06, on the
vibe-py pair holdout: **12 of 14 detected, Youden +0.857, no leaks**, against the overlay's 5 of 14 on the same
pairs (class-aware scoring; `benchmarks/measurements/2026-09-06-vibe-py-holdout-hunter-*.json`). Caveats: one
language, curated excerpts, a pair corpus rather than whole repositories, and never run on an independent
population. Classes 1–3 above are exactly where a model reading the code has an advantage over token lists;
class 4 (cost, scope) is where it has a disadvantage.
## 4. Options
**A. Continue engine-first.** Model framework entry points, fix the sink-collision classes, raise completion,
then qualify on a v3 population. Every item is sound; the evidence (v1 → v2) says the breadth does not converge
at the pace one population teaches. Highest effort, weakest evidence.
**B. Model proposes, engine proves.** The hunter supplies recall; the CPG, where it can reach, supplies the
witness and admission evidence; findings the engine cannot confirm are reported as unconfirmed rather than
dropped. This inverts `propose-adjudicate-prove`'s current out-of-scope line ("making an LLM the adjudicator")
only partly: the model proposes, it does not adjudicate. Needs a cost and scope design (which files a pre-push
scan hands the model) and a run of the **unchanged** hunter on an untouched population before any tuning.
**C. Narrow the product.** Qualify one ecosystem first (for example WordPress plugins, the declared v0.1 target),
where a framework model is finite, and ship only that capability (M5 already allows per-capability enablement).
Compatible with A or B.
## 5. Recommendation
Run the decisive, cheap experiment before choosing: **the unchanged tool hunter on the v2 pins**, scored with
protocol v2's matching, within a stated model-cost budget. v2 is spent for the engine, but the hunter was never
tuned on it; the rule is that no hunter prompt, tool or configuration may change before that measurement is
recorded, and the scope handed to it must be decided and written down first (it must not use the fix diff or the
declared sites, which would leak the answer).
- If the hunter reaches useful recall on v2 with few fixed-side and benign alerts, re-plan M2–M4 around B,
narrowed by C.
- If it does not, pursue A narrowed by C, starting with the WordPress entry-point model and the sink-collision
precision classes, and qualify on a v3 population.
## 6. Decisions needed
1. Direction: run the hunter experiment first (recommended), or choose A, B or C now.
2. For the experiment: the scope handed to the model per pin, the model, and the cost ceiling.
3. Whether the roadmap's M1b/M2 work (rendering, runtime) pauses until the direction is chosen.
## 7. Update 2026-09-29: what the experiment showed
Recorded in `benchmarks/independent/results-v2-model-pipeline.json` (exploratory; v2 is spent for tuning).
**Instrument first.** Four defects had made every model-backed result since 2026-09-10 unmeasured: the default
model id was the product name and the API rejected it (cf57995); the hunter reached the embeddings provider, not
the chat provider (cf57995); the final-answer turn used the provider's JSON mode and the answers were lost
(3ffb8b4); one grep match in a minified bundle overflowed the model's context (8794884). The "hunter 1/17" number
in section 5's experiment was produced through the broken answer turn and is void.
**Scope selection, source only.** The declared vulnerable function is among the candidates for: the ranker's
top-20 entry points 1/17; the shipped sink vocabulary 2/17; a model reading every product file 15/17 ($14). The
vocabulary route (option A) is confirmed not to converge on this population.
**Verification.** Per-file triage, one 6-step tool hunt per file, two independent passes, report only agreement:
- validation set: 16 of 20 declared sites agreed, 12 of 15 cases; the 26 pgAdmin candidates that flickered
between single runs reduce to 9 stable, 4 disputed, 13 quiet;
- full run, 7 of 17 cases before the account emptied ($23): detected 4/7 (pgAdmin, qwed, LMDeploy,
ReactPress); ContextForge and FUXA found in one pass of two; winml missed because a CORS literal is triaged
"constant" -- the config family needs its own question, not the reachability one;
- cost: $0.009-0.022 per candidate for two passes (old single-pass design: $0.017); a full two-pass run of the
population is roughly $45-50.
**Precision is the open problem.** 196 agreed findings across 7 cases (pgAdmin alone 126 of 875 verified). Five
pgAdmin findings read against source: three true (privileged; raw `{{ data.* }}` in SQL templates, unescaped
stored `schema_res`), two false with rationales that contradict their own verdict (an ORM call the model itself
calls parameterized; a pickle from the signed session). Agreement cannot catch a mistake both passes make. The
proposed next filter is a no-tools judge over each agreed finding's rationale and code (~$0.002 per finding),
which was not run. Two-pass agreement is itself not stable between runs (ContextForge, FUXA).
**Standing.** Recall: the model route reaches 12/15 candidate-site cases where the engine reached 0/17 and the
vocabulary 2/17; that decides between options A and B in the model's favour for recall. Precision, fixed-pin
behaviour and cost at product scale are unmeasured. No gate is met. Any qualification needs population v3 and
a pre-registered protocol that also states the judge stage, the pass rule and the config-family question.