ci: add quality gates and release workflows

This commit is contained in:
Marius Ciepluch
2026-06-18 10:06:21 +02:00
parent 5519413c82
commit 1e6c1f4436
22 changed files with 1606 additions and 77 deletions
+12 -2
View File
@@ -66,9 +66,19 @@ def test_ranking_metrics_track_false_positive_reasons_by_tier() -> None:
metrics = compute_ranking_metrics(
[
{"tier": "A", "outcome": FindingOutcome.VERIFIED, "time_to_verification_seconds": 10},
{"tier": "A", "outcome": FindingOutcome.REJECTED, "reason": FalsePositiveReason.UNREACHABLE_PATH, "time_to_verification_seconds": 20},
{
"tier": "A",
"outcome": FindingOutcome.REJECTED,
"reason": FalsePositiveReason.UNREACHABLE_PATH,
"time_to_verification_seconds": 20,
},
{"tier": "B", "outcome": FindingOutcome.REJECTED, "reason": FalsePositiveReason.DUPLICATE},
{"tier": "A", "outcome": FindingOutcome.UNVERIFIED, "reason": FalsePositiveReason.UNVERIFIED, "missed_fixture_vulnerabilities": 1},
{
"tier": "A",
"outcome": FindingOutcome.UNVERIFIED,
"reason": FalsePositiveReason.UNVERIFIED,
"missed_fixture_vulnerabilities": 1,
},
],
tier="A",
)
+1
View File
@@ -74,6 +74,7 @@ def test_quick_hunter_prompt_is_bounded() -> None:
repo = Path("/repo/example.py")
target = next(iter(preprocess_repository(repo.parent)[1]), None) if repo.parent.exists() else None
assert target is None
class Target:
path = "example.py"
language = "python"
+3 -7
View File
@@ -34,9 +34,7 @@ class BadWriteProcessor:
def test_runtime_emits_stage_events_and_processor_state(tmp_path: Path) -> None:
trace_path = tmp_path / "events.jsonl"
processor = RecordingProcessor(
ProcessorSpec(name="recorder", version="1", handles=("stage_start",), writes=("seen_stage",))
)
processor = RecordingProcessor(ProcessorSpec(name="recorder", version="1", handles=("stage_start",), writes=("seen_stage",)))
runtime = HarnessRuntime(
scan_id="scan-1",
config=load_config(None),
@@ -84,10 +82,8 @@ def test_warn_contract_mode_records_degradation(tmp_path: Path) -> None:
def test_harness_config_serializes_models_processors_and_prompt_hashes(tmp_path: Path) -> None:
config_path = tmp_path / "openultrasast.toml"
config_path.write_text("[models]\nranker = \"openrouter/ranker\"\n")
processor = RecordingProcessor(
ProcessorSpec(name="recorder", version="2026-01-01", handles=("stage_start",), writes=("seen_stage",))
)
config_path.write_text('[models]\nranker = "openrouter/ranker"\n')
processor = RecordingProcessor(ProcessorSpec(name="recorder", version="2026-01-01", handles=("stage_start",), writes=("seen_stage",)))
output = tmp_path / "harness.json"
write_harness_config(
+1 -1
View File
@@ -3,8 +3,8 @@ from pathlib import Path
import pytest
from openultrasast.index import CodeChunk
from openultrasast.index import (
CodeChunk,
build_code_chunks,
build_retrieval_package,
build_vector_index,
+19 -17
View File
@@ -62,18 +62,24 @@ def test_mapping_task_records_cover_disciplines(tmp_path: Path) -> None:
assert semgrep_mapping_tasks(semgrep_hints)[0].task == "pattern_variant_review"
assert codeql_mapping_tasks(codeql_hints)[0].task == "source_sink_sanitizer_path_review"
assert differential_mapping_record(
"parser.c",
change_kind="modified_function",
trust_boundary="network_input",
rationale="Parser changed behind a socket boundary.",
).trust_boundary == "network_input"
assert sharp_edge_record(
"config.py",
category="insecure_default",
surface="debug mode",
rationale="Debug default changes production risk.",
).category == "insecure_default"
assert (
differential_mapping_record(
"parser.c",
change_kind="modified_function",
trust_boundary="network_input",
rationale="Parser changed behind a socket boundary.",
).trust_boundary
== "network_input"
)
assert (
sharp_edge_record(
"config.py",
category="insecure_default",
surface="debug mode",
rationale="Debug default changes production risk.",
).category
== "insecure_default"
)
def test_write_static_hints_artifact(tmp_path: Path) -> None:
@@ -144,11 +150,7 @@ def test_entry_point_mapping_covers_solidity_state_changing_access(tmp_path: Pat
def test_entry_point_mapping_records_solidity_feature_conditions(tmp_path: Path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "Vault.sol").write_text(
"contract Vault {\n"
" function withdraw(uint256 amount) external onlyOwner whenNotPaused { }\n"
"}\n"
)
(repo / "Vault.sol").write_text("contract Vault {\n function withdraw(uint256 amount) external onlyOwner whenNotPaused { }\n}\n")
_, targets = preprocess_repository(repo)
entry_points = analyze_entry_points(repo, targets)
+15 -16
View File
@@ -93,12 +93,7 @@ def test_cli_scan_writes_accepted_and_rejected_verifications(tmp_path: Path, mon
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text(
"@app.route('/admin')\n"
"def admin():\n"
" return eval(request.data)\n"
"\n"
"def helper(value):\n"
" return exec(value)\n"
"@app.route('/admin')\ndef admin():\n return eval(request.data)\n\ndef helper(value):\n return exec(value)\n"
)
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
@@ -113,16 +108,20 @@ def test_cli_scan_writes_accepted_and_rejected_verifications(tmp_path: Path, mon
def _finding(*, evidence_level: str, reachability_status: str) -> StaticFinding:
evidence = [
{
"kind": "route",
"access_level": "public",
"line": 1,
"end_line": 3,
"function_name": "admin",
"conditions": [],
}
] if reachability_status == "reachable" else []
evidence = (
[
{
"kind": "route",
"access_level": "public",
"line": 1,
"end_line": 3,
"function_name": "admin",
"conditions": [],
}
]
if reachability_status == "reachable"
else []
)
return StaticFinding(
finding_id="python-unsafe-eval:app.py:3",
path="app.py",