mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
fix(hunt): build output gets no candidate rows, a minified line is not scanned once per function, the screen checkpoints every change
The second access-control screen spun for 2h20m on one ordinary mage-ai change: a Next.js build commit whose 3 MB one-line bundles hold 12,355 functions each. `source.ranges_from_ir` ran two regex scans of the whole line once per function, and the screen kept its 422 finished changes in memory only, so stopping it lost them. - `source.ranges_from_ir`: a line over MINIFIED_LINE_CHARS (2000) is not checked per function; JavaScript names on it stay anonymous, other languages keep the parser's name. - `source.generated_reason`: why a changed file is build output (a directory the product walk leaves out, a minified name, over MAX_FILE_BYTES, a line over the cap). `hunt.producers._candidates` leaves such files out and records the reason on the context; the fix site of a vulnerability is always kept. The DSPy arm shares the enumeration. - The screen writes rows.jsonl and log.jsonl per change (rows first, entry last) and resumes by skipping logged ids. Tests: the minified line never reaches the per-function scan; generated_reason on the four shapes and two source files; build output in an ordinary change yields no rows while a vulnerability path in a one-line file is kept. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4cf9c85d22
commit
81ae206978
@@ -256,3 +256,18 @@ def test_obligation_loader_errors_remain_type_only(repository, monkeypatch):
|
||||
assert row["provenance.semantic.ok"] is False
|
||||
assert row["provenance.semantic.reason"] == "OSError"
|
||||
assert "private file contents" not in json.dumps(row)
|
||||
|
||||
|
||||
def test_build_output_in_an_ordinary_change_gets_no_candidate_rows(repository):
|
||||
root, change = repository
|
||||
(root / "dist").mkdir()
|
||||
(root / "dist" / "bundle.js").write_text("function a() {}\n")
|
||||
(root / "app.min.js").write_text("function b() {}\n")
|
||||
(root / "one.js").write_text("".join(f"function f{i}(){{}}" for i in range(400)) + "\n")
|
||||
(root / "big.js").write_text("function c() {}\n" + "// padding\n" * 100_000)
|
||||
files = [{"path": p, "head_lines": [[1, 1]]} for p in ("dist/bundle.js", "app.min.js", "one.js", "big.js", "app.py")]
|
||||
rows = candidate_rows(root, {**change, "kind": "ordinary", "files": files})
|
||||
assert {row["path"] for row in rows} == {"app.py"}
|
||||
# The fix site of a vulnerability is kept whatever the file looks like.
|
||||
kept = candidate_rows(root, {**change, "path": "one.js", "function": "f0", "function_lines": [1, 1], "files": files[2:3]})
|
||||
assert [row["path"] for row in kept] == ["one.js"]
|
||||
|
||||
@@ -162,3 +162,30 @@ def test_call_index_gives_the_same_call_sites_and_is_reused_across_names(tmp_pat
|
||||
assert source.callers_of(tmp_path, "handler", files=["other.py"], index=cache) == (("other.py", 6),)
|
||||
(tmp_path / "other.py").write_text("") # the cached index, not the disk, answers the second name
|
||||
assert source.callers_of(tmp_path, "other", files=["other.py"], index=cache) == (("other.py", 7),)
|
||||
|
||||
|
||||
def test_a_minified_line_is_not_scanned_once_per_function(monkeypatch):
|
||||
text = "".join(f"function f{i}(){{return {i}}}" for i in range(3000)) + "\n"
|
||||
assert len(text) > source.MINIFIED_LINE_CHARS
|
||||
calls: list[str] = []
|
||||
original = source._declares
|
||||
monkeypatch.setattr(source, "_declares", lambda line, name: calls.append(line) or original(line, name))
|
||||
result = source.ranges("bundle.js", text, "javascript")
|
||||
assert len(result) == 3001 and result[0].name == source.MODULE_FUNCTION
|
||||
assert set(calls) == {""} # the long line itself never reaches the per-function scan
|
||||
assert {r.name for r in result[1:]} == {source.ANON_FUNCTION}
|
||||
|
||||
|
||||
def test_generated_reason_names_build_output_only(tmp_path):
|
||||
(tmp_path / "dist").mkdir()
|
||||
(tmp_path / "dist" / "bundle.js").write_text("function a() {}\n")
|
||||
(tmp_path / "app.min.js").write_text("function b() {}\n")
|
||||
(tmp_path / "one.js").write_text("".join(f"function f{i}(){{}}" for i in range(400)) + "\n")
|
||||
(tmp_path / "big.js").write_text("function c() {}\n" + "// padding\n" * 100_000)
|
||||
(tmp_path / "app.js").write_text("function d() {}\n")
|
||||
assert source.generated_reason(tmp_path, "dist/bundle.js") == "excluded_dir"
|
||||
assert source.generated_reason(tmp_path, "app.min.js") == "minified_name"
|
||||
assert source.generated_reason(tmp_path, "one.js") == "long_line"
|
||||
assert source.generated_reason(tmp_path, "big.js") == "too_large"
|
||||
assert source.generated_reason(tmp_path, "app.js") is None
|
||||
assert source.generated_reason(tmp_path, "missing.js") is None
|
||||
|
||||
Reference in New Issue
Block a user