mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
wip(archive): fix-mechanisms arm — parked WIP preserved before worktree prune
Uncommitted work from the fix-mechanisms worktree (mechanisms.py + cst/extra/cli edits), committed so the worktree can be removed without loss. Branch is 0 commits on top of a 172-behind base; re-integrate via rebase if resumed. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ee422b63e0
commit
485aacca02
@@ -0,0 +1,289 @@
|
||||
"""Authored fix pairs only: no store, models, containers, or population files."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from dataclasses import replace
|
||||
|
||||
import pytest
|
||||
|
||||
from openultrasast.learn.folds import Fold
|
||||
from openultrasast.learn.mechanisms import Pair, mine_pairs
|
||||
|
||||
|
||||
def pair(before: str, after: str, *, language: str = "python", family: str = "output_encoding", repo: str = "owner/alpha") -> Pair:
|
||||
return Pair("pair-" + repo, repo, repo, language, family, before, after)
|
||||
|
||||
|
||||
def repeated(p: Pair, **kwargs):
|
||||
return mine_pairs([p, replace(p, id="pair-beta", repository="owner/beta", group="owner/beta")], **kwargs)
|
||||
|
||||
|
||||
def test_learn_mechanisms_sanitizer_return_and_def_use():
|
||||
p = pair("def f(x):\n return x", "def f(x):\n y = html.escape(x)\n return y")
|
||||
result = repeated(p)
|
||||
assert [(c["form"], c["pattern"]) for c in result.candidates] == [("sanitizer", {"callee": "html.escape", "argument": 0})]
|
||||
assert result.candidates[0]["repository_count"] == 2
|
||||
assert "owner/alpha" not in json.dumps(result.record)
|
||||
assert result.record["pairs_mined"] == 2
|
||||
|
||||
|
||||
def test_learn_mechanisms_javascript():
|
||||
p = pair("function f(x) { res.send(x); }", "function f(x) { const y = escapeHtml(x); res.send(y); }", language="javascript")
|
||||
assert repeated(p).candidates[0]["pattern"] == {"callee": "escapeHtml", "argument": 0}
|
||||
|
||||
|
||||
def test_learn_mechanisms_guard_and_known():
|
||||
p = pair(
|
||||
"def f(u):\n redirect(u)",
|
||||
"def f(u):\n if not is_safe_url(u):\n abort()\n redirect(u)",
|
||||
family="untrusted_destination",
|
||||
)
|
||||
candidate = repeated(p).candidates[0]
|
||||
assert candidate["form"] == "guard"
|
||||
assert candidate["pattern"] == {"callee": "is_safe_url", "argument": 0, "predicate": "truthy"}
|
||||
assert candidate["known"] is True
|
||||
assert not any(f["eligible"] for f in candidate["folds"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(importlib.util.find_spec("tree_sitter_php") is None, reason="PHP tree-sitter wheel unavailable")
|
||||
def test_learn_mechanisms_php_safe_argument_and_echo():
|
||||
p = pair(
|
||||
"function f($d) { return unserialize($d); }",
|
||||
"function f($d) { return unserialize($d, ['allowed_classes' => false]); }",
|
||||
language="php",
|
||||
family="deserialization",
|
||||
)
|
||||
assert repeated(p).candidates[0]["pattern"] == {"callee": "unserialize", "argument": 1, "key": "allowed_classes", "value": False}
|
||||
assert repeated(p).candidates[0]["form"] == "safe_argument"
|
||||
p = pair("function f($x) { echo $x; }", "function f($x) { echo htmlspecialchars($x); }", language="php")
|
||||
assert repeated(p).candidates[0]["form"] == "sanitizer"
|
||||
|
||||
|
||||
def test_learn_mechanisms_safe_alternative():
|
||||
p = pair("def f(x):\n return yaml.load(x)", "def f(x):\n return yaml.safe_load(x)", family="deserialization")
|
||||
candidate = repeated(p).candidates[0]
|
||||
assert candidate["form"] == "safe_alternative"
|
||||
assert candidate["pattern"] == {"callee": "yaml.load", "replacement": "yaml.safe_load"}
|
||||
assert candidate["known"] is True
|
||||
|
||||
|
||||
def test_learn_mechanisms_keyword():
|
||||
p = pair("def f(x):\n subprocess.run(x, shell=True)", "def f(x):\n subprocess.run(x, shell=False)", family="injection")
|
||||
result = repeated(p)
|
||||
assert any(c["pattern"] == {"callee": "subprocess.run", "key": "shell", "value": False} for c in result.candidates)
|
||||
|
||||
|
||||
def test_learn_mechanisms_one_repository_and_local_helpers_rejected():
|
||||
p = pair("def f(x):\n return x", "def f(x):\n return cleanse(x)")
|
||||
assert mine_pairs([p]).candidates == []
|
||||
local = replace(p, fixed="def cleanse(x):\n return x\ndef f(x):\n return cleanse(x)")
|
||||
result = repeated(local)
|
||||
assert result.candidates == []
|
||||
assert result.record["skips"]["local_definition"] == 2
|
||||
assert repeated(replace(p, fixed="def f(x):\n return alpha_escape(x)")).candidates == []
|
||||
|
||||
|
||||
def test_learn_mechanisms_folds_exclude_proposers():
|
||||
p = pair("def f(x):\n return x", "def f(x):\n return cleanse(x)")
|
||||
folds = [Fold("outer", "outer-0", frozenset({p.group})), Fold("outer", "outer-1", frozenset({"elsewhere"}))]
|
||||
candidate = repeated(p, folds=folds).candidates[0]
|
||||
assert [(f["repository_count"], f["eligible"]) for f in candidate["folds"]] == [(1, False), (2, True)]
|
||||
assert candidate["folds"][0]["proposing_pairs"] == ["pair-beta"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixed",
|
||||
[
|
||||
"def f(x):\n cleanse(x)\n return x",
|
||||
"def f(x):\n y = cleanse(x)\n y = 'constant'\n return y",
|
||||
"def f(x):\n if is_safe_url(x):\n pass\n redirect(x)",
|
||||
],
|
||||
)
|
||||
def test_learn_mechanisms_unrelated_calls_are_not_candidates(fixed):
|
||||
before = "def f(x):\n redirect(x)" if "redirect" in fixed else "def f(x):\n return x"
|
||||
family = "untrusted_destination" if "redirect" in fixed else "output_encoding"
|
||||
assert repeated(pair(before, fixed, family=family)).candidates == []
|
||||
|
||||
|
||||
def test_learn_mechanisms_skips_and_determinism():
|
||||
p = pair("def f(x):\n return x", "def f(x):\n return cleanse(x)")
|
||||
assert repeated(p) == repeated(p)
|
||||
assert repeated(replace(p, fixed="def broken(")).record["skips"]["unparseable"] == 2
|
||||
assert repeated(replace(p, language="ruby")).record["skips"]["unsupported_language"] == 2
|
||||
assert (
|
||||
repeated(replace(p, fixed="def f(x):\n for y in x:\n cleanse(y)\n return x")).record["skips"]["unsupported_shape"] == 2
|
||||
)
|
||||
|
||||
|
||||
def test_learn_mechanisms_guard_must_test_sink_value():
|
||||
p = pair(
|
||||
"def f(x, u):\n redirect(u)",
|
||||
"def f(x, u):\n if not check(x):\n abort()\n redirect(u)",
|
||||
family="untrusted_destination",
|
||||
)
|
||||
assert repeated(p).candidates == []
|
||||
|
||||
|
||||
def test_learn_mechanisms_existing_path_call_is_not_new():
|
||||
p = pair("def f(x):\n return html.escape(x)", "def f(x):\n y = html.escape(x)\n return y")
|
||||
assert repeated(p).candidates == []
|
||||
|
||||
|
||||
def test_learn_mechanisms_missing_grammar_is_counted(monkeypatch):
|
||||
monkeypatch.setattr("openultrasast.learn.mechanisms.grammar_for", lambda language: None)
|
||||
result = repeated(pair("def f(x):\n return x", "def f(x):\n return cleanse(x)"))
|
||||
assert result.candidates == []
|
||||
assert result.record["skips"] == {"grammar_unavailable": 2}
|
||||
|
||||
|
||||
def test_learn_mechanisms_pair_identity_and_unread_inputs():
|
||||
from openultrasast.learn.mechanisms import pairs_from_rows
|
||||
|
||||
base = {
|
||||
"repo": "owner/alpha",
|
||||
"group": "owner/alpha",
|
||||
"source": "pairs",
|
||||
"candidate": "app.py::f",
|
||||
"language": "python",
|
||||
"family": "output_encoding",
|
||||
"profile": "static",
|
||||
"unit": "pin",
|
||||
}
|
||||
rows = [{**base, "label": label, "excerpt_sha": str(label)} for label in (0, 1)]
|
||||
pairs, skips, count = pairs_from_rows(rows, lambda sha: "def f(x):\n return x")
|
||||
assert len(pairs) == count == 1 and not skips
|
||||
assert pairs_from_rows(rows + [rows[0]], lambda sha: "code")[1] == {"ambiguous_pair": 1}
|
||||
assert pairs_from_rows(rows[:1], lambda sha: "code")[1] == {"incomplete_pair": 1}
|
||||
assert pairs_from_rows(rows, lambda sha: None)[1] == {"unreadable_excerpt": 1}
|
||||
forbidden = [{**row, "source": "population-v3"} for row in rows]
|
||||
assert pairs_from_rows(forbidden, lambda sha: pytest.fail("must not read excluded excerpts"))[1] == {"excluded_source": 2}
|
||||
|
||||
|
||||
def memory_store(tmp_path):
|
||||
from openultrasast.plane.memory import FileStore, row_id
|
||||
|
||||
store = FileStore(tmp_path / "memory")
|
||||
rows = []
|
||||
for repo in ("owner/alpha", "owner/beta", "owner/gamma"):
|
||||
for label in (0, 1):
|
||||
code = "def f(x):\n return x" if label else "def f(x):\n return cleanse(x)"
|
||||
sha = store.put_blob("excerpts", code.encode())
|
||||
rows.append(
|
||||
{
|
||||
"id": row_id("example", "test", "test", f"{repo}:{label}"),
|
||||
"kind": "example",
|
||||
"repo": repo,
|
||||
"pin": str(label) * 40,
|
||||
"run": "test",
|
||||
"task": "test",
|
||||
"population": "pairs",
|
||||
"split": "pairs",
|
||||
"image": "host",
|
||||
"candidate": "app.py::f",
|
||||
"family": "output_encoding",
|
||||
"language": "python",
|
||||
"profile": "static",
|
||||
"label": label,
|
||||
"source": "pairs",
|
||||
"group": repo,
|
||||
"unit": "pin",
|
||||
"x": {},
|
||||
"instruments": {},
|
||||
"excerpt_sha": sha,
|
||||
}
|
||||
)
|
||||
store.put_rows(rows)
|
||||
return store
|
||||
|
||||
|
||||
def test_learn_mechanisms_store_cli_and_privacy(tmp_path, monkeypatch):
|
||||
from openultrasast.cli import main
|
||||
from openultrasast.learn.mechanisms import RECORD, mine_store
|
||||
|
||||
store = memory_store(tmp_path)
|
||||
result = mine_store(store)
|
||||
assert result.record["complete_pairs"] == 3
|
||||
assert result.record["excerpt_bytes_read"] > 0
|
||||
assert len(result.candidates) == 1
|
||||
assert len(result.candidates[0]["folds"]) == 5
|
||||
monkeypatch.setenv("OUSAST_RESULTS", str(tmp_path / "results"))
|
||||
root = tmp_path / "repo"
|
||||
out = tmp_path / "results" / "plane" / "mining" / "candidates.jsonl"
|
||||
assert main(["learn", "mechanisms", "mine", "--memory", f"file://{tmp_path / 'memory'}", "--out", str(out), "--root", str(root)]) == 0
|
||||
record = (root / RECORD).read_text()
|
||||
assert all(repo not in record for repo in ("alpha", "beta", "gamma"))
|
||||
assert json.loads(out.read_text())["repository_count"] == 3
|
||||
assert main(["learn", "mechanisms", "mine", "--memory", f"file://{tmp_path / 'empty'}", "--out", str(out), "--root", str(root)]) == 2
|
||||
assert json.loads((root / RECORD).read_text())["status"] == "no_mineable_pairs"
|
||||
|
||||
|
||||
def test_learn_mechanisms_output_location_rejected(tmp_path):
|
||||
from openultrasast.learn.mechanisms import MiningResult, write_result
|
||||
|
||||
root = tmp_path / "repo"
|
||||
with pytest.raises(ValueError, match="outside"):
|
||||
write_result(MiningResult([], {}), root / "candidate.jsonl", root=root, results=tmp_path)
|
||||
assert not root.exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("signature,source", [("", "input()"), ("request", "request.get_json()")])
|
||||
def test_learn_mechanisms_source_calls(signature, source):
|
||||
p = pair(f"def f({signature}):\n return {source}", f"def f({signature}):\n return html.escape({source})")
|
||||
assert repeated(p).candidates[0]["pattern"] == {"callee": "html.escape", "argument": 0}
|
||||
|
||||
|
||||
def test_learn_mechanisms_inserted_log_is_not_sink_replacement():
|
||||
p = pair("def f(x):\n yaml.load(x)", "def f(x):\n logger.info(x)\n yaml.safe_load(x)", family="deserialization")
|
||||
result = repeated(p)
|
||||
assert result.candidates == []
|
||||
assert result.record["skips"]["ambiguous_sink"] == 2
|
||||
|
||||
|
||||
def test_learn_mechanisms_keyword_reordering_is_not_change():
|
||||
p = pair(
|
||||
"def f(x):\n subprocess.run(x, shell=False, timeout=10)",
|
||||
"def f(x):\n subprocess.run(x, timeout=10, shell=False)",
|
||||
family="injection",
|
||||
)
|
||||
assert repeated(p).candidates == []
|
||||
|
||||
|
||||
def test_learn_mechanisms_legacy_pair_ids_are_repository_scoped(tmp_path):
|
||||
from openultrasast.learn.mechanisms import mine_store
|
||||
|
||||
result = mine_store(memory_store(tmp_path))
|
||||
candidate = result.candidates[0]
|
||||
assert len(candidate["proposing_pairs"]) == 3
|
||||
assert all(len(ids) == 2 for ids in candidate["proposing_examples"].values())
|
||||
assert len({i for ids in candidate["proposing_examples"].values() for i in ids}) == 6
|
||||
|
||||
|
||||
def test_learn_mechanisms_runtime_callable_is_unsupported():
|
||||
p = pair("def f(x, clean):\n return x", "def f(x, clean):\n return clean(x)")
|
||||
result = repeated(p)
|
||||
assert result.candidates == []
|
||||
assert result.record["skips"]["unsupported_shape"] == 2
|
||||
|
||||
|
||||
def test_learn_mechanisms_language_support_is_not_pooled():
|
||||
p = pair("def f(x):\n return x", "def f(x):\n return cleanse(x)")
|
||||
js = pair("function f(x) { return x; }", "function f(x) { return cleanse(x); }", language="javascript", repo="owner/beta")
|
||||
assert mine_pairs([p, js]).candidates == []
|
||||
|
||||
|
||||
def test_learn_mechanisms_guard_inside_branch_and_numbered_excerpt():
|
||||
p = pair(
|
||||
" 10 def f(x):\n 11 redirect(x)",
|
||||
" 20 def f(x):\n 21 if is_safe_url(x):\n 22 redirect(x)",
|
||||
family="untrusted_destination",
|
||||
)
|
||||
candidate = repeated(p).candidates[0]
|
||||
assert candidate["form"] == "guard" and candidate["pattern"]["predicate"] == "truthy"
|
||||
|
||||
|
||||
@pytest.mark.skipif(importlib.util.find_spec("tree_sitter_php") is None, reason="PHP tree-sitter wheel unavailable")
|
||||
def test_learn_mechanisms_php_receiver_normalization():
|
||||
p = pair("function f($x, $helper) { echo $x; }", "function f($x, $helper) { echo $helper->escape($x); }", language="php")
|
||||
assert repeated(p).candidates[0]["pattern"] == {"callee": "escape", "argument": 0}
|
||||
Reference in New Issue
Block a user