mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
feat(pre-push-safety-net): compare targeted base and head evidence
This commit is contained in:
@@ -197,3 +197,20 @@ unshipped test-path records. Runtime origin remains unspecified without declarat
|
||||
readable Python graph and exercises the production taint, dominance and configuration
|
||||
batch queries. Each must return the actual source filename in its census. This
|
||||
checks query compilation and census transport, not security-property coverage.
|
||||
|
||||
## Targeted novelty comparison smoke
|
||||
|
||||
After rebuilding the image, run a declared JavaScript guard-removal comparison:
|
||||
|
||||
```bash
|
||||
docker run --rm -i --network none --entrypoint python openultrasast:dev - --case guard < ops/smoke_delta.py
|
||||
```
|
||||
|
||||
Use `--case movement` for unchanged JavaScript backlog shifted by comments, or
|
||||
`--case connection` for a PHP input newly reaching an unchanged operation. Every
|
||||
case verifies immutable base/head bytes, runs the existing driver and targeted
|
||||
comparison under one 900-second lab budget, and checks that dirty checkout/index
|
||||
state survives. Output contains an evidence JSON object followed by a completion
|
||||
JSON object; success requires exit zero and `verified: true` in that final object.
|
||||
These declared regions exercise comparison mechanics; full replay, normal alert
|
||||
admission and hook latency are separate evaluation work.
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"""Real Git/Joern novelty controls; no hook admission or latency claim."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
|
||||
from openultrasast.cpg.backend import JoernBackend
|
||||
from openultrasast.model.contracts import ExecutionBudget
|
||||
from openultrasast.model.regions import ScanRegion
|
||||
from openultrasast.model.scan import ScanBudget, scan_repository
|
||||
from openultrasast.push.policy import compare_targeted_base, semantics_digest
|
||||
from openultrasast.push.snapshot import SnapshotAdapter
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--case", choices=("guard", "movement", "connection"), required=True)
|
||||
args = parser.parse_args()
|
||||
before = (
|
||||
b"export function handler(req) {\n"
|
||||
b" if (!authenticate(req)) return;\n"
|
||||
b" deleteUser(req.params.id);\n} // handler\n"
|
||||
b"export function sibling(other) {\n"
|
||||
b" if (!authenticate(other)) return;\n"
|
||||
b" deleteUser(other.params.id);\n} // sibling\n"
|
||||
)
|
||||
after = before.replace(b" if (!authenticate(req)) return;\n", b"", 1)
|
||||
language, family, filename = "javascript", "access_control", "api.js"
|
||||
expected = "worsened"
|
||||
if args.case == "movement":
|
||||
before = after
|
||||
after = b"// documentation moved this existing operation\n\n" + before
|
||||
expected = "unchanged"
|
||||
elif args.case == "connection":
|
||||
language, family, filename = "php", "injection", "api.php"
|
||||
before = b'<?php\nfunction handler($req) {\n $value = "fixed";\n eval($value);\n}\n'
|
||||
after = before.replace(b'$value = "fixed";', b"$value = $req;")
|
||||
expected = "new"
|
||||
started = time.monotonic()
|
||||
budget = ExecutionBudget(started + 900, 2.0)
|
||||
report: dict[str, object] = {
|
||||
"case": args.case,
|
||||
"purpose": (
|
||||
"Real targeted comparison with declared regions and Git context; no automatic context projection, full replay or admission"
|
||||
),
|
||||
"shared_budget_seconds": 900,
|
||||
}
|
||||
with tempfile.TemporaryDirectory(prefix="ousast-delta-smoke-") as temporary:
|
||||
repo = Path(temporary)
|
||||
|
||||
def git(*parts: str) -> bytes:
|
||||
return subprocess.run(["git", "-C", str(repo), *parts], check=True, capture_output=True, timeout=10).stdout
|
||||
|
||||
git("init")
|
||||
git("config", "user.name", "Delta smoke")
|
||||
git("config", "user.email", "[email protected]")
|
||||
git("config", "core.hooksPath", os.devnull)
|
||||
source = repo / filename
|
||||
source.write_bytes(before)
|
||||
git("add", filename)
|
||||
git("commit", "-m", "base")
|
||||
base_oid = git("rev-parse", "HEAD").decode().strip()
|
||||
source.write_bytes(after)
|
||||
git("add", filename)
|
||||
git("commit", "-m", args.case)
|
||||
head_oid = git("rev-parse", "HEAD").decode().strip()
|
||||
source.write_bytes(b"uncommitted content\n")
|
||||
index = (repo / ".git/index").read_bytes()
|
||||
status = git("status", "--porcelain=v1", "-z")
|
||||
adapter = SnapshotAdapter(repo)
|
||||
(comparison,) = adapter.resolve_updates(f"refs/heads/topic {head_oid} refs/heads/topic {base_oid}\n").comparisons
|
||||
context = adapter.compare(comparison, declaration_paths=(), budget=budget)
|
||||
regions = (ScanRegion(filename, "handler", language, (family,), 1.0, "entry_point"),)
|
||||
backend = JoernBackend(heap_mb=1024)
|
||||
semantics = semantics_digest(engine_identity="joern-4.0.625/frontend-retention-v1", options={"max_regions": 1})
|
||||
with adapter.materialize(base_oid, budget=budget) as base_snapshot, adapter.materialize(head_oid, budget=budget) as head_snapshot:
|
||||
assert (base_snapshot.root / filename).read_bytes() == before and before
|
||||
assert (head_snapshot.root / filename).read_bytes() == after and after
|
||||
head = scan_repository(
|
||||
head_snapshot.root,
|
||||
regions,
|
||||
backend=backend,
|
||||
budget=ScanBudget(max_regions=1),
|
||||
execution_budget=budget,
|
||||
ranking_mode="evidence",
|
||||
unit="delta-smoke",
|
||||
population_complete=True,
|
||||
)
|
||||
assert head.findings and head.scope and head.question_outcomes, head
|
||||
assert not head.degradations and all(q.status == "completed" for q in head.question_outcomes), head
|
||||
delta = compare_targeted_base(
|
||||
base_snapshot.root,
|
||||
head=head,
|
||||
head_regions=regions,
|
||||
base_regions=regions,
|
||||
context=context,
|
||||
backend=backend,
|
||||
execution_budget=budget,
|
||||
scan_budget=ScanBudget(max_regions=1),
|
||||
head_semantics=semantics,
|
||||
base_semantics=semantics,
|
||||
)
|
||||
report.update(
|
||||
{
|
||||
"comparison": comparison.to_payload(),
|
||||
"context": context.to_payload(),
|
||||
"base_input": {"bytes": len(before), "sha256": hashlib.sha256(before).hexdigest()},
|
||||
"head_input": {"bytes": len(after), "sha256": hashlib.sha256(after).hexdigest()},
|
||||
"head": asdict(head),
|
||||
"delta": asdict(delta),
|
||||
"expected_novelty": expected,
|
||||
}
|
||||
)
|
||||
# Emit evidence before assertions so a failed instrument cannot look like a clean zero.
|
||||
print(json.dumps(report, indent=2), flush=True)
|
||||
assert delta.base_scan and delta.base_scan.question_outcomes, delta
|
||||
assert not delta.base_scan.degradations, delta
|
||||
assert all(q.status == "completed" for q in delta.base_scan.question_outcomes), delta
|
||||
assert delta.candidates and all(c.novelty == expected for c in delta.candidates), delta
|
||||
assert not delta.coverage_reasons, delta
|
||||
assert not base_snapshot.root.exists() and not head_snapshot.root.exists()
|
||||
assert source.read_bytes() == b"uncommitted content\n"
|
||||
assert (repo / ".git/index").read_bytes() == index
|
||||
assert git("status", "--porcelain=v1", "-z") == status
|
||||
# A separate completion record distinguishes all assertions passing from partial JSON.
|
||||
print(json.dumps({"verified": True, "case": args.case, "elapsed_seconds": round(time.monotonic() - started, 2)}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user