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:
Marius Ciepluch
2026-10-04 10:34:50 +02:00
co-authored by Claude Opus 5.5
parent 20777d8863
commit e13ce35a75
13 changed files with 175 additions and 34 deletions
+30
View File
@@ -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()
+2 -2
View File
@@ -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
+34
View File
@@ -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()
+23
View File
@@ -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"}
+12
View File
@@ -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"}