mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-17 03:59:28 +00:00
engine: admit TypeScript (jssrc2cpg, JavaScript taint specs via one alias), .mts/.cts, mixed JS/TS builds one CPG
Implemented with Codex. Not merged: wall-clock deadline tests failed at host load 47; to be re-checked against main at normal load. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
20777d8863
commit
e13ce35a75
@@ -1368,3 +1368,33 @@ def test_requests_held_for_arbitration_are_reported_as_unasked() -> None:
|
||||
merged = scan._batched(once, "taint", {rid: {} for rid in weights}, weights=weights, within=lambda: next(budget, False), unasked=held)
|
||||
assert merged is not None and len(calls) == 1
|
||||
assert sorted(held + calls[0]) == sorted(weights), (held, calls)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("extension", [".ts", ".tsx", ".mts", ".cts"])
|
||||
def test_typescript_uses_javascript_frontend(tmp_path, monkeypatch, extension):
|
||||
from openultrasast.cpg.backend import JoernBackend
|
||||
|
||||
(tmp_path / "tests").mkdir()
|
||||
(tmp_path / f"tests/probe{extension}").write_bytes(b"function first_party_test(x) { return x; }")
|
||||
from openultrasast.preprocess import detect_language
|
||||
|
||||
assert detect_language(Path(f"probe{extension}")) == "typescript"
|
||||
commands = []
|
||||
|
||||
def runner(command, **kwargs):
|
||||
if _is_overlay(command):
|
||||
return _overlaid(command, kwargs.get("cwd"))
|
||||
commands.append(command)
|
||||
assert Path(command[0]).name == "jssrc2cpg"
|
||||
Path(command[command.index("-o") + 1]).write_bytes(b"graph")
|
||||
return subprocess.CompletedProcess(command, 0, "", "")
|
||||
|
||||
monkeypatch.setattr("shutil.which", lambda name: f"/opt/joern/{name}")
|
||||
monkeypatch.setenv("OPENULTRASAST_JOERN_PROBE", "on")
|
||||
result = JoernBackend(runner=runner).build(tmp_path, language="typescript")
|
||||
try:
|
||||
assert result is not None
|
||||
assert len(commands) == 1
|
||||
finally:
|
||||
if result is not None:
|
||||
result.cleanup()
|
||||
|
||||
@@ -87,8 +87,8 @@ def test_feature_typescript_repository_witness_reaches_static_record(tmp_path: P
|
||||
assert parts["engine"].state == "ran"
|
||||
assert parts["engine"].values["eng.witness_steps_min"] == 5
|
||||
assert parts["engine"].values["eng.findings"] == 1
|
||||
assert builder.recorded_engine("typescript", [], None, True).state == "none"
|
||||
assert builder.recorded_engine("typescript", [], result, False).state == "none"
|
||||
assert builder.recorded_engine("typescript", [], None, True).state == "failed"
|
||||
assert builder.recorded_engine("typescript", [], result, False).state == "ran"
|
||||
assert builder.recorded_engine("typescript", [], result, True).values["eng.findings"] == 0
|
||||
|
||||
|
||||
|
||||
@@ -344,3 +344,37 @@ def test_unnamed_sharded_census_remains_explicitly_incomplete(tmp_path):
|
||||
assert "partition_file_census_unavailable" in graph.execution_diagnostics()
|
||||
finally:
|
||||
graph.cleanup()
|
||||
|
||||
|
||||
def test_mixed_javascript_typescript_pin_builds_and_queries_one_graph(tmp_path):
|
||||
from openultrasast.model.partitions import build_partitions
|
||||
|
||||
sources = {"app.js": "function run(x) { return x; }", "handler.ts": "const x: number = 1;"}
|
||||
for path, body in sources.items():
|
||||
(tmp_path / path).write_text(body)
|
||||
builds, queries = [], []
|
||||
|
||||
class Graph:
|
||||
def run_batch(self, kind, requests):
|
||||
queries.append(requests)
|
||||
return {**{key: [] for key in requests}, "__census__": [{"file_names": list(sources)}]}
|
||||
|
||||
def build(root, language):
|
||||
assert {p.name: p.read_text() for p in root.iterdir()} == sources
|
||||
builds.append(language)
|
||||
return Graph()
|
||||
|
||||
graph = build_partitions(tmp_path, build, None)
|
||||
try:
|
||||
assert builds == ["javascript"]
|
||||
assert {p.language for p in graph.partitions} == {"javascript", "typescript"}
|
||||
assert sum(p.source_bytes for p in graph.partitions) == sum(len(body.encode()) for body in sources.values())
|
||||
assert "cross_partition_semantics_unresolved" not in graph.boundaries
|
||||
assert graph.boundary_languages["typescript_property_support_unvalidated"] == ("typescript",)
|
||||
requests = {path: {"file": path} for path in sources}
|
||||
answers = graph.run_batch("taint", requests)
|
||||
assert all(answers[path] == [] for path in sources)
|
||||
assert queries == [requests]
|
||||
assert not graph.execution_diagnostics()
|
||||
finally:
|
||||
graph.cleanup()
|
||||
|
||||
@@ -322,3 +322,26 @@ def test_a_sanitizer_that_does_not_stop_sql_injection_is_not_listed() -> None:
|
||||
assert "esc_html" not in sanitizers
|
||||
assert "$wpdb->prepare" in sanitizers, "the parameterized form does stop it"
|
||||
assert "esc_sql" in sanitizers
|
||||
|
||||
|
||||
def test_typescript_taint_specs_share_javascript_facts():
|
||||
from openultrasast.model.specs import taint_specs
|
||||
|
||||
javascript = taint_specs(language="javascript")
|
||||
assert javascript
|
||||
assert taint_specs(language="typescript") == javascript
|
||||
|
||||
|
||||
def test_typescript_native_facts_take_precedence_over_alias():
|
||||
from dataclasses import replace
|
||||
|
||||
from openultrasast.model.specs import taint_specs
|
||||
from openultrasast.semantic.facts import load_facts
|
||||
|
||||
facts = load_facts()
|
||||
native = replace(next(sink for sink in facts.sinks if sink.language == "javascript"), language="typescript", calls=("tsOnly",))
|
||||
facts = replace(facts, sinks=(*facts.sinks, native))
|
||||
specs = taint_specs(language="typescript", facts=facts)
|
||||
assert specs
|
||||
assert {spec.language for spec in specs.values()} == {"typescript"}
|
||||
assert {sink for spec in specs.values() for sink in spec.sinks} == {"tsOnly"}
|
||||
|
||||
@@ -311,3 +311,15 @@ def test_the_loop_runs_as_one_plane_run_and_the_gate_rejects_a_demotion_that_cos
|
||||
assert set(features) == {"lmdeploy/vl/media/connection.py::_load_http_url"}, "no fixed-pin alert became a candidate"
|
||||
assert all(r["pin"] == VULNERABLE and "in_fix_range" not in json.dumps(r["x"]) for r in features.values())
|
||||
assert fixed_alert.row["in_fix_range"] is True and fixed_alert.row["function"] == "load"
|
||||
|
||||
|
||||
def test_engine_languages_include_aliases_only_for_available_tables(tmp_path):
|
||||
from openultrasast.plane.tasks.alerts import engine_languages
|
||||
|
||||
assert "typescript" in engine_languages()
|
||||
semantic = tmp_path / "semantic"
|
||||
semantic.mkdir()
|
||||
(semantic / "python.toml").write_text("")
|
||||
assert engine_languages(tmp_path) == {"python"}
|
||||
(semantic / "javascript.toml").write_text("")
|
||||
assert engine_languages(tmp_path) == {"python", "javascript", "typescript"}
|
||||
|
||||
Reference in New Issue
Block a user