Files
Open_Ultra_SAST/.kiro/specs/propose-adjudicate-prove/research.md
T
Marius Ciepluch 1ae41eea19 feat(propose-adjudicate-prove): overlay adjudicates inventory before prove
Regex inventory stays the proposer. MAP attaches promote/demote/unadjudicated/coverage
from intra-file taint (tree-sitter when a grammar parses, Python ast as last resort).
Deep sandboxes promotions only, ordered by the existing heuristic ranker plus optional
OpenRouter mechanism recall. The SAST pair slice scores overlay outcomes; the stage-1
detection gate and local pair gate stay inventory-only.
2026-09-04 13:57:33 +02:00

7.6 KiB
Raw Blame History

Research & Design Decisions

Summary

  • Feature: propose-adjudicate-prove
  • Discovery Scope: Extension of three-stage-scan (MAP grows an overlay; STATIC and REGRESS stay)
  • Key Findings:
    • Inventory already decides; MAP ranks; REGRESS can run. Nothing adjudicates flow.
    • Labeled SAST slice Youden is about +9%. Leaks are OWASP-false / Juliet goodG2B that still contain the sink. Misses are one-hop assignments and sink aliases.
    • Clearwing already does the split we want: tree-sitter callgraph + pattern taint tables, then ranker (surface/influence/reachability 0.5/0.2/0.3), then mechanism-memory embeddings. OpenUltraSAST already copied the rank formula and has json-local vectors; they are not wired as prove-budget.
  • Tree-sitter CLI/API covers C/C++/Python/JS/Go/Rust (Clearwing grammars); Joern is the CPG sidecar when present. Stdlib ast is last-resort Python only. LLM hunter remains suspicion-only.
    • Real working trees will often fail parse or cross a file boundary. Those cases must stay unadjudicated, not demoted.

Research Log

Labeled vs unlabeled code

  • Context: Operator: known-vuln corpora are “nice”; written code will not be.
  • Sources Consulted: In-tree LANGUAGE_MANIFESTS, benchmarks/pairs/, OWASP Benchmark true/false files, Juliet bad/goodG2B/goodB2G, CWE-Bench-Java catalog.
  • Findings:
    • Cheat-sheets: one CWE, named sink, often same-line source.
    • OWASP: true and false files both often contain exec / eval; the difference is source constancy or infeasible branches.
    • Juliet goodG2B: hardcoded source, same sink.
    • Juliet goodB2G: same source, sanitized sink (printf("%s")).
    • Working trees: syntax errors, generated bundles, wrappers (helpers.db_sqlite.execute), no labels.
  • Implications: Overlay scored on sast slice; unlabeled scans must not pretend demote. Unadjudicated is a first-class disposition.

Engine choice

  • Context: Operator prefers tree-sitter + Joern look, embeddings before prove, still v1-focused.
  • Sources Consulted: clearwing/sourcehunt/callgraph.py, taint.py, ranker.py, mechanism_memory.py; openultrasast/rank.py, vectorstore.py, index.py.
  • Findings:
    • Clearwing tree-sitter is optional: missing grammar disables callgraph, does not crash.
    • Clearwing taint is intra-procedural, pattern tables for sources/sinks — same shape as our fact TOML.
    • Clearwing ranker fills reachability from the callgraph; taint_paths boost surface. Embeddings retrieve mechanisms, they do not decide taint.
    • Our heuristic_rank already uses surface·0.5 + influence·0.2 + reachability·0.3. Vector store bakeoff selected json-local. VALID_NAMESPACES already includes mechanisms but nothing writes Clearwing-style records into it.

