Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
7.5 KiB
Scanning
This page shows the scan flows.
ousast scan PATH --mode quick|standard|deepscans a checkout.ousast pre-pushchecks only what a push changes, or what an explicit--base/--headpair changes.
The stage-by-stage pipeline, the evidence ladder and the score are in Architecture.
The three scan modes
The stages each mode requests are listed in MODE_STAGES in src/openultrasast/stages.py:
quickruns STATIC.standardadds MAP.deepadds REGRESS.
flowchart TD
start["ousast scan PATH --mode M"] --> policy["policy_load, ruleset_load, policy_check"]
policy --> prep["static_mapping, preprocess, entry_point_mapping, rank, calibrate"]
prep --> which{"mode"}
which -- "quick, deep" --> qf["quick_findings: language-scoped pattern rules"]
which -- "standard" --> hp["hunter_pool: the same rules per ranked target"]
qf --> verify["verify, record_calibration, score"]
hp --> verify
verify --> mapq{"MAP requested?"}
mapq -- "quick: no" --> out["report.md, report.sarif, manifest.json"]
mapq -- "standard, deep" --> map["MAP: complexity map, semantic overlay, obligations, Joern model layer"]
map --> regq{"REGRESS requested?"}
regq -- "standard: no" --> out
regq -- "deep" --> reg["REGRESS: snippets in a Docker sandbox, verdicts.json"]
reg --> out
| Mode | Adds | Needs | Without it |
|---|---|---|---|
quick |
pattern rules, entry-point hints, ranking, verification, score | nothing | - |
standard |
MAP: complexity map, semantic overlay, authorization obligations, the Joern model layer; fusion (deterministic) |
Joern; a provider key for the residual LLM question | cpg_unavailable degradation; the LLM's questions are skipped and only the graph's entailed findings are reported |
deep |
REGRESS: promoted candidates loaded in a sandbox with no network, read-only source, non-root | a working docker |
sandbox_unavailable degradation |
Sources: README.md (scan modes), src/openultrasast/cli.py (run_stage calls),
Architecture.
Quick rules per language
Each rule runs only against files of its language. The bundled ruleset lives in
src/openultrasast/ruleset/<language>/rules.toml.
| Language | Classes (CWE) |
|---|---|
| C / C++ | stack buffer overflow (121), format string (134), command injection (78) |
| Python | command injection (78), code injection (95), SSTI (94), deserialization (502), SQLi (89), path traversal (22), SSRF (918), weak hash (327), insecure default (489) |
| JavaScript / TS | command injection (78), code injection (95), XSS (79), SQLi (89), path traversal (22), SSRF (918), weak hash (327), deserialization (502) |
| Java + Groovy templates | command injection (78), SQLi (89), weak hash (327), deserialization (502), unescaped template XSS (79) |
| PHP | SQLi (89), command injection (78), file inclusion (98), path (22), open redirect (601), SSRF (918); code eval (95), unserialize (502) and echo XSS (79) ship as shadow rules |
The detection gate (python -m openultrasast.gate) enforces two limits per language on the
bundled corpora in benchmarks/manifests/: at least 90% recall and under 10% false positives.
- Those corpora are cheat-sheet fixtures. They catch regressions. They do not estimate recall on real code.
- PHP is not in the gate. Its numbers are in-sample development numbers
(
benchmarks/measurements/2026-09-30-php-quick-rules/measurement.json).
See README.md for the gate's per-language output.
The Joern engine
The model layer (src/openultrasast/model/, src/openultrasast/cpg/) is the core of standard.
It builds one code property graph per scan with Joern. A code property graph is a graph of the
program's syntax, control flow and data flow. The build runs joern-parse, then batched
joern --script queries from cpg/queries/*.sc.
The layer sends each question to the arbiter (the decider) of its type:
- taint: a flow from source to sink with no discharging sanitizer;
- dominance: does a guard govern the obligated operation;
- configuration: constant abstraction over a value.
Taint facts ship for C, Java, JavaScript, PHP and Python
(src/openultrasast/ruleset/semantic/*.toml).
flowchart LR
src["Checkout"] --> parse["joern-parse: one CPG per repository"]
parse --> q["Batched joern --script queries"]
q --> arb{"Arbiter by question type"}
arb --> taint["Taint: flow with no discharging sanitizer"]
arb --> dom["Dominance: obligation with no guard"]
arb --> conf["Configuration: permissive value"]
taint --> rung["Rung: model_entailed or suspicion"]
dom --> rung
conf --> rung
rung -- "residual question only" --> judge["LLM judge: confirm or contradict, never create a flow"]
The Docker image ships Joern and php-cli. The PHP frontend needs php-cli. Without Joern, the
layer records cpg_unavailable; the rest of the scan is unchanged.
- Engine installation and the Joern features used: Engine and pre-push.
- How the graph is built, queried and refined, with the records behind each refinement: Detection techniques.
Framework priors
Some rule and taint-fact entries know a framework or library. They carry a framework or
library tag. The tag names a row of src/openultrasast/ruleset/frameworks.toml (for example
wordpress, flask, django, express, spring).
The loaders take priors= with one of three values:
"all": the default and today's behaviour;"off": language-level knowledge only;- a set of ids.
So the contribution of framework knowledge can be measured and switched off. The decision engine's first slice ran with priors off (Decision engine).
Pre-push: the delta check (experimental)
ousast pre-push analyses the commits a git push would publish, or an explicit
--base/--head pair. It compares head with base. Only new or worsened defects become
candidates. Sources: ousast pre-push --help, src/openultrasast/push/,
Engine and pre-push.
flowchart TD
input["git push refs on stdin, or --base / --head"] --> resolve["Resolve both commit identities"]
resolve --> snap["Materialise tracked bytes in private scratch storage"]
snap --> regions["Discover changed regions, up to --max-regions"]
regions --> engine["Evidence ranker and arbiter on head"]
engine --> cmp["Targeted base comparison: new or worsened only"]
cmp --> admit{"Admission policy: capability registry"}
admit -- "registry empty today" --> diag["Diagnostic candidates in the artifact"]
admit -- "an enabled capability" --> alert["Normal alert"]
diag --> art["JSON artifact outside the repository"]
alert --> art
art --> exit{"--mode"}
exit -- "advisory, the default" --> allow["Exit 0: allow"]
exit -- "blocking, explicit opt-in" --> block["May block the push"]
--deadline(30 s by default) bounds preparation, both revisions and reporting together.--incomplete-coverage blockopts into failing when coverage is incomplete.- The default capability registry is empty until independent qualification passes. So no normal alert is emitted today. Every candidate stays diagnostic.
- A model is called only with an explicit
--model-config. - Hook installation (
ops/install-pre-push) and the private cache (--cache-dir) are documented in Engine and pre-push.