mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
One page, docs/where-we-stand.md, composed with the COBESY composition method (governing idea, SCQA opener, MECE sections): the M4 gates and the state against them, the detection approach step by step, ax on the server, token economics before and after, and the open items. Every figure is quoted from a committed record whose path stands next to it. decision-engine.md, evaluation.md and token-ergonomics.md link to the page where they repeated its content (detail tables kept); decision-engine.md's Experiments section now records exp-002's result instead of saying it has not run. Linked from the nav, the index and the README. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
259 lines
19 KiB
Markdown
259 lines
19 KiB
Markdown
# Where we stand
|
|
|
|
State as of 2026-10-02 (v2.0.0). This page answers five questions in one place. How far is
|
|
detection from its gates. How does the project now try to close that gap. How does the model work
|
|
run on ax. What did the tokens cost before and after that move. What is still open. Every figure
|
|
is quoted from a committed record whose path stands next to it; detail tables stay on the pages
|
|
linked from each section.
|
|
|
|
The goal has not changed: a safety net on repositories the tool has never seen, which says
|
|
BLOCK or ADVISORY about the code a push would change and stays silent on fixes and on benign
|
|
commits. Two independent populations have been spent against that goal, with engine recall 1 of
|
|
11 and then 0 of 17. The hand edits that closed misses on the first population did not carry to
|
|
the second. So the question is no longer which rule to add next. It is how a change to detection
|
|
can be shown to work on code it was not tuned on, at a cost per candidate that can be paid for
|
|
every push.
|
|
|
|
The answer this page records: the pre-push safety net is NO-GO against every M4 gate, and the
|
|
project has stopped trying to reach the gates by hand. Every detection change now has to win a
|
|
controlled measurement on held-out repositories, run on an agentic plane whose cost per candidate
|
|
is known. The pieces of that method exist and have been measured. None of them has produced a
|
|
BLOCK decision yet.
|
|
|
|
## 1. Detection capability and goals: NO-GO, measured
|
|
|
|
No M4 gate is met on an independent population. The qualification milestone for the pre-push
|
|
safety net (M4) requires, together: at least 95% actionable precision, at least 90% supported
|
|
recall, at least 95% supported-check completion, the runtime gates, and zero fixed-side or benign
|
|
false alerts. A population is frozen before any
|
|
case is scanned, scored once under a pre-registered protocol, then spent; it can inform changes
|
|
but can qualify nothing afterwards ([Evaluation](evaluation.md#independent-populations-and-the-m4-gates)).
|
|
|
|
| Gate | Required | Latest independent result | Record |
|
|
| --- | --- | --- | --- |
|
|
| Supported recall | >= 90% | engine 0 of 17 declared sites on population v2 | `benchmarks/independent/results-v2.json` |
|
|
| Actionable precision | >= 95% | engine 0 of 17 (`precision`); the tool hunter on the same population 2 of 14 | `results-v2.json`, `results-v2-hunter.json` |
|
|
| Supported-check completion | >= 95% | 13,206 of 108,374 | `results-v2.json` |
|
|
| Fixed-side and benign alerts | zero | 0 fixed-side, which the record calls met trivially since nothing was detected; 3 benign alerts | `results-v2.json` |
|
|
|
|
Three later measurements narrow the picture without changing the verdict.
|
|
|
|
- The plane reproduces the script pipeline's agreement at a known cost. On the model-driven
|
|
pipeline's validation set (46 candidates, 15 cases of v2, 20 declared vulnerable sites), the
|
|
plane's verify passes a and b plus a tie-break on the 10 disputed candidates agreed on 16 of 20
|
|
declared sites. The script reference agreed on 16 as well. The cost was $0.0205 per candidate
|
|
(`benchmarks/independent/plane-increment-2.json`). The record carries two caveats. The tie-break
|
|
was chosen after seeing the first measurement on the same set, so this compares plane with
|
|
script and qualifies nothing. And the passes flag fewer declared sites than the reference in
|
|
any pass, 17 against 19; that count is a correction in `plane-increment-1.json`, whose first
|
|
reading took agree's `declared_sites_matched` (19, coverage by candidates) for "found in either
|
|
pass".
|
|
- The learned decision engine ranks, but may not block. On six families, evaluated on
|
|
repositories the compiled program was not shown, the pooled AUC of its score runs from 0.77
|
|
(injection) to 0.96 (access control). The stricter reading is the within-pair AUC, which asks
|
|
whether the vulnerable side of a fix scores above its own fixed side: 0.72 to 0.93. BLOCK is
|
|
offered for no family; calibration holds only for output encoding, and there no fold reaches
|
|
the required precision (`benchmarks/measurements/2026-10-02-decision-engine-slice-2/record.json`).
|
|
The per-family table is on [Decision engine](decision-engine.md#second-slice-six-families-2026-10-02).
|
|
- PHP coverage is in-sample. The PHP quick rules were written and tightened while looking at
|
|
the pins and fixtures they are scored on, so the record says so itself (`provenance.in_sample` in
|
|
`benchmarks/measurements/2026-09-30-php-quick-rules/measurement.json`): a development check, not
|
|
a recall estimate. On real PHP code the Joern engine is the instrument.
|
|
|
|
What has not been touched: population v3, 15 PHP cases, frozen as the one-time final check. No
|
|
development or analysis work reads its cases, a test fails when anything in the tree names one
|
|
of its repositories, and it runs once, after a prediction is committed.
|
|
|
|
## 2. Our approach to detection, step by step
|
|
|
|
The path changed because hand edits did not transfer. Every gain before 2026-09-30 was a hand
|
|
edit after a miss: a rule, a taint vocabulary entry, a guard, a prompt line, a tie-break. Each was
|
|
tuned on the cases that exposed it, and the tuning measured on population v1 did not carry to v2
|
|
(1 of 11, then 0 of 17). The conclusions drawn: hand edits do not transfer. The data that could
|
|
teach a better decision cannot be produced by hand at the scale needed, so it has to be harvested
|
|
and labelled by machinery with a boundary around it. Framework knowledge (which function is a sink in which
|
|
framework) is useful as a prior but must stay optional, or the engine learns the vocabulary
|
|
instead of the code. A reader might object that a classifier is slower to show gains than the
|
|
next rule. It is. The difference is that its gains are measured where rules were not: on
|
|
repositories it never saw.
|
|
|
|
The chain, one job per step. The design is on [Memory and detection](memory-and-detection.md);
|
|
this is the order of operations.
|
|
|
|
1. Instruments produce signals, never verdicts. Quick rules, the Joern engine, model sink
|
|
classification, the plane's verify passes a and b with the tie-break c, repository facts
|
|
(callers, spans) and model source/sink/sanitizer roles each write one block of a candidate's
|
|
record. None decides alone.
|
|
2. Feature records are allow-listed, and missing is not zero. A candidate is `(path, function,
|
|
family)` of one repository and pin. An instrument that did not run, had no coverage or failed
|
|
records that state; "silent" and "did not look" stay distinguishable. Input profile v1 withholds
|
|
the engine block and the function length, which a leak audit found separate the two sides of a
|
|
pair by construction (`benchmarks/measurements/2026-10-01-decision-engine-leak-audit/`), and
|
|
since 2026-10-02 the plane's `verify.flag_b` and `ms.operation`, flagged the same way by the
|
|
audit of the plane profile (`2026-10-02-decision-engine-harvest-advisory/record.json`,
|
|
`src/openultrasast/learn/schema.py`).
|
|
3. Labels come from ground truth only. A fail-closed allow-list (`src/openultrasast/learn/sources.toml`)
|
|
names the sources: the spent populations' declared sites and fix ranges, the vulnerable and
|
|
fixed sides of pairs, including the 97 real public fixes in `benchmarks/pairs/advisory-fixes/`,
|
|
benign controls and recorded adjudications. Assumed-benign commits are their own source,
|
|
weighted 0.5 and reported separately. A plane verdict is a feature, never a label: "rejected by
|
|
the plane" is not a negative, or the engine would learn to reproduce the plane's mistakes.
|
|
4. Retrieval finds similar labelled cases. A Gower distance over the signal profile, re-ranked
|
|
by cosine similarity of code embeddings (OpenAI `text-embedding-3-small` through OpenRouter,
|
|
cached by excerpt hash). The evaluation boundary is the only door into a prompt: never the
|
|
candidate's own repository group, never a group the fold evaluates.
|
|
5. A classifier compiled in house, DSPy-style. `retrieve -> classify(k)`: an instruction and
|
|
compiled demonstrations (a fixed prefix, so the provider's cache hits), the retrieved examples,
|
|
then the candidate's excerpt and signals. The model answers vulnerable, not vulnerable or
|
|
unsure with a rationale that cites a line; `k` samples give a score. One program is compiled
|
|
per profile and family on a compile split that is never evaluated.
|
|
6. Calibration on held-out repositories. Platt scaling of the score, cross-fitted by
|
|
repository group so no map is checked on the fold that fitted it. Calibration holds when the
|
|
slope interval contains 1, the intercept interval contains 0 and ECE <= 0.05.
|
|
7. Two operating points. BLOCK only where calibration holds and a fold reaches the required
|
|
precision; otherwise ADVISORY. An unsure majority can never BLOCK.
|
|
8. A/B experiments are the only way a change is adopted. Prompts, models, pass counts,
|
|
features and sampling are compared under a pre-registered manifest with frozen units, paired
|
|
arms, a repository bootstrap and two looks. exp-002 is the first to finish: a retrieval
|
|
ensemble (five samples over disjoint example sets at temperature 0) against today's
|
|
temperature sampling. It raised re-run agreement a little (0.906 against 0.875, interval
|
|
spanning zero) and lowered the within-pair AUC from 0.815 to 0.741 at 2.4 times the cost per
|
|
candidate ($0.0051 against $0.0021). Decision: REJECTED
|
|
(`benchmarks/experiments/exp-002-retrieval-ensemble/result.json`).
|
|
9. Extrapolation with intervals. Every number carries a 95% repository-cluster bootstrap
|
|
interval; the pooled and the within-pair readings are both reported, and the stricter one is
|
|
named.
|
|
10. The one-time v3 check. When a program is adopted and a prediction is committed, population
|
|
v3 is scored once under `benchmarks/independent/protocol-v3.md`.
|
|
|
|
## 3. AI integration: ax on the server
|
|
|
|
All model work runs as Tasks on google/ax over Agent Substrate, in a single-node kind cluster on
|
|
the maintainer's host, each Task in its own gVisor sandbox. There is no local subprocess path.
|
|
The reconciler (`ousast plane run`) is a host process: it applies a Run's Workspaces and Tasks,
|
|
resumes each Task, sends its start signal, collects its artifacts and records state. Flow and
|
|
contract: [The plane on ax](plane.md); bring-up and the host's lessons: [ax on this host](ops/ax/README.md).
|
|
|
|
What each Run guarantees:
|
|
|
|
- One Model and one budget per Task. A Task binds at most one ax Model (`deepseek-flash` for
|
|
chat, `openrouter-embedding` for embeddings) and `budget: {usd, calls}`. The metered client
|
|
refuses the next call at the ceiling; the task ends unfinished and resumes on a rerun. Model-free
|
|
tasks run with a zero budget, so a stray call fails loudly.
|
|
- Credentials exist only in the start request. Agent Substrate boots each template once as a
|
|
golden actor, so the runner never starts by itself. It waits for `POST /ousast/v1/start`, which
|
|
the reconciler sends through `atenet-router` to that actor alone. The key is read from the
|
|
operator's environment or `.env`, kept in memory by the runner, passed to the command and
|
|
redacted from echoed stderr; no manifest, rendered Task or file carries it.
|
|
- Per-task egress. One EgressPolicy per actor through the `atenet-egress` gateway, deny by
|
|
default, hostnames only: plain HTTP to the artifact receiver, TLS passthrough to the Workspaces'
|
|
Git hosts and the Model's declared hosts. Deleted with the actor.
|
|
- Artifacts come back through the gateway. The runner posts its output directory as a tar to
|
|
the receiver, an HTTP server inside the reconciler process on the host, reached through a Service
|
|
with an EndpointSlice to the host. That delivery is the task's completion; ax has no Completed
|
|
phase.
|
|
- Memory on S3-compatible RustFS. `OUSAST_MEMORY=s3://sast-memory` on a RustFS server. The
|
|
store verifies the bucket at every start (versioning, lifecycle, tags, S3 Select) and refuses to
|
|
start when anything is missing; there is no local or fetch-and-filter fallback. Each row kind
|
|
has a fixed set of queryable fields, enforced at write time
|
|
([Memory](memory.md#verify-at-startup-no-fallback), [RustFS setup](rustfs.md)).
|
|
|
|
Lessons the host taught, each from a failed live run (the full list is on
|
|
[ax on this host](ops/ax/README.md#what-this-host-taught-each-one-cost-a-failed-live-run)):
|
|
|
|
- Substrate installs no WorkerPool, and actor images must be pinned by digest.
|
|
- A release `ax` CLI skews from the server; build it from the deployed checkout.
|
|
- ax has no Completed phase, so completion is our artifact delivery.
|
|
- The golden boot means the runner must wait for a start request.
|
|
- The egress gateway dials the address the actor connected to, so the receiver is a Service
|
|
dialled by ClusterIP with its name as Host.
|
|
- ax's snapshot bucket must exist in your S3 server. When it did not, deleted golden actors piled
|
|
up in `DELETING` and filled the disk during a 600-task harvest on 2026-10-01.
|
|
|
|
Deployment status, in the two words [Deployment](deployment.md) uses strictly. Implemented:
|
|
ax as the only executor, per-task EgressPolicy, credentials only in the start request, per-task
|
|
budgets and attribution, the S3 store verified at startup, local bring-up, doctor and smoke Run,
|
|
re-pinning generated Tasks to another registry. Planned, not done: deployment to any cluster
|
|
other than this host's kind cluster, a configurable kube context (`kind-` is hard-coded in
|
|
`doctor.py`, `egress.py`, `router.py`), the receiver as an in-cluster Deployment, the
|
|
reconciler in-cluster with Secrets injected, presigned-URL delivery to the store.
|
|
|
|
## 4. Token economics, before and after
|
|
|
|
Before the plane, the script pipeline cost $0.022 per candidate for two verify passes
|
|
(`benchmarks/independent/verifier-batched-check-2026-09-29.json`), and the maintainers' own
|
|
coding sessions spent about 90% of their tokens on tool input and output rather than reasoning.
|
|
After it, the per-candidate costs below are what the records show. The mechanisms (one budget
|
|
per Task, pay-once reuse, cheap-before-expensive ordering, replay at $0) are on
|
|
[Token ergonomics](token-ergonomics.md); this table is the before and after.
|
|
|
|
| Item | Before | After | Record |
|
|
| --- | --- | --- | --- |
|
|
| Verification, two passes per candidate | $0.022 (script pipeline) | $0.0205 over 46 candidates with the recorded triage, including the tie-break pass on 10 disputed; 2,385,152 of 3,820,395 prompt tokens were cache hits in increment 1 (62%) | `verifier-batched-check-2026-09-29.json`, `plane-increment-1.json`, `plane-increment-2.json` |
|
|
| Decision per candidate (classifier, k = 5) | none existed | $0.0032 per evaluated candidate on the injection slice; $0.0027 to $0.0032 across six families | `2026-10-01-decision-engine-injection-slice/record.json`, `2026-10-02-decision-engine-slice-2/record.json` |
|
|
| An A/B arm, per candidate | none existed | $0.0021 (temperature sampling) against $0.0051 (retrieval ensemble); the cheaper arm won | `benchmarks/experiments/exp-002-retrieval-ensemble/result.json` |
|
|
| Harvest of plane signals for the engine | none existed | `verify` $2.5373 over 1,961 calls and 474 tasks; `roles` $1.4036 over 1,034 calls and 151 tasks | `2026-10-01-decision-engine-harvest/record.json` |
|
|
| Development transcript, tool I/O share | 90% | 85.5% over 2,701 tool calls (`dev_token_report`) | `plane-increment-1.json` |
|
|
|
|
Two notes from the records. The meter prices every call at the Model's list price
|
|
(`plane/models/deepseek-flash.yaml`); the DeepSeek account moved less, about one third of the
|
|
metered amount on the injection slice ($0.30 for $0.91 metered), and budgets and gates are set
|
|
against the meter, never against the balance. And the development share barely moved: 90 to 85.5
|
|
is a small gain, and the remedies (a read guard, a bulk reader, scripts in files) target file
|
|
contents because that is where the tokens go.
|
|
|
|
## 5. Open items
|
|
|
|
Five groups, each item with its record: the engine's own gates, data, measurement debt,
|
|
infrastructure, and instrument gaps.
|
|
|
|
The engine's gates.
|
|
|
|
- BLOCK is reachable for no family: calibration fails for five (ECE 0.071 to 0.119) and the
|
|
sixth reaches no fold with a Wilson lower bound of 0.95 on precision (slice-2 record).
|
|
- Re-run agreement is below 0.9 for three families: injection 0.77, access control 0.83,
|
|
untrusted destination 0.87. exp-002 tried the obvious variance fix and lost on the ranking.
|
|
|
|
Data.
|
|
|
|
- Positives-only groups: 98 (family, group) pairs in the 904-example memory hold positive examples
|
|
and no negative. 94 of them exist because the fixed side no longer has the function (the vibe-py
|
|
pairs). No advisory-fixes group is among them
|
|
(`benchmarks/measurements/2026-10-02-decision-engine-negatives-gap/record.json`). There are no
|
|
single-label negatives, so every family's pooled AUC is inflated by positives that never had to
|
|
be told apart from their own fix (slice-2 record, `paired_groups_check`).
|
|
- The model has seen public fixes. A verify pass named the advisory's CVE id for a public fix, an
|
|
observation the engine's design specification (maintainer tooling) records as visible
|
|
memorisation. Excerpts now redact advisory ids and security-worded comments
|
|
(`src/openultrasast/learn/excerpt.py`), but labels drawn from public advisories are partly in the
|
|
model's training data, and the within-pair numbers carry that.
|
|
- PHP is covered by the engine on real code and by in-sample quick rules; the population that
|
|
would measure it (v3) is reserved.
|
|
|
|
Measurement debt.
|
|
|
|
- exp-001 (known callers) is registered and has not run; the model-free candidate generation on
|
|
the 41 development repositories that its units need is not built
|
|
(`benchmarks/experiments/exp-001-known-callers/registration.json`).
|
|
- Leave-one-source-out, leave-one-framework-out and the memory-size curve points (0, 25, 50%)
|
|
have not run; one paired point exists (724 against 904 examples, injection, AUC 0.76 against 0.77).
|
|
- The plane-profile leak audit flagged `verify.flag_b` and `ms.operation`; they are withheld from
|
|
profile v1, and whether they track the label on unseen code has to be decided before any
|
|
plane-profile program runs (`2026-10-02-decision-engine-harvest-advisory/record.json`).
|
|
- Population v3 waits for `benchmarks/independent/prediction-v3.json`, which does not exist. It is
|
|
written only after a program is adopted.
|
|
|
|
Infrastructure.
|
|
|
|
- Deployment to a separate Kubernetes cluster is planned, not done; the code changes it needs are
|
|
listed with file paths on [Deployment](deployment.md#what-is-implemented-and-what-is-planned).
|
|
|
|
Instrument gaps.
|
|
|
|
- A missing provider key is a silent path in a scan. The plane refuses to start a Task whose Model
|
|
names an unset variable (`plane/router.py`, `StartError`), but a scan without a key asks the
|
|
model nothing, reports only the graph's entailed findings, and records no degradation for the
|
|
skipped model layer (`cli.py` writes degradations for `hunter_model_unavailable`,
|
|
`cpg_unavailable` and `learning_endpoint_unavailable` only). The rule that a scan which could
|
|
not look must say so does not yet cover this case.
|