Clearwing vector-store reference (real-world bugs)

  • Context: Operator: Clearwing’s store is effective on real bugs, not textbook files.
  • Sources Consulted: clearwing/sourcehunt/mechanism_memory.py, data/memory/semantic_memory.py, docs/architecture.md steps 7–8; openultrasast/index.py namespaces.
  • Findings:
    • Store mechanisms, not findings and not 80-line file windows. Example: “length field trusted before alloc; 16-bit value widened to size_t.”
    • JSONL is the source of truth (~/.clearwing/sourcehunt/mechanisms.jsonl). Chroma/sqlite embeddings are ephemeral caches rebuilt from JSONL.
    • Write path is verified findings only (after adversarial verify / crash). Suspicion never seeds the store.
    • Clearwing’s JSONL + verified-write + metadata filters are the reference. Its TF-IDF default is not: bag-of-words will not match paraphrased real bugs (length field trusted before alloc vs user-controlled size_t in malloc).
    • OpenUltraSAST already has OpenRouterEmbeddingClient and json-local cosine. Use that as the only semantic ranker; CWE/language/tags are hard filters. No TF-IDF, no Chroma, no local MiniLM.
    • Query is language + file tags + optional free text; same-language boost +0.5. Top-N injects hunter context and variant-grep seeds — it does not decide taint.
    • Variant loop then greps the messy tree for structural matches of that mechanism. That is how it leaves Juliet behind.
  • Implications: Copy JSONL + verified-write + metadata filters. Rank with OpenRouter embeddings only. Do not copy TF-IDF. Do not embed 80-line file windows as the prove store. Seed from sandbox-proven mechanisms, not OWASP BenchmarkTest00007.
    • Joern CLI (joern-parse / scripts) is the only practical CPG without a Scala runtime in-process.
  • Implications: Probe tree-sitter like Docker. Joern sidecar cannot demote. Embeddings order prove only.

Decision: Python intra-file first

  • Context: Zero-dep core vs multi-language trees.
  • Selected: Superseded. Tree-sitter is the prio engine; stdlib ast is Python last resort; quick stays zero-dep.
  • Rationale: Operator working trees are mixed-language; Python-only adjudication would leave JS/C/Java as perpetual unadjudicated.
  • Trade-offs: standard needs a grammar or CLI on PATH; missing tools → unadjudicated, not skip the scan.

Evidence ladder

  • Context: Three-stage-scan forbids hunter JSON as corroboration.
  • Sources Consulted: verification.EvidenceLevel, tool_hunter suspicion rule, worth_fixing gate.
  • Findings: Overlay can justify promote at static_corroboration. It cannot skip to crash or exploit. Worth-fixing stays sandbox + reachability.
  • Implications: Disposition is not an evidence rung. Do not add a seventh ladder value.

Architecture Pattern Evaluation

Option Description Strengths Risks Notes
Replace regex with CodeQL External engine is the detector Real dataflow Breaks zero-dep, smoke gate, ledger shadows Rejected
LLM hunter as adjudicator Model reads file and votes Flexible on messy code Unstable Youden; violates suspicion rule Rejected
Overlay on inventory Regex proposes; tree-sitter taint adjudicates; embed ranks prove Fits stages; multi-lang Parser must be probed Selected
Whole-program taint v1 Inter-file Closes wrappers Too large; false demote risk Defer; stay unadjudicated

Design Decisions

Decision: Unadjudicated is not demote

  • Context: Messy files and cross-file flow.
  • Alternatives: Silent drop; treat as safe; force demote.
  • Selected: Explicit unadjudicated with reason.
  • Rationale: Operator writing code must not read silence as “clean.”
  • Trade-offs: Reports stay noisy until languages and inter-file land.
  • Follow-up: Count unadjudicated rate on unlabeled trees as an honesty metric.

Decision: Score overlay on sast slice, inventory on smoke gate

  • Context: +9% Youden vs 93% cheat-sheet recall.
  • Selected: Two gates stay separate; overlay Youden cannot unblock smoke failure.
  • Rationale: Same as detection-gate vs score in the improve loop.

Risks & Mitigations

  • False demote on real bugs (infeasible-branch OWASP tricks, wrappers) — never demote when flow is incomplete; only demote when the engine has a dominating constant or sanitizer.
  • Youden chase via extra sinks — each coverage rule needs a false/good counterpart that must stay demoted or silent.
  • Parse failures on operator WIP — unadjudicated, quick still works.

References

  • .kiro/specs/three-stage-scan/design.md — stage plan, sandbox, hunter suspicion
  • benchmarks/pairs/sast/README.md — OWASP/Juliet slice
  • NIST Juliet goodG2B vs goodB2G templates
  • OWASP Benchmark expectedresults true/false protocol