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
7 KiB
CI/CD Integration Guide
Integrate llm-security into your CI/CD pipeline for automated security scanning of AI/LLM projects. The standalone CLI runs 14 deterministic Node.js scanners — no AI models, no external API calls, no data leaves your pipeline environment.
Data Sovereignty
The standalone CLI makes zero network calls by default. All 14 scanners operate locally on your source code using Shannon entropy analysis, regex pattern matching, AST traversal, and git log parsing. No data is transmitted to any external service.
Exception: supply-chain-recheck — When scanning lockfiles for known vulnerabilities, this scanner optionally queries the OSV.dev batch API. This sends only package names and versions (not source code) over HTTPS. There is no kill-switch for this call today; block the host at the network layer if the run must be air-gapped.
What about Claude Code integration? The Claude Code plugin (hooks, agents, commands) uses AI models and sends data to Anthropic. These components are not included in the standalone CLI. When you run npx llm-security scan, only deterministic scanners execute.
Schrems II / NSM Compliance
- Standalone CLI: fully compliant — no cross-border data transfer
- OSV.dev queries (opt-in): sends package metadata to Google-operated API — evaluate per your organization's data classification
- Claude Code plugin: sends code context to Anthropic (US) — requires data processing agreement for regulated environments
Norwegian Regulatory Context
- NSM Grunnprinsipper: Automated security scanning fulfills GP 3.1 (vulnerability management) and GP 2.4 (secure development)
- Digitaliseringsdirektoratet: Aligns with recommended practices for AI system development lifecycle security
- EU AI Act: Directly supports Art. 9 (risk management) and Art. 15 (cybersecurity) requirements. Note the Digital Omnibus (European Parliament 16 June 2026, Council 29 June 2026) deferred the high-risk obligations these articles sit under — to 2 December 2027 for stand-alone Annex III systems and 2 August 2028 for AI embedded in Annex I regulated products. Transparency obligations still apply from 2 August 2026, so this remains preparatory rather than deadline-driven work
5-Minute Setup
GitHub Actions
Copy ci/github-action.yml to .github/workflows/llm-security.yml:
name: LLM Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- run: npx llm-security scan . --fail-on high --format sarif --output-file results.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
SARIF results appear in the repository's Security tab under Code scanning alerts.
Azure DevOps
Copy ci/azure-pipelines.yml to your pipeline, or include the scan step in an existing pipeline:
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
- script: npx llm-security scan . --fail-on high --format sarif --output-file $(Build.ArtifactStagingDirectory)/results.sarif
displayName: Run llm-security scan
- task: PublishBuildArtifacts@1
condition: always()
inputs:
pathToPublish: $(Build.ArtifactStagingDirectory)/results.sarif
artifactName: llm-security-scan
For Azure DevOps Advanced Security, replace PublishBuildArtifacts@1 with AdvancedSecurity-Publish@1.
GitLab CI
Add to .gitlab-ci.yml:
llm-security-scan:
image: node:18-alpine
stage: test
script:
- npx llm-security scan . --fail-on high --format sarif --output-file results.sarif
artifacts:
paths:
- results.sarif
reports:
sast: results.sarif
when: always
SAST report parsing requires GitLab Ultimate. On Free/Premium tiers, download the SARIF artifact manually.
Configuration
CLI Flags
| Flag | Description |
|---|---|
--fail-on <severity> |
Exit 1 if any finding at or above severity exists. Values: critical, high, medium, low |
--compact |
One-liner per finding format. Reduces CI log noise |
--format sarif |
Output OASIS SARIF 2.1.0 (default: JSON) |
--output-file <path> |
Write full results to file. Stdout gets compact aggregate |
--baseline |
Diff against stored baseline (show new/resolved findings) |
--save-baseline |
Save current results as baseline for future diffs |
Policy File
Configure defaults in .llm-security/policy.json:
{
"ci": {
"failOn": "high",
"compact": true
}
}
CLI flags always take precedence over policy file values.
Environment Variables
| Variable | Description |
|---|---|
LLM_SECURITY_PRECOMPACT_MODE |
block|warn|off for the PreCompact transcript scan (default warn) |
LLM_SECURITY_UPDATE_CHECK=off |
Disable the daily update-check HTTP call |
The audit trail moved to the policy file in v8.0.0: set audit.log_path in
.llm-security/policy.json instead of LLM_SECURITY_AUDIT_LOG.
Exit Codes
| Code | Meaning | When |
|---|---|---|
0 |
Clean / below threshold | No findings at or above --fail-on level, or ALLOW verdict |
1 |
Threshold exceeded | Findings at or above --fail-on level, or WARNING verdict (without --fail-on) |
2 |
Block | BLOCK verdict (only without --fail-on) |
With --fail-on, exit codes are binary: 0 (clean) or 1 (threshold exceeded). Without --fail-on, the legacy tri-state (0/1/2) is preserved.
What Gets Scanned
The 14 deterministic scanners cover:
| Scanner | Detects |
|---|---|
| Unicode | Zero-width characters, homoglyphs, Unicode Tag steganography |
| Entropy | High-entropy strings (potential secrets/tokens) |
| Permission | Overly broad permissions, missing tool justification |
| Dependency | Known vulnerable packages, typosquats |
| Taint | Untrusted input flows to sensitive operations |
| Git forensics | Force pushes, sensitive file history, author anomalies |
| Network | Suspicious URLs, exfiltration endpoints, C2 patterns |
| Memory poisoning | Injection patterns in CLAUDE.md, memory files, rules |
| Supply chain | Lockfile audit, blocklists, OSV.dev (opt-in) |
| Workflow | CI/CD workflow injection — untrusted triggers, unpinned actions, spoofed bots |
| Trigger abuse | Activation-surface abuse in command/agent/skill frontmatter — shadowing, baiting, overly broad triggers |
| Signature | Known-malware identity match (webshells, reverse shells, cryptominers, hacktools) |
| AST taint | Scope-aware Python taint analysis (parse-only), falls back to regex taint tracing |
| Toxic flow | Lethal trifecta correlation (input + access + exfil) |
Local Testing
Test the exact same command locally before adding to CI:
# With npx (requires npm publish)
npx llm-security scan . --fail-on high --compact
# With local clone
node bin/llm-security.mjs scan . --fail-on high --compact