mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
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]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fb7594a3ed
commit
444d8c9490
@@ -58,6 +58,14 @@ class ScoreConfig:
|
||||
blocking: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RulesetConfig:
|
||||
# Emission floors that replace "every regex match becomes a finding". The
|
||||
# defaults (0.0) emit everything, keeping output byte-identical by default.
|
||||
min_emit_priority: float = 0.0
|
||||
min_emit_precision: float = 0.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ResolvedConfig:
|
||||
models: ModelConfig = ModelConfig()
|
||||
@@ -67,6 +75,7 @@ class ResolvedConfig:
|
||||
evidence: EvidenceConfig = EvidenceConfig()
|
||||
static_analysis: StaticAnalysisConfig = StaticAnalysisConfig()
|
||||
score: ScoreConfig = ScoreConfig()
|
||||
ruleset: RulesetConfig = RulesetConfig()
|
||||
runs_dir: str = ".openultrasast/runs"
|
||||
|
||||
|
||||
@@ -84,6 +93,7 @@ def load_config(config_path: Path | None = None) -> ResolvedConfig:
|
||||
evidence=_load_evidence(data.get("evidence", {})),
|
||||
static_analysis=_load_static_analysis(data.get("static_analysis", {})),
|
||||
score=_load_score(data.get("score", {})),
|
||||
ruleset=_load_ruleset_config(data.get("ruleset", {})),
|
||||
runs_dir=os.environ.get("OPENULTRASAST_RUNS_DIR", ".openultrasast/runs"),
|
||||
)
|
||||
|
||||
@@ -170,6 +180,14 @@ def _load_score(value: object) -> ScoreConfig:
|
||||
)
|
||||
|
||||
|
||||
def _load_ruleset_config(value: object) -> RulesetConfig:
|
||||
data = _section(value)
|
||||
return RulesetConfig(
|
||||
min_emit_priority=_float_value(data.get("min_emit_priority"), 0.0),
|
||||
min_emit_precision=_float_value(data.get("min_emit_precision"), 0.0),
|
||||
)
|
||||
|
||||
|
||||
def _float_value(value: object, default: float) -> float:
|
||||
if isinstance(value, bool):
|
||||
return default
|
||||
|
||||
Reference in New Issue
Block a user