feat(llm-security)!: v7.0.0 commit 1 — severity-dominated log-scaled risk score

Replace sum-and-cap formula (every non-trivial scan → 100/Extreme) with
severity-dominated, log-scaled-within-tier model. Discriminates actual
risk: 1 critical = 80, 2 critical = 86, 17 high = 65. Hyperframes-class
rendering codebases no longer collapse to Extreme just from shader noise.

Changes:
- scanners/lib/severity.mjs: new riskScore() v2; keep riskScoreV1() for
  reference; riskBand() cutoffs aligned (14/39/64/84).
- scanners/posture-scanner.mjs: delete inline duplicate formula, import
  riskScore/riskBand/verdict from severity.mjs. Single source of truth.

Breaking: aggregate.risk_score semantics change. Batched with entropy
suppression (Commit 2+) under v7.0.0 bump in Commit 6. Do not release
individually — JSON consumers depend on scoring band stability.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-19 22:00:29 +02:00
commit d83424a782
2 changed files with 60 additions and 28 deletions

View file

@ -13,7 +13,7 @@ import { readFile, readdir, stat, access } from 'node:fs/promises';
import { join, resolve, relative, extname } from 'node:path';
import { homedir } from 'node:os';
import { scanForInjection } from './lib/injection-patterns.mjs';
import { gradeFromPassRate, SEVERITY } from './lib/severity.mjs';
import { gradeFromPassRate, riskScore, riskBand, verdict, SEVERITY } from './lib/severity.mjs';
import { finding, scannerResult, resetCounter } from './lib/output.mjs';
// ---------------------------------------------------------------------------
@ -1489,21 +1489,10 @@ export async function scan(targetPath) {
const grade = gradeFromPassRate(passRate, failsInCritCats, counts.critical);
// Risk score
const riskScoreValue = Math.min(
counts.critical * 25 + counts.high * 10 + counts.medium * 4 + counts.low * 1,
100,
);
const riskBandValue =
riskScoreValue <= 20 ? 'Low' :
riskScoreValue <= 40 ? 'Medium' :
riskScoreValue <= 60 ? 'High' :
riskScoreValue <= 80 ? 'Critical' : 'Extreme';
const verdictValue =
counts.critical >= 1 || riskScoreValue >= 61 ? 'BLOCK' :
counts.high >= 1 || riskScoreValue >= 21 ? 'WARNING' : 'ALLOW';
// Risk score (delegated to severity.mjs — single source of truth, v7.0.0+)
const riskScoreValue = riskScore(counts);
const riskBandValue = riskBand(riskScoreValue);
const verdictValue = verdict(counts);
const durationMs = Date.now() - startMs;