mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(repos): pinned known-vulnerable checkouts, offline by default
The pair corpus scores excerpts: a vulnerable function and its fixed twin, handed to the engine with the answer already narrowed down. Nothing in it exercises a repository -- region ranking, the budget, one CPG over thousands of files, or whether a verdict that holds on fifteen lines still holds when the source, the sink and the guard live in three modules. benchmarks/repos/<name>.toml names a real checkout: url, a full 40-char commit SHA, licence, and every known vulnerability with its file, function and line. `ousast repos` lists them; `--fetch` materialises one; CI does neither, and conftest clears the switch and redirects the cache so a developer with warm checkouts cannot read them by accident. Three rules the loader enforces rather than documents: - A branch or tag is rejected. Either would make the same recipe mean different code next month while the baseline it produced kept the old numbers. - `in_scope` must be stated per vulnerability, never defaulted. It decides whether "not found" is a miss or a non-question, which is the most important thing about the result. - A recipe that declares `detection` must name something in scope to detect. `ousast repos` does not trust a recipe either: against a materialised checkout it checks HEAD against the pin, that the licence file is there, and that each declared function appears above its declared line. That earned its place immediately -- the first libpng recipe named LICENSE.md, which is the filename at HEAD today and not at the pinned commit, where it is still LICENSE. Two checkouts, verified against the real repositories: - vampi (MIT, 520 lines) is the DETECTION checkout. Excerpts of it are already in the pair corpus, including the update_password miss diagnosed earlier, so a whole-repository run says directly whether a verdict survives the move off the excerpt. Its routes register through an OpenAPI operationId, so it exercises registration-based entry-point mapping rather than the decorator path. - libpng (libpng-2.0, 91k lines of C) is the ENVELOPE checkout, pinned at the parent of the commit that added the missing clamp. Its CVE is marked out of scope: an out-of-bounds read is not a family this engine arbitrates, and recording "not found" against it as a miss is the easiest way to make a measurement lie. Nothing is vendored; the recipe records the licence so an artifact can say what it ran against. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
co-authored by
Claude Opus 5
parent
c35426e186
commit
8c387306be
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"counts": {
|
||||
"load_bearing": 88,
|
||||
"load_bearing": 89,
|
||||
"standalone_capability": 7
|
||||
},
|
||||
"generated": "2026-09-09 (regenerated after contributor-scan group 1 added regions and the repo driver)",
|
||||
"generated": "2026-09-09 (regenerated after contributor-scan 2.1 added the pinned-checkout recipes)",
|
||||
"kept_with_a_lever_stripped": {
|
||||
"improve/evolve.py + improve/validator.py": "the design's Removed list named these whole, but each is a mix: the mechanism lever (removed) and the deterministic rule-status improve loop that `ousast improve` still runs from benchmark evidence, which has no LLM in it and is not what this feature supersedes. Deleting the files would have taken a retained capability with them."
|
||||
},
|
||||
"modules": 95,
|
||||
"modules": 96,
|
||||
"note": "Classification of every source module after the group-1 subtraction. load_bearing = reachable from a gate, the CLI, or named by a surviving requirement; standalone_capability = its own entry point or protocol, deliberately off the scan path; orphaned = no importer, no entry point, no surviving requirement -- removed, never carried.",
|
||||
"orphaned": [],
|
||||
"removed_in_this_pass": {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Pinned known-vulnerable checkouts
|
||||
|
||||
The pair corpus scores excerpts: a vulnerable function and its fixed twin, fifteen lines each, handed to the
|
||||
engine with the answer already narrowed down. That measures arbitration. It cannot measure anything about a
|
||||
repository — region ranking, the budget, one CPG over thousands of files, or whether a verdict that holds on
|
||||
an excerpt still holds when the source, the sink and the guard live in three different modules.
|
||||
|
||||
These recipes name real checkouts so those questions can be measured instead of assumed.
|
||||
|
||||
## What a recipe is
|
||||
|
||||
One TOML file per repository, naming a url, a **full 40-character commit SHA**, the licence, and every known
|
||||
vulnerability with the file, function and line it lives at.
|
||||
|
||||
```
|
||||
ousast repos # what is pinned, and whether it is checked out
|
||||
ousast repos --repo vampi --fetch # materialise one (network; CI never does this)
|
||||
ousast repos --json # the same, for an artifact
|
||||
```
|
||||
|
||||
Nothing here is vendored. The repositories stay in their own projects under their own licences; the recipe
|
||||
records the licence so a measurement artifact can say what it ran against.
|
||||
|
||||
## Three rules, and why
|
||||
|
||||
**Offline by default.** A recipe is inert until someone asks for the network — `--fetch` for one run, or
|
||||
`OPENULTRASAST_REPOS_NETWORK=1`. CI sets neither, and the test suite clears both. This is the same rule the
|
||||
pointer pairs follow, for the same reason: a test suite that quietly reaches the network is a test suite that
|
||||
fails for reasons that have nothing to do with the code.
|
||||
|
||||
**Pinned, not tracked.** `commit` must be a full SHA; a branch or a tag is rejected at load. A tag gets
|
||||
re-cut and a branch moves every week, and either would make the same recipe mean different code next month
|
||||
while the baseline it produced still carried the old numbers.
|
||||
|
||||
**A recipe declares what it can measure.** `measures` is `detection`, `envelope`, or both, and every known
|
||||
vulnerability states `in_scope` explicitly — whether it belongs to a family this engine arbitrates at all.
|
||||
There is no default, because the default would silently decide the most important question about the result:
|
||||
|
||||
> An out-of-bounds read in a C library is a fine envelope test and an impossible detection test. Recording
|
||||
> "not found" against it as a miss is the easiest way to make a measurement lie.
|
||||
|
||||
## Verification
|
||||
|
||||
`ousast repos` does not trust a recipe. Against a materialised checkout it checks that HEAD is the pinned
|
||||
commit, that the licence file is really there, and that each declared function appears above its declared
|
||||
line in its declared file. A recipe that contradicts the code exits non-zero; a recipe that simply has not
|
||||
been fetched does not, or CI could never run this command.
|
||||
|
||||
That check earned its place immediately: the first libpng recipe named `LICENSE.md`, which is the filename at
|
||||
HEAD today and not at the pinned commit, where it is still `LICENSE`.
|
||||
|
||||
## What is pinned
|
||||
|
||||
| repo | language | size | measures | why |
|
||||
|---|---|---|---|---|
|
||||
| `vampi` | python | 520 lines, 11 files | detection | Small enough that a detection result is not confounded by budget or ranking. Its routes are registered through an OpenAPI `operationId` rather than decorators, so it exercises registration-based entry-point mapping. Excerpts of it are already in the pair corpus, so a whole-repository run says directly whether a verdict survives the move off the excerpt. |
|
||||
| `libpng` | c | 91k lines, 101 files | envelope | Whether one CPG over a real C library is affordable at all: build time, query time, peak memory, and whether the ranker yields a sane number of regions. Its CVE is out of scope by construction. |
|
||||
|
||||
## Baselines
|
||||
|
||||
Task 6.1 turns a scan of a pinned checkout into a committed baseline, so a change to the fact tables or the
|
||||
queries can be read as findings gained, findings lost and rung movements (Req 10.1–10.4). Until then these
|
||||
recipes exist to be measured against by hand, and the first such measurement is task 2.2.
|
||||
@@ -0,0 +1,34 @@
|
||||
# libpng -- 91k lines of C across 101 files.
|
||||
#
|
||||
# This is the ENVELOPE checkout, and the only question it answers is whether one CPG over a real C library
|
||||
# is affordable: build time, query time, peak memory, and whether the region ranker produces a sane number
|
||||
# of regions rather than thousands.
|
||||
#
|
||||
# Its known CVE is deliberately marked out of scope. An out-of-bounds read is not a family this engine
|
||||
# arbitrates -- there is no taint sanitizer lattice, guard dominance or constant abstraction that decides
|
||||
# it -- so "not found" here is not a miss, and recording it as one would be the easiest way to make a
|
||||
# measurement lie.
|
||||
|
||||
name = "libpng"
|
||||
url = "https://github.com/glennrp/libpng.git"
|
||||
commit = "cf53e54ccf631d8de68a30ebb3b8eeed1d6f1a08"
|
||||
language = "c"
|
||||
license = "libpng-2.0"
|
||||
license_file = "LICENSE"
|
||||
measures = ["envelope"]
|
||||
note = """
|
||||
The pinned commit is the parent of 788a624d7387a758ffd5c7ab010f1870dea753a1, the commit that added the
|
||||
missing clamp; `git describe` calls it v1.6.51-1-gcf53e54cc. The licence file is `LICENSE` at this commit; it was renamed
|
||||
to `LICENSE.md` later, which is why the recipe names the file and the verifier checks it. Nothing is vendored from this repository -- the
|
||||
recipe fetches it on request and CI never does.
|
||||
"""
|
||||
|
||||
[[known]]
|
||||
id = "CVE-2025-66293"
|
||||
family = "memory_safety"
|
||||
weakness = "out-of-bounds read: an unclamped composited component indexes the sRGB conversion table"
|
||||
file = "pngread.c"
|
||||
function = "png_image_read_composite"
|
||||
line = 3279
|
||||
reference = "fixed in 788a624d7387a758ffd5c7ab010f1870dea753a1; the CVE is named in the code comment added by d1ec15266204deb4102d2b5e40216bb7637261c8"
|
||||
in_scope = false
|
||||
@@ -0,0 +1,47 @@
|
||||
# VAmPI -- a deliberately vulnerable Flask API, small enough to iterate on.
|
||||
#
|
||||
# This is the DETECTION checkout. The corpus already scores excerpts cut out of VAmPI, and one of them
|
||||
# (users/update_password) was diagnosed as a miss. Scanning the whole repository asks the question the
|
||||
# excerpts cannot: does a verdict survive when the route registration, the handler and the query live in
|
||||
# three different modules, and when the region ranker has to find the handler rather than being handed it?
|
||||
#
|
||||
# Its routes are registered through an OpenAPI `operationId`, not a decorator, so it also exercises the
|
||||
# registration-based entry-point mapping rather than the decorator path.
|
||||
|
||||
name = "vampi"
|
||||
url = "https://github.com/erev0s/VAmPI.git"
|
||||
commit = "f16052dce83f05847133ec98f01c5193a41de7d8"
|
||||
language = "python"
|
||||
license = "MIT"
|
||||
license_file = "LICENSE"
|
||||
measures = ["detection"]
|
||||
note = """
|
||||
520 lines across 11 Python files: too small to say anything about the envelope, and chosen for exactly that
|
||||
reason -- a detection result here is not confounded by budget exhaustion or region ranking.
|
||||
"""
|
||||
|
||||
# VAmPI serves the vulnerable code paths behind a global switch, `int(os.getenv('vulnerable', 1))` in app.py.
|
||||
# The default is already vulnerable; it is pinned here because a measurement that silently ran the patched
|
||||
# branch would report a clean repository and look like a detection failure.
|
||||
[environment]
|
||||
vulnerable = "1"
|
||||
|
||||
[[known]]
|
||||
id = "VAmPI-SQLI"
|
||||
family = "injection"
|
||||
weakness = "SQL injection: the username is interpolated into the query string at line 72 and executed at 73"
|
||||
file = "models/user_model.py"
|
||||
function = "get_user"
|
||||
line = 73
|
||||
reference = "README.md 'List of Vulnerabilities'; the vulnerable branch is guarded by `if vuln:`"
|
||||
in_scope = true
|
||||
|
||||
[[known]]
|
||||
id = "VAmPI-BOLA"
|
||||
family = "access_control"
|
||||
weakness = "unauthorized password change: the path username is used instead of the authenticated token subject"
|
||||
file = "api_views/users.py"
|
||||
function = "update_password"
|
||||
line = 187
|
||||
reference = "README.md 'Unauthorized Password Change'; the non-vulnerable branch uses resp['sub']"
|
||||
in_scope = true
|
||||
Reference in New Issue
Block a user