Files
Open_Ultra_SAST/tests/test_score_pipeline.py
Marius CiepluchandClaude Opus 4.8 444d8c9490 feat(ruleset): Phase 2 (task 3) — rules as governed data, severity from policy
Externalize detection rules to versioned TOML and remove rule-local severity, so
rules detect and the central CWE policy decides severity — fixing "rules too
influential / not centrally managed". Zero-dependency; detection is byte-identical
(benchmark recall 44/47 = 93.6%, FP 0% unchanged) and the 90/10 gate holds.

- RulesetStore (ruleset/store.py + ruleset/<lang>/rules.toml): PatternRule as data
  (rule_id, title, languages, cwe, tags, pattern, status, min_evidence_level,
  precision_estimate, version) with NO severity; load_ruleset (sorted, with
  optional rule_policy.json ledger overlay) and a stdlib TOML write_ruleset. The
  32 rules were generated from the prior PATTERN_RULES with a verified exact
  round-trip (pattern/cwe/languages/tags/title).
- findings.py: drop the hardcoded PatternRule class + PATTERN_RULES tuple; load the
  default ruleset from TOML; quick_scan_findings(root, targets, rankings, ruleset,
  policy) resolves each finding's severity from policy via resolve_severity
  (SEVERITY_LABEL maps 0-5 -> string) and tags findings with their rule status.
- Three-status volume control: StaticFinding gains status (default "enabled");
  disabled rules never fire; shadow findings are partitioned out of the report and
  score in the CLI (written to shadow_findings.json) — no shadow rules by default.
- Emission floors (RulesetConfig.min_emit_priority / min_emit_precision, default
  0.0 = emit all) replace "every match is a finding".
- CLI loads the ruleset as a stage and uses it for the fail-loud policy check,
  detection, hunter pool, and the score CWE map. Wheel ships the ruleset TOML.

85 tests pass; ruff + mypy clean. Spec tasks 3.1-3.4 checked off.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 15:27:16 +02:00

43 lines
1.7 KiB
Python

"""Phase 1 in-pipeline wiring: project score artifact + fail-loud CWE invariant."""
import json
from pathlib import Path
from types import SimpleNamespace
import pytest
from openultrasast import cli
from openultrasast.cli import main
from openultrasast.policy import PolicyError
def test_scan_writes_score_artifact_and_manifest_block(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text("@app.route('/x')\ndef view():\n return eval(request.data)\n")
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
assert main(["scan", str(repo), "--mode", "quick"]) == 0
run_dir = sorted((repo / ".runs").iterdir())[-1]
score = json.loads((run_dir / "score.json").read_text())
manifest = json.loads((run_dir / "manifest.json").read_text())
assert isinstance(score["project_score"], int) and 0 <= score["project_score"] <= 100
assert set(score["gate"]) == {"min_score", "block_severity_reachable", "blocking", "passed"}
assert manifest["score"] == score
assert manifest["artifacts"]["score"] == "score.json"
def test_scan_fails_loud_when_an_enabled_rule_cwe_is_unmapped(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text("x = 1\n")
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
unmapped = SimpleNamespace(rule_id="bogus", cwe="CWE-99999", status="enabled")
real_loader = cli.load_ruleset
monkeypatch.setattr(cli, "load_ruleset", lambda *a, **k: (*real_loader(*a, **k), unmapped))
with pytest.raises(PolicyError):
main(["scan", str(repo), "--mode", "quick"])