mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
feat(corpus-seeded-mechanisms): improve loop admits recovering mechanisms and retracts leakers
Task 4.2 (Req 5.3, 5.4, 5.5). - propose_mechanism_edits: retract admitted records that leak on a holdout fixed side; admit a candidate only when a trial store holding it alone recovers a currently missed holdout pair without leaking. - run_round(mechanism_candidates=, mechanism_store=): validates, applies after the rule gate passed, re-scores the holdout pairs per profile with variant search (evaluate_mechanism_profiles), and rejects with a byte-for-byte revert on mechanism_leak, profile_regression or mechanism_no_gain; the journal records each mechanism edit with its pair provenance and the metrics before and after. - The rule gate and its per-profile clause are unchanged. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
73e856a06b
commit
23aae79768
@@ -0,0 +1,161 @@
|
||||
"""corpus-seeded-mechanisms task 4.2: the improve loop admits mechanisms that recover holdout pairs and retracts leakers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from openultrasast.benchmark import ExpectedFinding, load_benchmark_manifest
|
||||
from openultrasast.pairs import PairCase
|
||||
from openultrasast.ruleset import PatternRule, write_ruleset
|
||||
from openultrasast.semantic.mechanisms import MechanismStore, append_from_pair
|
||||
from openultrasast.semantic.variants import Shape
|
||||
|
||||
EXEC_VULN = (
|
||||
"import sqlite3\n\n\ndef lookup(user):\n c = sqlite3.connect('x').cursor()\n"
|
||||
+ " c.execute('SELECT * FROM t WHERE u = ' + user)\n"
|
||||
)
|
||||
EXEC_FIX = (
|
||||
"import sqlite3\n\n\ndef lookup(user):\n c = sqlite3.connect('x').cursor()\n"
|
||||
+ " c.execute('SELECT * FROM t WHERE u = ?', (user,))\n"
|
||||
)
|
||||
SYS_VULN = "import os\n\n\ndef ping(host):\n os.system('ping ' + host)\n"
|
||||
SYS_LEAKY_FIX = (
|
||||
"import os\n\n\ndef ping(host):\n log(host)\n os.system('ping ' + host)\n" # the 'fix' still calls the sink: a leaking shape
|
||||
)
|
||||
|
||||
|
||||
def _shape(sink: str, guard: str = "none") -> Shape:
|
||||
return Shape(
|
||||
language="python",
|
||||
sink_name=sink,
|
||||
arity=1,
|
||||
source_positions=(0,),
|
||||
source_kinds=("parameter",),
|
||||
guard=guard,
|
||||
mechanism="source_reaches_sink",
|
||||
)
|
||||
|
||||
|
||||
def _pair(tmp_path: Path, name: str, vuln: str, fixed: str, *, function: str, cwe: str, provenance: str = "human") -> PairCase:
|
||||
(tmp_path / f"{name}-v.py").write_text(vuln)
|
||||
(tmp_path / f"{name}-f.py").write_text(fixed)
|
||||
return PairCase(
|
||||
name=name,
|
||||
slice="github",
|
||||
language="python",
|
||||
origin="test",
|
||||
vuln_file=tmp_path / f"{name}-v.py",
|
||||
fixed_file=tmp_path / f"{name}-f.py",
|
||||
relpath="app.py",
|
||||
expected=(
|
||||
ExpectedFinding(
|
||||
cwe=cwe, vulnerability_class="x", path="app.py", evidence="", function=function, mechanism="source_reaches_sink"
|
||||
),
|
||||
),
|
||||
min_recall=1.0,
|
||||
fix_policy="silent",
|
||||
provenance=provenance,
|
||||
split="holdout",
|
||||
review_tier="reviewed",
|
||||
reviewer="t",
|
||||
)
|
||||
|
||||
|
||||
def _round_env(tmp_path: Path) -> dict[str, object]:
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
(repo / "app.py").write_text("def f(x):\n return eval(x)\n")
|
||||
rd = tmp_path / "ruleset" / "python"
|
||||
write_ruleset(
|
||||
rd / "rules.toml",
|
||||
[PatternRule(rule_id="good-eval", title="e", languages=("python",), cwe="CWE-95", tags=("injection",), pattern=r"\beval\s*\(")],
|
||||
)
|
||||
manifest_path = tmp_path / "bench.toml"
|
||||
manifest_path.write_text(
|
||||
'name = "t"\nlanguage = "python_web"\n\n[source]\npath = "repo"\n\n'
|
||||
'[[expected]]\ncwe = "CWE-95"\nclass = "code injection"\npath = "app.py"\n'
|
||||
'line = 2\nrule_id = "good-eval"\nsink = "eval"\nevidence = "eval executes input"\n'
|
||||
)
|
||||
return {"target": repo, "manifest": load_benchmark_manifest(manifest_path), "ruleset_dir": tmp_path / "ruleset"}
|
||||
|
||||
|
||||
def test_proposer_admits_recovering_records_and_retracts_leakers(tmp_path: Path) -> None:
|
||||
from openultrasast.improve.evolve import evaluate_mechanism_profiles, propose_mechanism_edits
|
||||
|
||||
candidates = MechanismStore(tmp_path / "candidates.jsonl")
|
||||
exec_record = append_from_pair(
|
||||
candidates, _shape("execute"), summary="s", cwe="CWE-89", pair="teacher-a", provenance="human", tier="seeded"
|
||||
)
|
||||
sys_record = append_from_pair(
|
||||
candidates, _shape("system"), summary="s", cwe="CWE-78", pair="teacher-b", provenance="human", tier="seeded"
|
||||
)
|
||||
holdout = [
|
||||
_pair(tmp_path, "x", EXEC_VULN, EXEC_FIX, function="lookup", cwe="CWE-89"),
|
||||
_pair(tmp_path, "y", SYS_VULN, SYS_LEAKY_FIX, function="ping", cwe="CWE-78", provenance="agent"),
|
||||
]
|
||||
scan_store = tmp_path / "scan" / "mechanisms.jsonl"
|
||||
edits = propose_mechanism_edits(holdout, candidates, scan_store)
|
||||
assert [(e.action, e.mechanism_id) for e in edits] == [("admit", exec_record.id)] # sys leaks on y's fixed side: never proposed
|
||||
assert "x" in edits[0].rationale
|
||||
# once a leaking record is admitted (by a human or an earlier round), the proposer asks to retract it
|
||||
from openultrasast.improve.validator import MechanismEdit, apply_mechanism_edits
|
||||
|
||||
apply_mechanism_edits([MechanismEdit(action="admit", mechanism_id=sys_record.id)], candidates, scan_store)
|
||||
edits = propose_mechanism_edits(holdout, candidates, scan_store)
|
||||
assert ("retract", sys_record.id) in [(e.action, e.mechanism_id) for e in edits]
|
||||
profiles = evaluate_mechanism_profiles(holdout, scan_store)
|
||||
assert profiles["agent"]["pairs"] == 1.0 and profiles["agent"]["pair_correct"] == 0.0 # y: detected but leaked
|
||||
|
||||
|
||||
def test_round_accepts_a_recovering_admission_and_rejects_a_leaking_one_byte_for_byte(tmp_path: Path) -> None:
|
||||
from openultrasast.improve.evolve import run_round
|
||||
from openultrasast.improve.validator import MechanismEdit
|
||||
|
||||
env = _round_env(tmp_path)
|
||||
candidates = MechanismStore(tmp_path / "candidates.jsonl")
|
||||
exec_record = append_from_pair(
|
||||
candidates, _shape("execute"), summary="s", cwe="CWE-89", pair="teacher-a", provenance="human", tier="seeded"
|
||||
)
|
||||
sys_record = append_from_pair(
|
||||
candidates, _shape("system"), summary="s", cwe="CWE-78", pair="teacher-b", provenance="human", tier="seeded"
|
||||
)
|
||||
holdout = [
|
||||
_pair(tmp_path, "x", EXEC_VULN, EXEC_FIX, function="lookup", cwe="CWE-89"),
|
||||
_pair(tmp_path, "y", SYS_VULN, SYS_LEAKY_FIX, function="ping", cwe="CWE-78"),
|
||||
]
|
||||
scan_store = tmp_path / "scan" / "mechanisms.jsonl"
|
||||
ledger = tmp_path / "rule_policy.json"
|
||||
journal = tmp_path / "journal.json"
|
||||
outcome = run_round(
|
||||
env["target"],
|
||||
env["manifest"],
|
||||
ledger_path=ledger,
|
||||
journal_path=journal,
|
||||
ruleset_dir=env["ruleset_dir"], # type: ignore[arg-type]
|
||||
pair_cases=holdout,
|
||||
min_holdout_pairs=1,
|
||||
mechanism_candidates=candidates.log_path,
|
||||
mechanism_store=scan_store,
|
||||
)
|
||||
assert outcome.accepted and outcome.reason == "accepted"
|
||||
assert [r.id for r in MechanismStore(scan_store).load()] == [exec_record.id]
|
||||
entry = json.loads(journal.read_text())[0]
|
||||
mech_edits = [e for e in entry["edits"] if e["lever"] == "mechanisms"]
|
||||
assert mech_edits and mech_edits[0]["mechanism_id"] == exec_record.id and mech_edits[0]["pairs"] == ["teacher-a"]
|
||||
assert entry["mechanisms_before"]["human"]["pair_correct"] == 0.0 and entry["mechanisms_after"]["human"]["pair_correct"] == 1.0
|
||||
before = scan_store.read_bytes()
|
||||
forced = run_round(
|
||||
env["target"],
|
||||
env["manifest"],
|
||||
ledger_path=ledger,
|
||||
journal_path=journal,
|
||||
ruleset_dir=env["ruleset_dir"], # type: ignore[arg-type]
|
||||
pair_cases=holdout,
|
||||
min_holdout_pairs=1,
|
||||
mechanism_candidates=candidates.log_path,
|
||||
mechanism_store=scan_store,
|
||||
scripted_mechanism_edits=[MechanismEdit(action="admit", mechanism_id=sys_record.id, source="export", rationale="scripted")],
|
||||
)
|
||||
assert not forced.accepted and forced.reason.startswith("mechanism_leak") or forced.reason.startswith("profile_regression")
|
||||
assert scan_store.read_bytes() == before # byte-for-byte revert
|
||||
Reference in New Issue
Block a user