mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-20 21:49:28 +00:00
86 lines
7.2 KiB
Markdown
86 lines
7.2 KiB
Markdown
# Research & Design Decisions
|
|||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
- **Feature**: `corpus-seeded-mechanisms`
|
||
|
|
- **Discovery Scope**: Extension of mechanism memory, MAP stage, pair eval, and the improve loop
|
||
|
|
- **Key Findings**:
|
||
|
|
- `semantic/mechanisms.py` already has `Mechanism`, `MechanismStore` (append-only JSONL), `append_mechanism`, embedding cache, hard filters by language/CWE/tags, and `order_promotions`. It is written only by `cli._append_proven_mechanisms` after a sandbox trigger, so it is empty on every real tree.
|
||
|
|
- Clearwing's recall lever is the variant loop: a proven mechanism becomes a structural search over the messy tree. OpenUltraSAST copied the store and the retrieval, not the loop.
|
||
|
|
- `FileIR` call sites already carry callee name, argument texts, argument identifier lists, and constant flags; binds carry source text and names. That is enough for a shape without new parsing.
|
||
|
|
- The pair scorer (`pair-corpus-honesty`) gives labeled function, mechanism id, provenance, and soon a review tier; the fixed excerpt gives the guard the fix added by diffing statement kinds.
|
||
|
|
- The improve loop has bounded levers with a validator, journal, byte-for-byte revert, and now a per-profile holdout clause; a `mechanisms` lever fits without changing the gate.
|
||
|
|
- Measured 2026-09-05: vibe-py 13/35, vfc-js 1/16, sast 4/10 achievable. Web classes repeat structurally (request-derived string into `exec`/`execute`, identity from the body, `os.path.join` with a parameter, permissive defaults), which is what a shape search catches.
|
||
|
|
|
||
|
|
## Research Log
|
||
|
|
|
||
|
|
### Mechanism store today
|
||
|
|
|
||
|
|
- **Context**: Can the existing store carry corpus-derived records?
|
||
|
|
- **Sources Consulted**: `semantic/mechanisms.py`, `cli.py` `_append_proven_mechanisms`, `propose-adjudicate-prove` design §Mechanism.
|
||
|
|
- **Findings**: `Mechanism` fields are summary, cwe, language, tags, keywords, what_made_it_exploitable, source_finding_id, source_repo. Identity is the pattern. JSONL is authoritative; vectors are a rebuildable cache keyed by id and model.
|
||
|
|
- **Implications**: add `origin` (`sandbox` | `corpus`), `review_tier`, `pairs`, `shape` (structured), `guard` as additive fields with defaults so old rows load. A second writer (`append_from_pair`) beside the sandbox writer.
|
||
|
|
|
||
|
|
### Shape derivation from FileIR
|
||
|
|
|
||
|
|
- **Context**: Avoid regex and LLM authoring.
|
||
|
|
- **Sources Consulted**: `semantic/ir.py` (`CallSite.arg_names`, `arg_is_constant`, `Bind.names`, `FunctionIR.params`), `semantic/taint.py` (`_source_in`, parameter seeding).
|
||
|
|
- **Findings**: for the labeled function, the sink call site is the call whose trailing name equals the label's sink or whose line equals the label's line; source positions are argument indexes whose identifiers trace by binds to a fact source, a parameter, or a container read. Guard kind comes from statements on the fixed side absent on the vulnerable side: `if` with `== NULL`/`is None`/`!x` → `null_test`; comparison with `<`, `>`, `len(` → `bounds_test`; membership in a literal collection or `startswith`/regex → `allowlist_test`; identity read from an auth context (`request.state`, `req.user`, `session`) → `auth_check`; parameterized second argument → `parameterized_call`; changed declared type → `type_change`.
|
||
|
|
- **Implications**: `semantic/variants.py` implements `derive_shape(pair_ir_vuln, pair_ir_fixed, label)` and `match_shape(file_ir, shape)`. Guard classification is heuristic and closed; unknown → `none`.
|
||
|
|
|
||
|
|
### Where variant search sits
|
||
|
|
|
||
|
|
- **Context**: Propose, adjudicate, prove roles must stay separate.
|
||
|
|
- **Sources Consulted**: `three-stage-scan` design (MAP stage, hunter suspicion), `propose-adjudicate-prove` (overlay records, coverage), `regress/candidate.py`.
|
||
|
|
- **Findings**: MAP already runs overlay and the tool hunter; variant search is a third proposer that reads `FileIR` and the store. Findings at `suspicion`, origin `variant`; when the overlay has a flow at that call site, merge (attach mechanism id to the overlay record). Candidate eligibility follows the hunter-eligibility path being added in `reachability-flow-model` or the sev-5 forced-hotspot path today.
|
||
|
|
- **Implications**: no disposition change; one new finding origin; manifest counters.
|
||
|
|
|
||
|
|
### Leave-one-out
|
||
|
|
|
||
|
|
- **Context**: Prove that corpus growth raises recall, honestly.
|
||
|
|
- **Findings**: pair eval materializes each side into a temp tree already; a leave-one-out runner seeds a temp store from the other trusted pairs and runs variant search on both sides, scoring with the pair scorer's detection and leak rules.
|
||
|
|
- **Implications**: `pairs.py` gains a read-only consumer path (`evaluate_loo`) or a sibling module `semantic/loo.py` to keep the scorer's boundary; report per slice, profile, mechanism; artifact only.
|
||
|
|
|
||
|
|
## Architecture Pattern Evaluation
|
||
|
|
|
||
|
|
| Option | Description | Strengths | Risks / Limitations | Notes |
|
||
|
|
|--------|-------------|-----------|---------------------|-------|
|
||
|
|
| Embedding retrieval only | Rank existing promotions by mechanism similarity | Already exists | Cannot propose anything new; store empty | Rejected as recall lever |
|
||
|
|
| LLM writes Semgrep rules from pairs | Model authors patterns | Flexible | Unbounded pattern text, unverifiable, violates no-model-authored-patterns | Rejected |
|
||
|
|
| Structural shapes from FileIR plus variant search | Closed shape vocabulary, deterministic match | Zero-dep, bounded, testable leave-one-out | Shapes coarse; guard heuristics | **Selected** |
|
||
|
|
| Fine-tuned classifier on pairs | ML detector | Learns anything | Opaque, no fix guidance, data too small | Rejected |
|
||
|
|
|
||
|
|
## Design Decisions
|
||
|
|
|
||
|
|
### Decision: Seed only from seeded and reviewed tiers
|
||
|
|
|
||
|
|
- **Context**: `title` pairs are unreviewed guesses; `advisory` pairs have correct fixes but unlabeled mechanisms.
|
||
|
|
- **Selected Approach**: export reads `review_tier`; `advisory`/`title` may be scored leave-one-out as held-out targets but never seed.
|
||
|
|
- **Trade-offs**: fewer seeds at first; honest about what the detector was taught.
|
||
|
|
|
||
|
|
### Decision: Variant is a proposer at suspicion
|
||
|
|
|
||
|
|
- **Context**: Evidence honesty.
|
||
|
|
- **Selected Approach**: origin `variant`, `suspicion`; merge with overlay flow when present; sandbox may raise.
|
||
|
|
- **Trade-offs**: variant-only hits do not fail CI; correct.
|
||
|
|
|
||
|
|
### Decision: Lever admits records, not shapes
|
||
|
|
|
||
|
|
- **Context**: Keep the validator closed.
|
||
|
|
- **Selected Approach**: `mechanisms` lever edits reference record ids produced by the exporter; free-form shapes are rejected.
|
||
|
|
|
||
|
|
## Risks & Mitigations
|
||
|
|
|
||
|
|
- Coarse shapes over-match (`exec(x)` everywhere) — source-position requirement plus fact/parameter/container check; report leak rate on fixed sides leave-one-out; the per-profile gate rejects admissions that leak.
|
||
|
|
- Guard heuristics misclassify — closed set with `none`; reported, not used for demotion.
|
||
|
|
- Store growth — dedupe by shape; one record per shape with pair provenance list.
|
||
|
|
- Leave-one-out cost — one temp store per pair over tens of pairs; fine; cache parsed IR per excerpt.
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- `semantic/mechanisms.py`, `semantic/ir.py`, `semantic/taint.py`
|
||
|
|
- `.kiro/specs/propose-adjudicate-prove/design.md` §Mechanism (Clearwing reference architecture)
|
||
|
|
- `.kiro/specs/pair-corpus-honesty/` (review tiers, pointer slices, scorer rules)
|
||
|
|
- `.kiro/specs/harnessx-self-improving-rulesets/design.md` (bounded levers, accept gate)
|
||
|
|
- Clearwing sourcehunt mechanism memory and variant loop (reference, not a dependency)
|