mirror of
https://github.com/vee1e/supplicant.git
synced 2026-09-01 09:50:34 +00:00
- render_sarif() maps each Signal to a SARIF result, rules deduped by kind - Severity -> level: critical/high = error, medium = warning, low/info = note - Evidence file/line become physicalLocation so GitHub code scanning renders inline source links - --format sarif on scan and diff subcommands
127 lines
4.5 KiB
Python
127 lines
4.5 KiB
Python
"""Tests for the SARIF 2.1.0 renderer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from supplicant.models import Artifact, DiffOutcome, ScanReport, Signal
|
|
from supplicant.report.render import render_sarif
|
|
|
|
|
|
def _signal(kind, severity, artifact=None, file=None, line=None, explanation="x"):
|
|
evidence = {}
|
|
if file is not None:
|
|
evidence["file"] = file
|
|
if line is not None:
|
|
evidence["line"] = line
|
|
return Signal(
|
|
kind=kind,
|
|
artifact=artifact,
|
|
severity=severity,
|
|
confidence=0.9,
|
|
evidence=evidence,
|
|
explanation=explanation,
|
|
)
|
|
|
|
|
|
def test_sarif_envelope():
|
|
report = ScanReport(artifacts=[Artifact("npm", "x", "1.0.0")])
|
|
out = json.loads(render_sarif(report))
|
|
assert out["version"] == "2.1.0"
|
|
assert out["$schema"].endswith("sarif-2.1.0.json")
|
|
run = out["runs"][0]
|
|
assert run["tool"]["driver"]["name"] == "supplicant"
|
|
assert run["tool"]["driver"]["informationUri"].startswith("https://")
|
|
assert run["results"] == []
|
|
|
|
|
|
def test_sarif_maps_signal_to_result_with_rule_and_location():
|
|
art = Artifact("npm", "keyv", "6.0.0")
|
|
sig = _signal(
|
|
"install_script_network",
|
|
"high",
|
|
artifact=art,
|
|
file="setup.mjs",
|
|
line=3,
|
|
explanation="curl | sh in install hook",
|
|
)
|
|
report = ScanReport(artifacts=[art], signals=[sig])
|
|
out = json.loads(render_sarif(report))
|
|
result = out["runs"][0]["results"][0]
|
|
assert result["ruleId"] == "install_script_network"
|
|
assert result["level"] == "error"
|
|
assert result["message"]["text"] == "curl | sh in install hook"
|
|
loc = result["locations"][0]["physicalLocation"]
|
|
assert loc["artifactLocation"]["uri"] == "setup.mjs"
|
|
assert loc["region"]["startLine"] == 3
|
|
rule = out["runs"][0]["tool"]["driver"]["rules"][0]
|
|
assert rule["id"] == "install_script_network"
|
|
|
|
|
|
def test_sarif_dedupes_rules_by_kind():
|
|
art = Artifact("npm", "x", "1.0.0")
|
|
sigs = [
|
|
_signal("obfuscation", "low", artifact=art, file="a.js"),
|
|
_signal("obfuscation", "low", artifact=art, file="b.js"),
|
|
_signal("native_binary", "medium", artifact=art, file="lib.so"),
|
|
]
|
|
report = ScanReport(artifacts=[art], signals=sigs)
|
|
out = json.loads(render_sarif(report))
|
|
rules = out["runs"][0]["tool"]["driver"]["rules"]
|
|
assert {r["id"] for r in rules} == {"obfuscation", "native_binary"}
|
|
assert len(out["runs"][0]["results"]) == 3
|
|
|
|
|
|
def test_sarif_severity_to_level_mapping():
|
|
art = Artifact("npm", "x", "1.0.0")
|
|
sigs = [
|
|
_signal("k", "critical", artifact=art, explanation="c"),
|
|
_signal("k", "high", artifact=art, explanation="h"),
|
|
_signal("k", "medium", artifact=art, explanation="m"),
|
|
_signal("k", "low", artifact=art, explanation="l"),
|
|
_signal("k", "info", artifact=art, explanation="i"),
|
|
]
|
|
report = ScanReport(artifacts=[art], signals=sigs)
|
|
out = json.loads(render_sarif(report))
|
|
levels = [r["level"] for r in out["runs"][0]["results"]]
|
|
assert levels == ["error", "error", "warning", "note", "note"]
|
|
|
|
|
|
def test_sarif_includes_diff_signals():
|
|
art = Artifact("npm", "keyv", "6.0.0")
|
|
diff_sig = _signal(
|
|
"baseline_deviation",
|
|
"critical",
|
|
artifact=art,
|
|
file="setup.mjs",
|
|
explanation="new install hook in patch bump",
|
|
)
|
|
diff = DiffOutcome(
|
|
artifact=art,
|
|
from_version="5.2.0",
|
|
to_version="6.0.0",
|
|
signals=[diff_sig],
|
|
)
|
|
report = ScanReport(artifacts=[art], diffs=[diff])
|
|
out = json.loads(render_sarif(report))
|
|
result = out["runs"][0]["results"][0]
|
|
assert result["ruleId"] == "baseline_deviation"
|
|
assert result["level"] == "error"
|
|
assert out["runs"][0]["results"][0]["locations"][0]["physicalLocation"]["artifactLocation"]["uri"] == "setup.mjs"
|
|
|
|
|
|
def test_sarif_handles_missing_evidence_file():
|
|
sig = _signal("obfuscation", "medium", artifact=Artifact("npm", "x", "1.0.0"), explanation="eval found")
|
|
report = ScanReport(artifacts=[sig.artifact], signals=[sig])
|
|
out = json.loads(render_sarif(report))
|
|
result = out["runs"][0]["results"][0]
|
|
assert "locations" not in result or result.get("locations") == []
|
|
|
|
|
|
def test_sarif_output_is_valid_json():
|
|
sig = _signal("obfuscation", "low", artifact=Artifact("npm", "x", "1.0.0"), file="a.js")
|
|
report = ScanReport(artifacts=[sig.artifact], signals=[sig])
|
|
out = render_sarif(report)
|
|
parsed = json.loads(out)
|
|
assert isinstance(parsed["runs"], list)
|
|
assert isinstance(parsed["runs"][0]["results"], list)
|