2026-10-12 18:16:41 +02:00
|
|
|
|
# Brief: contributor-scan, make the model layer a tool someone runs
|
2026-09-08 19:40:38 +02:00
|
|
|
|
|
|
|
|
|
|
**Status:** brief only, unapproved. Successor to `model-grounded-detection`, which is complete (16/16 tasks)
|
|
|
|
|
|
and is not reopened by this.
|
|
|
|
|
|
|
|
|
|
|
|
## The gap this exists to close
|
|
|
|
|
|
|
|
|
|
|
|
`model-grounded-detection` built and measured an arbiter. It works: 41.3% of the corpus reaches
|
|
|
|
|
|
`model_entailed` against the flat IR's 2.2%, and the wired pipeline scores 86.7% pair-correct at 6.7% leak
|
|
|
|
|
|
against the LLM hunter's 76.7% / 12.3%, at a fifth of the model calls and ~100× cheaper.
|
|
|
|
|
|
|
|
|
|
|
|
**And none of it is reachable by a user.** `ousast scan` still runs
|
|
|
|
|
|
`preprocess → map → overlay → obligations → regress → report` and touches the model layer nowhere;
|
|
|
|
|
|
`model/pipeline`, `judge`, `config_value`, `report`, `calibrate` and `execution` have zero production callers
|
|
|
|
|
|
outside `model/`. Every number quoted above came from a measurement harness in a scratch directory.
|
|
|
|
|
|
|
|
|
|
|
|
That is a scope gap in the predecessor spec, not an oversight in its execution: it had no task saying *make
|
|
|
|
|
|
this reachable*, and its Req 11.2 (the gates stay byte-identical) is satisfied precisely **because** nothing
|
|
|
|
|
|
is wired. The spec is complete and the feature is unshipped, both at once.
|
|
|
|
|
|
|
|
|
|
|
|
## What the goal actually is
|
|
|
|
|
|
|
|
|
|
|
|
> "a real world tool that helps developers when they want to contribute"
|
|
|
|
|
|
|
2026-10-12 18:16:41 +02:00
|
|
|
|
That reframes the work. A contributor does not have a labelled function and a known weakness class; they
|
2026-09-08 19:40:38 +02:00
|
|
|
|
have a repository they are about to touch and a question about what is risky in it. Everything measured so
|
|
|
|
|
|
far is function-level pairs: a labelled function, a declared family, a known target. None of it exercises the
|
|
|
|
|
|
questions a whole repository asks.
|
|
|
|
|
|
|
|
|
|
|
|
## What is missing, in the order it bites
|
|
|
|
|
|
|
|
|
|
|
|
1. **Scan integration.** Wire `model/pipeline` into `ousast scan` so a finding with a rung reaches a report.
|
|
|
|
|
|
Without this nothing else matters.
|
|
|
|
|
|
2. **Whole-repo scanning.** Region selection from entry points instead of a labelled function; ranking across
|
|
|
|
|
|
thousands of candidates rather than one; a budget policy (the enumerator caps at 8 candidates per region,
|
2026-10-12 18:16:41 +02:00
|
|
|
|
which is meaningless at repo scale); and the performance envelope: a CPG build on a 100k-line tree, not a
|
2026-09-08 19:40:38 +02:00
|
|
|
|
40-line excerpt. **This has never been run.**
|
|
|
|
|
|
3. **A known-vulnerable repository as the target.** The 176 real-world CVE pairs already in the corpus are
|
2026-10-12 18:16:41 +02:00
|
|
|
|
single-function excerpts and do not exercise any of the above. Running against a real checkout (curl,
|
|
|
|
|
|
libpng, a WordPress plugin) is the only way to find what breaks at scale.
|
2026-09-08 19:40:38 +02:00
|
|
|
|
4. **The memory decision.** 173 of those 176 pairs are `memory` family, routed to a deferred execution tier
|
|
|
|
|
|
that is a stub. Either adopt Clearwing for them or scope C memory-safety out explicitly. Right now it is
|
|
|
|
|
|
neither, and a sixth of the corpus sits idle.
|
|
|
|
|
|
5. **Retire `evidence_level`.** It duplicates `rung` in intent; noted as a follow-up in group 3 and never
|
|
|
|
|
|
tracked.
|
|
|
|
|
|
|
|
|
|
|
|
## Already built and unrecorded
|
|
|
|
|
|
|
|
|
|
|
|
These were delivered during `model-grounded-detection` without being in any spec. They belong here so the
|
|
|
|
|
|
record is not scattered:
|
|
|
|
|
|
|
2026-10-12 18:16:41 +02:00
|
|
|
|
- The pipeline itself (`model/pipeline.py`): Req 8 existed, task 3.2 built `judge()`, and nothing ever
|
2026-09-08 19:40:38 +02:00
|
|
|
|
said to connect an enumerator to it. Built, tested, measured, unspecified.
|
2026-10-12 18:16:41 +02:00
|
|
|
|
- Ops: a single image carrying tool and engine, a compose file with read-only target and tmpfs scratch,
|
2026-09-08 19:40:38 +02:00
|
|
|
|
bash/zsh/PowerShell wrappers that make the container invisible, and a pyinfra deploy for host installs.
|
|
|
|
|
|
The Docker path is written and syntax-validated but **the image has never been built**.
|
2026-10-12 18:16:41 +02:00
|
|
|
|
- Corpus: the `owasp` slice (40 validated xss pairs; `output_encoding` went from 1 pair to 41) and
|
2026-09-08 19:40:38 +02:00
|
|
|
|
authored sink/sanitizer facts for Java and JavaScript.
|
|
|
|
|
|
|
|
|
|
|
|
## Known limits to carry forward
|
|
|
|
|
|
|
|
|
|
|
|
- `permissive_values` is a flat literal list with no keyword association, so it holds both `True` and `False`.
|
|
|
|
|
|
`debug=True` is dangerous where `httponly=True` is safe and the model cannot tell them apart.
|
|
|
|
|
|
- Interprocedural resolution is the most repeated miss cause across every experiment: OWASP's helper classes,
|
|
|
|
|
|
`angular-http-server`'s `resolveUrl`. Joern supports it; our queries may not be configured for it.
|
2026-10-12 18:16:41 +02:00
|
|
|
|
- Five bugs in `model-grounded-detection` shared one shape: a taint-specific assumption applied to every
|
2026-09-08 19:40:38 +02:00
|
|
|
|
family, each found by a measurement rather than a check. Any new family-specific path should be audited for
|
|
|
|
|
|
siblings before it is measured, not after.
|
2026-09-09 13:13:11 +02:00
|
|
|
|
|
|
|
|
|
|
## What the evidence says so far (appended 2026-09-09, after group 2)
|
|
|
|
|
|
|
|
|
|
|
|
This is a research project whose findings get taught, so a negative result is a deliverable. But the standing
|
|
|
|
|
|
test the work has to pass is blunter than that:
|
|
|
|
|
|
|
|
|
|
|
|
> "it must find the bugs, otherwise it's SAST noise. most devs know codeql / semgrep have dataflow
|
|
|
|
|
|
> capabilities but never use it"
|
|
|
|
|
|
|
|
|
|
|
|
That sentence settles what the differentiator is, and it is **not the dataflow engine**. CodeQL and Semgrep
|
|
|
|
|
|
already have one and developers do not turn it on. What they will not do is write queries, tune a ruleset, or
|
|
|
|
|
|
read two hundred rows to find the one that matters. So the product is the **rung** -- a claim the graph
|
|
|
|
|
|
decided, reported without configuration -- and everything below `model_entailed` is on trial.
|
|
|
|
|
|
|
|
|
|
|
|
### The scoreboard, honestly
|
|
|
|
|
|
|
|
|
|
|
|
| checkout | known in-scope | found | rung | false positives |
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
| vampi (python, 520 lines) | 3 | **3** | all `model_entailed` | 2, both endpoints the OpenAPI spec declares public |
|
|
|
|
|
|
| libpng (c, 89k lines) | 0 (its CVE is out of family) | — | — | 30 over the session, now 1 |
|
|
|
|
|
|
|
|
|
|
|
|
vampi is the result worth teaching: SQL injection across two modules, a broken object-level authorization,
|
|
|
|
|
|
and a second BOLA **the engine found before it was in the recipe**. libpng is the result worth being honest
|
|
|
|
|
|
about: 198 rows, zero of them valid, and the reason is structural rather than a tuning problem -- the library
|
|
|
|
|
|
never calls a sink the `c/memory` family models.
|
|
|
|
|
|
|
|
|
|
|
|
### What the session actually demonstrated
|
|
|
|
|
|
|
|
|
|
|
|
Nine defects, each found by a measurement and none by review. Three of them were one bug class in three
|
|
|
|
|
|
matchers (`resolve` in `resolveUrl`, `user` in `users`, `gets` in `fgets`); two were capability present but
|
|
|
|
|
|
unreachable (the model layer itself, then `query_batch`); two were a paid budget stopping unpaid work and a
|
|
|
|
|
|
question scoped to a function when the bug crosses modules.
|
|
|
|
|
|
|
|
|
|
|
|
The teachable finding is not any one of them. It is that **a corpus of function-level pairs cannot see any of
|
|
|
|
|
|
them**, and every one appeared within minutes of pointing the tool at a real repository. The gates stayed
|
|
|
|
|
|
byte-identical through all nine fixes, which is the same statement from the other side.
|
|
|
|
|
|
|
|
|
|
|
|
### What this changes about priorities
|
|
|
|
|
|
|
|
|
|
|
|
* **2.11 (the suspicion band) is the critical path**, not a cleanup task. libpng produced 163 rows that
|
|
|
|
|
|
assert an API is present rather than misused. That is precisely the noise the quote above is about, and no
|
|
|
|
|
|
amount of arbiter accuracy survives shipping it.
|
|
|
|
|
|
* **A family that cannot decide its class must say so** (2.16). A silent C scan must not read as a clean bill
|
|
|
|
|
|
of health; that is worse than noise because it is quiet.
|
|
|
|
|
|
* **Negative results get recorded as measurements, not as failures.** `c/memory` scoring zero on libpng is a
|
|
|
|
|
|
finding about the abstraction, and the artifact says which abstraction would reach it (size arithmetic,
|
|
|
|
|
|
i.e. a constraint problem) rather than leaving it as a gap to be tuned at.
|
2026-09-09 13:15:54 +02:00
|
|
|
|
|
|
|
|
|
|
### The final objective, and what it needs
|
|
|
|
|
|
|
|
|
|
|
|
> "that + an LLM way of diving in without being forced to rethink the code"
|
|
|
|
|
|
|
|
|
|
|
|
Two halves, and they are complementary. The **rung** says *this was decided*; the second half says the
|
|
|
|
|
|
developer must not have to re-derive *why*. The material for that already exists and is called the
|
|
|
|
|
|
**witness**: `username -> db.session.execute(text(user_query)) in models/user_model.py (line 73, 8 steps)` is
|
|
|
|
|
|
the reasoning, already written down, already attached to the finding.
|
|
|
|
|
|
|
|
|
|
|
|
So the objective is largely a plumbing question, and the session's recurring defect shape applies to it.
|
|
|
|
|
|
Checked rather than assumed: `openultrasast.get_finding` returns the whole record including the witness, but
|
|
|
|
|
|
`openultrasast.findings` was dropping the rung -- so an LLM listing libpng's 198 rows could not tell the one
|
|
|
|
|
|
the graph decided from the 197 an enumerator proposed without fetching all 198 individually. Fixed: the list
|
|
|
|
|
|
carries the rung, leads with what was decided, and returns a per-rung tally.
|
|
|
|
|
|
|
|
|
|
|
|
What still stands between here and the objective:
|
|
|
|
|
|
|
|
|
|
|
|
* **The witness is one line.** It names the source, the sink, the file and the step count, but not the steps.
|
|
|
|
|
|
For "dive in without rethinking", the path elements are the thing worth having, and the query already walks
|
|
|
|
|
|
them (`flow.elements`) before discarding all but the first.
|
|
|
|
|
|
* **The suspicion band drowns the signal** (2.11). An LLM given 163 "this API is present" rows will reason
|
|
|
|
|
|
about them, at length, and that is worse than a human ignoring them.
|
|
|
|
|
|
* **A family that cannot decide must say so** (2.16), or the LLM will confidently explain a silence.
|