mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
95 lines
4.3 KiB
Python
95 lines
4.3 KiB
Python
"""model-grounded-detection task 2.1: the Joern seam, out of process and capability-detected.
|
|||
|
|
|
||
|
|
Joern is a JVM tool. The whole integration is one subprocess boundary so that the core install stays
|
||
|
|
zero-dependency and an absent engine degrades to `suspicion` with a recorded reason rather than a traceback.
|
||
|
|
Nothing here imports a JVM binding, and every test runs with Joern absent.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
|
||
|
|
def test_without_joern_the_resolved_backend_is_the_null_one(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
|
|
from openultrasast.cpg.backend import NullBackend, resolve_cpg_backend
|
||
|
|
|
||
|
|
monkeypatch.setenv("OPENULTRASAST_JOERN_PROBE", "off")
|
||
|
|
backend = resolve_cpg_backend()
|
||
|
|
assert isinstance(backend, NullBackend)
|
||
|
|
assert backend.available() is False
|
||
|
|
assert backend.build(Path(".")) is None, "an absent engine yields no CPG, and the caller degrades"
|
||
|
|
|
||
|
|
|
||
|
|
def test_the_capability_probe_reuses_the_existing_joern_probe(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
|
|
"""`semantic/engines.joern_available` already exists; the model layer must not grow a second probe."""
|
||
|
|
from openultrasast.cpg.capability import has_cpg
|
||
|
|
|
||
|
|
monkeypatch.setenv("OPENULTRASAST_JOERN_PROBE", "off")
|
||
|
|
assert has_cpg() is False
|
||
|
|
monkeypatch.setenv("OPENULTRASAST_JOERN_PROBE", "on")
|
||
|
|
assert has_cpg() is True
|
||
|
|
|
||
|
|
|
||
|
|
def test_the_seam_imports_nothing_outside_the_standard_library() -> None:
|
||
|
|
"""Req 4.2: the core install has no dependencies, so the seam may not import one at module load."""
|
||
|
|
import ast
|
||
|
|
|
||
|
|
source = Path("src/openultrasast/cpg/backend.py").read_text()
|
||
|
|
imported = set()
|
||
|
|
for node in ast.walk(ast.parse(source)):
|
||
|
|
if isinstance(node, ast.ImportFrom) and node.module and node.level == 0:
|
||
|
|
imported.add(node.module.split(".")[0])
|
||
|
|
elif isinstance(node, ast.Import):
|
||
|
|
imported.update(alias.name.split(".")[0] for alias in node.names)
|
||
|
|
stdlib = {"__future__", "json", "os", "re", "shutil", "subprocess", "tempfile", "dataclasses", "pathlib", "typing",
|
||
|
|
"collections", "logging", "hashlib"}
|
||
|
|
assert imported <= stdlib, f"the seam imports outside the standard library: {sorted(imported - stdlib)}"
|
||
|
|
assert "import jpype" not in source and "py4j" not in source, "Joern is reached by subprocess, never in-process"
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_scripted_backend_parses_a_query_result_without_joern(tmp_path: Path) -> None:
|
||
|
|
"""The parsing is exercised by canned JSON, so the query contract is testable with no engine installed."""
|
||
|
|
from openultrasast.cpg.backend import CpgResult
|
||
|
|
|
||
|
|
payload = [{"sink": "os.system", "source": "request.args", "path": ["a", "b"], "sanitized": False}]
|
||
|
|
|
||
|
|
def run(query: str, params: dict[str, object]) -> object:
|
||
|
|
assert query == "taint"
|
||
|
|
return payload
|
||
|
|
|
||
|
|
result = CpgResult(cpg_path=tmp_path / "cpg.bin", run=run)
|
||
|
|
assert result.run("taint", {}) == payload
|
||
|
|
|
||
|
|
|
||
|
|
def test_joern_output_is_read_from_a_delimited_block_not_from_noisy_stdout() -> None:
|
||
|
|
"""Joern prints a banner and a REPL prompt; the payload is fenced so the parse cannot swallow either."""
|
||
|
|
from openultrasast.cpg.backend import BEGIN, END, extract_payload
|
||
|
|
|
||
|
|
noisy = f"Compiling...\nJoern v4.0\n{BEGIN}\n{json.dumps([{'sink': 'eval'}])}\n{END}\njoern> \n"
|
||
|
|
assert extract_payload(noisy) == [{"sink": "eval"}]
|
||
|
|
assert extract_payload("no markers at all") is None
|
||
|
|
assert extract_payload(f"{BEGIN}\nnot json\n{END}") is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_a_failing_or_slow_engine_fails_closed(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||
|
|
"""Fail closed: a build or query that errors yields no verdict, never a wrong one."""
|
||
|
|
import subprocess
|
||
|
|
|
||
|
|
from openultrasast.cpg.backend import JoernBackend
|
||
|
|
|
||
|
|
def boom(*args: object, **kwargs: object) -> object:
|
||
|
|
raise subprocess.TimeoutExpired(cmd="joern-parse", timeout=1)
|
||
|
|
|
||
|
|
backend = JoernBackend(runner=boom)
|
||
|
|
monkeypatch.setenv("OPENULTRASAST_JOERN_PROBE", "on")
|
||
|
|
assert backend.build(tmp_path) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_the_query_scripts_are_shipped_beside_the_seam() -> None:
|
||
|
|
"""The CPGQL is ours and is data: it lives with the package, not inlined in a Python string."""
|
||
|
|
queries = Path("src/openultrasast/cpg/queries")
|
||
|
|
assert (queries / "taint.sc").is_file(), "the injection taint query must ship with the package"
|