feat(llm-security)!: v8 Phase 3 complete - riskScoreV1, posture heuristic, docs
Closes Phase 3 (B11) of the v8.0.0 plan. Three parts, all with the failing
test written first.
riskScoreV1 removed. scanners/lib/severity.mjs drops riskScoreV1() and its
SEVERITY_WEIGHTS_V1 table - @deprecated since v7.0.0, kept for diff/comparison,
zero callers in code or tests (re-verified, not taken from the plan). The v1
weights are recorded in CHANGELOG so an old score stays re-derivable. riskScore
(v2) is untouched; a test pins that one critical still lands in the 70-95 tier
and that 50 lows score below it, which is exactly the case v1 collapsed to 100.
Posture category 12 no longer keys off an identifier name. The check was
/TRIFECTA_MODE/i over the session-guard source, which measured what a constant
was CALLED rather than whether enforcement was configurable. With the env-var
gone, that regex would have dropped every correctly-migrated project from PASS
to PARTIAL - the gate punishing the migration it exists to encourage. It now
matches getPolicyValue('trifecta', 'mode', ...) and still accepts a pre-v8
vendored guard reading the old env-var, because a third-party project carries
its own hook copy and is equally configurable either way; the evidence line
says which of the two was found. The PARTIAL finding recommended setting an
env-var that v8 ignores; it now names the policy key. The grade-a fixture hook
moves to the policy-era form.
Two never-implemented env-vars deleted from the docs. LLM_SECURITY_SCR_OFFLINE
(ci-cd-guide) and LLM_SECURITY_OFFLINE (supply-chain-attack example) were
documented as OSV.dev / npm-audit kill-switches. No code has ever read either -
verified by grep across scanners, hooks and scripts, which finds them only in
markdown. A promised kill-switch that does nothing is worse than a documented
absence: it is trusted precisely when the run is meant to be air-gapped. The
docs now say there is none and that egress must be blocked at the network
layer. The LLM_SECURITY_AUDIT_* wildcard is narrowed to the one real key.
Docs. Migration section in README + CHANGELOG with the env-var -> policy-key
table, the detection commands (env + shell rc + .envrc + workflows), and the
explicit warning that a removed variable is now INERT rather than an error -
which is the failure mode that loses a project its configuration silently. The
hardening-guide env table splits into surviving vars and a removed-vars
migration table; its "promote to block" runbook named two variables that no
longer exist. Also swept: CLAUDE.md hook table, scanner-reference, ci-cd-guide,
both lethal-trifecta example docs, mitigation-matrix, injection-research.
Test counts in README/CLAUDE.md synced 2034 -> 2045.
Suite 2045 tests, 0 fail (2039 + 4 posture-trifecta + 2 riskScoreV1). The two
known parallel-load flakes did not recur this run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4vXvwvtW4dxbPRd6vsez
This commit is contained in:
parent
b6af9b46df
commit
fdec4b36ad
16 changed files with 333 additions and 62 deletions
|
|
@ -23,16 +23,33 @@ in production. Deviations are fine, but the defaults here are the tested path.
|
|||
|
||||
| Variable | Default | Modes |
|
||||
|----------|---------|-------|
|
||||
| `LLM_SECURITY_INJECTION_MODE` | `block` | `block` — exit 2 on critical/high injection patterns. `warn` — advisory via systemMessage. `off` — disables scan. |
|
||||
| `LLM_SECURITY_TRIFECTA_MODE` | `warn` | `block` — exit 2 when lethal trifecta (untrusted input + sensitive data + exfiltration sink) detected. `warn` — advisory. `off` — disables. |
|
||||
| `LLM_SECURITY_PRECOMPACT_MODE` | `warn` | `block` — exit 2 on findings during PreCompact. `warn` — advisory via systemMessage. `off` — disables scan. |
|
||||
| `LLM_SECURITY_PRECOMPACT_MAX_BYTES` | `512000` | Tail size in bytes read from transcript for scanning. Higher values increase coverage at the cost of latency. |
|
||||
| `LLM_SECURITY_UPDATE_CHECK` | `on` | `off` disables the daily update-check HTTP call. |
|
||||
| `LLM_SECURITY_AUDIT_*` | unset | Audit trail configuration (destination, format, etc.) for SIEM-ready JSONL output. |
|
||||
|
||||
Apply env vars via shell profile, `.envrc`, or the host MDM. Do not write them
|
||||
into the repository.
|
||||
|
||||
**Removed in v8.0.0.** Four modes moved to `.llm-security/policy.json`, which
|
||||
travels with the repository instead of the operator's shell. Setting the old
|
||||
variable is now inert — it neither warns nor configures.
|
||||
|
||||
| Removed variable | Policy key | Default |
|
||||
|------------------|-----------|---------|
|
||||
| `LLM_SECURITY_INJECTION_MODE` | `injection.mode` | `block` — exit 2 on critical injection patterns. `warn` — advisory via systemMessage. `off` — disables scan. |
|
||||
| `LLM_SECURITY_TRIFECTA_MODE` | `trifecta.mode` | `warn` — advisory. `block` — exit 2 when the lethal trifecta (untrusted input + sensitive data + exfiltration sink) is detected. `off` — disables. |
|
||||
| `LLM_SECURITY_ESCALATION_WINDOW` | `trifecta.escalation_window` | `5` — calls between untrusted input and sub-agent delegation that still count as escalation-after-input. |
|
||||
| `LLM_SECURITY_AUDIT_LOG` | `audit.log_path` | unset — set a path to write the SIEM-ready JSONL audit trail. |
|
||||
| `LLM_SECURITY_DEPRECATION_QUIET` | *(none)* | Silenced the deprecation warning; removed with the warning. |
|
||||
|
||||
```json
|
||||
{
|
||||
"injection": { "mode": "block" },
|
||||
"trifecta": { "mode": "warn", "escalation_window": 5 },
|
||||
"audit": { "log_path": "/var/log/llm-security/audit.jsonl" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Sandboxing
|
||||
|
|
@ -73,14 +90,16 @@ shell; avoid root, which disables user-namespace confinement.
|
|||
### 3.1 Start in warn mode
|
||||
|
||||
Every new integration of `llm-security` should begin with all modes set to
|
||||
`warn`. This yields advisories without breaking workflow, and lets the team
|
||||
calibrate false-positive rates against their actual repositories.
|
||||
`warn` — `injection.mode` and `trifecta.mode` in `.llm-security/policy.json`,
|
||||
`LLM_SECURITY_PRECOMPACT_MODE` in the environment. This yields advisories
|
||||
without breaking workflow, and lets the team calibrate false-positive rates
|
||||
against their actual repositories.
|
||||
|
||||
### 3.2 Promote to block after baselining
|
||||
|
||||
After a baseline period (typically 1-2 weeks), flip each mode to `block` in this
|
||||
order: `LLM_SECURITY_INJECTION_MODE`, `LLM_SECURITY_TRIFECTA_MODE`,
|
||||
`LLM_SECURITY_PRECOMPACT_MODE`. The injection hook is first because false
|
||||
order: `injection.mode`, `trifecta.mode` (both in `.llm-security/policy.json`),
|
||||
then `LLM_SECURITY_PRECOMPACT_MODE`. The injection hook is first because false
|
||||
positives there are the most visible; blocking comes last because the others
|
||||
build confidence.
|
||||
|
||||
|
|
@ -183,7 +202,9 @@ tier:
|
|||
|
||||
Verdict cutoffs (`BLOCK ≥65`, `WARNING ≥15`) are locked to the `riskBand()`
|
||||
boundaries so you can't get a "BLOCK / Medium band" contradiction. The legacy
|
||||
formula is kept as `riskScoreV1()` for reference only.
|
||||
v1 formula was kept as `riskScoreV1()` for reference through the v7 line and
|
||||
was removed in v8.0.0; see `CHANGELOG.md` for the v1 weights if you need them
|
||||
to re-derive an old score.
|
||||
|
||||
**CI impact:** Pipelines with `--fail-on high` keep working (the severity
|
||||
gate is unaffected). Pipelines with score-based thresholds need recalibration
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue