chore(llm-security): v7.7.2 — language consistency pass

~/.claude/CLAUDE.md specifies English for code and documentation,
Norwegian for dialog only. Norwegian had crept into surface text
across v7.5-v7.7. Translated to English in eight surfaces.

No scanner, hook, or behavior changes — purely surface text.

- 18 skill commands: the HTML Report-step now reads "HTML report:
  [Open in browser]" instead of "HTML-rapport: [Åpne i nettleser]"
- scripts/lib/report-renderers.mjs: key-stat labels, lede defaults,
  table headers, maturity-ladder descriptions, action-tier labels,
  clean buckets, dry-run/apply copy, and JS comments. Regex
  alternations /^high|^høy/ and /resolution|løsning/i preserved.
- playground/llm-security-playground.html: same renderer changes
  mirrored bit-identical, plus playground-only UI strings (catalog,
  breadcrumb aria-label, theme toggle, builder-modal hint,
  guide-panel "no projects yet", delete confirmation, alert/copy).
  Demo-state fixture content for dft-komplett-demo preserved
  (intentional Norwegian persona).
- agents/skill-scanner-agent.md + agents/mcp-scanner-agent.md:
  Generaliseringsgrense + Parallell Read-strategi sections translated
  to Generalization boundary + Parallel Read strategy.
- README.md: playground architecture prose + Recent versions table
  (v7.5.0 — v7.7.1).
- CLAUDE.md: v7.7.1 highlights translated, new v7.7.2 highlights
  added.
- ../../README.md: llm-security v7.5.0 — v7.7.1 bullets.
- ../../CLAUDE.md: llm-security catalog entry.
- docs/scanner-reference.md: six runnable-examples table cells.
- docs/version-history.md: new v7.7.2 entry. v7.5-v7.7 narrative
  sections left in original language (deferred per operator).
- Version bumped 7.7.1 → 7.7.2 in package.json,
  .claude-plugin/plugin.json, README badge + Recent versions,
  CLAUDE.md header + state, docs/version-history.md, playground
  renderHome hardcoded string, root README + CLAUDE.md llm-security
  entries.

Tests: 1820/1820 green. CLI smoke-test: 18/18 commandIds produce
>138 KB self-contained HTML. Browser-dogfood verified.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-19 06:47:44 +02:00
commit 03b8885b6e
31 changed files with 467 additions and 359 deletions

View file

@ -1,5 +1,5 @@
{
"name": "llm-security",
"description": "Security scanning, auditing, and threat modeling for Claude Code projects. Detects secrets, validates MCP servers, assesses security posture, and generates threat models aligned with OWASP LLM Top 10.",
"version": "7.7.1"
"version": "7.7.2"
}

View file

@ -6,6 +6,83 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
## [7.7.2] - 2026-05-19
Language consistency pass. Norwegian had crept into surface text across
v7.5-v7.7. Per the `~/.claude/CLAUDE.md` convention (English for code and
documentation, Norwegian for dialog only), surface text was translated to
English. No scanner, hook, or behavior changes — purely surface text.
### Changed
- **18 skill commands `commands/*.md`** — the "HTML Report"-step appended
by each `/security <cmd>` flow now reads
`> **HTML report:** [Open in browser](file:///abs/path.html)` (previously
Norwegian).
- **CLI canonical module `scripts/lib/report-renderers.mjs`** — translated
KEY_STATS_CONFIG labels (`TOTALT``TOTAL`, `KRITISK``CRITICAL`,
`HØY``HIGH`, `FUNN``FINDINGS`, `PROSJEKTER``PROJECTS`,
`MASKINKLASSE``MACHINE GRADE`, `SVAKEST``WEAKEST`,
`NÅ-GRADE``CURRENT GRADE`, `AKSJONER``ACTIONS`, `MODUS``MODE`),
the 5-step maturity ladder descriptions, the suppressed-group desc,
4 table-header sets, the 6 renderer `lede` defaults (plugin-audit,
mcp-audit, harden, diff, watch, clean), the action tier labels
(Umiddelbar/Høy prioritet/Medium prioritet → Immediate/High priority/
Medium priority), the clean buckets, and the dry-run/apply text. JS
comments translated for consistency. Preserved the regex alternations
`/^high|^høy/` and `/resolution|løsning/i` — they intentionally match
Norwegian-language report markdown.
- **Playground `playground/llm-security-playground.html`** — the same
display strings as the canonical module (kept bit-identical), plus
playground-specific UI text: catalog row labels, search placeholder,
breadcrumb aria-label, theme-toggle labels, primary nav aria-label,
builder-modal hints, "no projects yet" guide-panel, delete-project
confirmation, alert/copy-confirm strings, and the field-from-tag
"felles" pill (now "shared"). The hardcoded `Plugin v7.7.1` in
`renderHome` bumped to `Plugin v7.7.2`, and `prosjekter`/`kommandoer`
there became `projects`/`commands`. Demo-state fixture content for the
`dft-komplett-demo` project (intentional Norwegian persona) and regex
tokens were preserved.
- **Agent prompts `agents/skill-scanner-agent.md` +
`agents/mcp-scanner-agent.md`** — translated the `Generaliseringsgrense`
and `Parallell Read-strategi` sections (identical content in both files)
to `Generalization boundary` and `Parallel Read strategy`.
- **`README.md`** — translated the Recent versions table rows for v7.5.0
→ v7.7.1 and the playground architecture prose (L495-553). Version
badge bumped to 7.7.2.
- **`CLAUDE.md`** — translated the v7.7.1 highlights paragraph and added a
new v7.7.2 highlights paragraph. Header and "release notes" sentinel
bumped to v7.7.2.
- **Marketplace root `../../README.md`** — translated the v7.5.0 → v7.7.1
llm-security bullet entries (lines 39-43). Version label in the
header bumped to v7.7.2. The voyage and ms-ai-architect entries on
lines 90-91 / 192-197 were not touched (strict plugin scope).
- **Marketplace root `../../CLAUDE.md`** — translated the llm-security
catalog entry on line 13 and bumped its version to v7.7.2.
- **`docs/scanner-reference.md`** — translated the six runnable-examples
table cells (L114-122) and the surrounding paragraph.
- **`docs/version-history.md`** — added a v7.7.2 entry describing this
pass. The v7.5.0 → v7.7.1 narrative sections retain the Norwegian they
were written in (deferred per operator decision).
- **`package.json` + `.claude-plugin/plugin.json`** — version 7.7.1 →
7.7.2.
### Preserved (intentional Norwegian)
- Demo-state `dft-komplett-demo` JSON `description`, `system_description`,
and parsed-data `"label": "HØY"` / `"label": "NÅ-GRADE"` entries —
intentional Norwegian persona for the public-sector reference scenario.
- Regex alternations `/^high|^høy/` and `/resolution|løsning/i` in both
the canonical renderer and the playground inline copy — they let
reports written in Norwegian still parse and route correctly.
- `knowledge/norwegian-context.md` and other knowledge files — out of
scope.
- The v7.5.0 → v7.7.1 entries in CHANGELOG.md and `docs/version-history.md`
remain in the language they were written in; rewriting historical
release notes was deferred.
- `REMEMBER.md`, `TODO.md`, `ROADMAP.md`, `*.local.md`, commit messages,
test fixtures, and the `playground/A11Y-RAPPORT.md` artifact.
## [7.7.1] - 2026-05-18
Playground UX-strip etter v7.7.0-operatør-feedback. Hjem-overflaten ledet

View file

@ -1,12 +1,14 @@
# LLM Security Plugin (v7.7.1)
# LLM Security Plugin (v7.7.2)
Security scanning, auditing, and threat modeling for Claude Code projects. 5 frameworks: OWASP LLM Top 10, Agentic AI Top 10 (ASI), Skills Top 10 (AST), MCP Top 10, AI Agent Traps (DeepMind). 1820+ unit, integration, and end-to-end tests (`tests/e2e/` covers the multi-hook attack chain, multi-session state simulation, and the full scan-orchestrator pipeline); mutation-testing coverage not published.
Release notes for v7.0.0 → v7.7.1: see `docs/version-history.md` — read on demand.
Release notes for v7.0.0 → v7.7.2: see `docs/version-history.md` — read on demand.
**v7.7.1 highlights** — Playground UX-strip etter operatør-feedback: katalog er nå eneste levende overflate (onboarding/home/project-render-funksjonene er bevart i kildekoden men ikke rutbare før funksjonalitet legges til igjen). Topbar-breadcrumb leser ikke lenger demo-state-orgnavn; viser nøytralt `llm-security · Katalog`. Hardkodet versjons-streng i `renderHome` synket. Ingen scanner- eller hook-atferdsendringer.
**v7.7.2 highlights** — Language consistency pass. Norwegian had crept into the playground UI strings, the canonical CLI renderer (`scripts/lib/report-renderers.mjs`), the HTML Report-step appended by all 18 skill commands, two agent prompts, and the marketplace + plugin README/CLAUDE.md state sections. Per the `~/.claude/CLAUDE.md` convention (English for code and documentation, Norwegian for dialog only), surface text was translated to English. Demo-state fixture content for the `dft-komplett-demo` project (intentional Norwegian persona) and regex alternations that match Norwegian-language report markdown (`/^high|^høy/`, `/resolution|løsning/`) were preserved. No scanner, hook, or behavior changes.
**v7.7.0 highlights** — All 18 report-producing skill commands now emit a clickable `file://` link to a self-contained HTML version of their markdown report. The new `scripts/render-report.mjs` CLI converts any of the 18 report types via a canonical `scripts/lib/report-renderers.mjs` (18 parsers + 18 renderers, bit-identical to the playground). HTML wraps the Tier 1/2/3 design system inline; no external assets, system fonts only (~140 KB per report). Playground also got list-view, copy-button, and prosjekt-surface cleanup.
**v7.7.1 highlights** — Playground UX strip after operator feedback: the catalog is now the only routable surface (the onboarding/home/project render functions remain in source but are not routable until the feature is restored). The topbar breadcrumb no longer reads the demo-state org name; it shows a neutral `llm-security · Catalog`. The hardcoded version string in `renderHome` was synced. No scanner or hook behavior changes.
**v7.7.0 highlights** — All 18 report-producing skill commands now emit a clickable `file://` link to a self-contained HTML version of their markdown report. The new `scripts/render-report.mjs` CLI converts any of the 18 report types via a canonical `scripts/lib/report-renderers.mjs` (18 parsers + 18 renderers, bit-identical to the playground). HTML wraps the Tier 1/2/3 design system inline; no external assets, system fonts only (~140 KB per report). Playground also got list-view, copy-button, and project-surface cleanup.
## Commands

View file

@ -6,7 +6,7 @@
*AI-generated: all code produced by Claude Code through dialog-driven development. [Full disclosure →](../../README.md#ai-generated-code-disclosure)*
![Version](https://img.shields.io/badge/version-7.7.1-blue)
![Version](https://img.shields.io/badge/version-7.7.2-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Commands](https://img.shields.io/badge/commands-20-orange)
![Agents](https://img.shields.io/badge/agents-6-orange)
@ -492,16 +492,16 @@ a browser (Chrome/Firefox/Safari over `file://`) — no build step, no
network calls, no npm install. Theme-bootstrap with FOUC-prevention; state
persisted in IndexedDB primary + localStorage fallback.
**v7.6.0 Tier 3-referanse-case:** Playgroundet er nå en visuelt og
strukturelt fullført referanse for `shared/playground-design-system/`
Tier 3-supplementet. 8 nye DS-komponenter integrert i de 18 rapport-
rendererne: `tfa-flow` (lethal trifecta-kjede), `mat-ladder` (modenhets-
stige), `suppressed-group` (narrative-audit), `codepoint-reveal` (Unicode-
steganografi), `top-risks` (rangert top-funn), `recommendation-card[data-
severity]` (severity-tinted advisory), `risk-meter` (band-visualisering
0-100), `card--severity-{level}` (severity-color findings-cards). Pluss
`badge--scope-security`, `verdict-pill-lg` og `form-progress`+`fp-step`
fra wave 1.
**v7.6.0 Tier 3 reference case:** The playground is now a visually and
structurally complete reference for the `shared/playground-design-system/`
Tier 3 supplement. 8 new DS components integrated into the 18 report
renderers: `tfa-flow` (lethal trifecta chain), `mat-ladder` (maturity
ladder), `suppressed-group` (narrative audit), `codepoint-reveal` (Unicode
steganography), `top-risks` (ranked top findings), `recommendation-card
[data-severity]` (severity-tinted advisory), `risk-meter` (band
visualization 0-100), `card--severity-{level}` (severity-color findings
cards). Plus `badge--scope-security`, `verdict-pill-lg`, and
`form-progress`+`fp-step` from wave 1.
**Layout:**
@ -509,48 +509,50 @@ fra wave 1.
playground/
├── llm-security-playground.html ← single-file SPA (~10 700 lines)
├── vendor/
│ └── playground-design-system/ ← synket fra shared/, sjekksum-låst
├── test-fixtures/ ← markdown-fixtures (én per kommando)
├── screenshots/v7.5.0/ ← Playwright-genererte demobilder (12)
├── screenshots/v7.6.0/ ← v7.6.0 demobilder (12, manuelt generert)
└── A11Y-RAPPORT.md ← WCAG 2.1 AA verifisering + Tier 3 ARIA
│ └── playground-design-system/ ← synced from shared/, checksum-locked
├── test-fixtures/ ← markdown fixtures (one per command)
├── screenshots/v7.5.0/ ← Playwright-generated demo images (12)
├── screenshots/v7.6.0/ ← v7.6.0 demo images (12, hand-generated)
└── A11Y-RAPPORT.md ← WCAG 2.1 AA verification + Tier 3 ARIA
```
**Hva playgroundet dekker:**
**What the playground covers:**
- **Onboarding (5 grupper):** organisasjon, scope, profil, plattform,
compliance. Verdier persisteres som `shared`-state og prefylles automatisk
i alle command-skjemaer.
- **Home:** prosjekt-grid, fleet-tracks for posture/scan/red-team. «Last
inn demo-data»-knappen aktiverer 3 prosjekter inkludert `dft-komplett-demo`
med alle 18 rapporter ferdig parsed.
- **Catalog:** alle 20 kommandoer gruppert i 5 kategorier. Søk filtrerer
cards, og «Åpne skjema»-knapp bygger ferdig pipeline-streng for klipp-og-
lim til terminalen.
- **Project surface:** 4 skjermer (Oversikt / Rapporter / Kontekst /
Eksport). Rapporter-tabben har category-tabs (discover / posture /
findings-ops / hardening / adversarial / mcp-ops) og lim-inn-import for
hver rapport-kommando.
- **Onboarding (5 groups):** organization, scope, profile, platform,
compliance. Values persist as `shared` state and prefill every command
form automatically.
- **Home:** project grid, fleet tracks for posture/scan/red-team. The
"Load demo data" button activates 3 projects, including
`dft-komplett-demo` with all 18 reports parsed in advance.
- **Catalog:** all 20 commands grouped into 5 categories. Search filters
cards, and the "Open form" button builds a ready-to-paste pipeline
string for the terminal.
- **Project surface:** 4 screens (Overview / Reports / Context / Export).
The Reports tab has category tabs (discover / posture / findings-ops /
hardening / adversarial / mcp-ops) and paste-import for every report
command.
**Parser/renderer-arkitektur:** Hver `produces_report=true`-kommando i
`CATALOG` har en parser (markdown → struktur) og en renderer (struktur
→ DS-komponenter). 18 archetypes støttes: `findings`, `findings-grade`,
`risk-score-meter`, `posture-cards`, `dashboard-fleet`, `red-team-results`,
`diff-report`, `kanban-buckets`, `matrix-risk`. Parser-kontrakten er
`{ ok: true, data: {...} } | { ok: false, errors: [...] }`. Test-fixtures
under `playground/test-fixtures/` er kontrakt-anker — én markdown-fil per
kommando som speiler `templates/unified-report.md`-formatet.
**Parser/renderer architecture:** Every `produces_report=true` command in
`CATALOG` has a parser (markdown → structure) and a renderer (structure
→ DS components). 18 archetypes are supported: `findings`,
`findings-grade`, `risk-score-meter`, `posture-cards`, `dashboard-fleet`,
`red-team-results`, `diff-report`, `kanban-buckets`, `matrix-risk`. The
parser contract is `{ ok: true, data: {...} } | { ok: false, errors:
[...] }`. The test fixtures under `playground/test-fixtures/` are the
contract anchor — one markdown file per command, mirroring the
`templates/unified-report.md` format.
**Eksponerte testing/automasjons-globaler:** `__store`, `__navigate`,
**Exposed testing/automation globals:** `__store`, `__navigate`,
`__loadDemoState`, `__scheduleRender`, `__PARSERS`, `__RENDERERS`,
`__CATALOG`, `__inferVerdict`, `__inferKeyStats`, `__renderPageShell`,
`__handlePasteImport`. Aktiverer Playwright-styrt navigasjon og
programmatisk parser/renderer-test mot fixture-katalogen.
`__handlePasteImport`. They enable Playwright-driven navigation and
programmatic parser/renderer tests against the fixture catalog.
**Begrensninger:** SPA er en lim-inn-overflate — den kjører ingen scannere
selv. Output må komme fra Claude Code (`/security scan ...`), CLI
(`node scanners/...`) eller stub-fixtures. Demo-state inneholder kun de
3 inline-prosjektene; nye prosjekter er per-bruker og lagres lokalt.
**Limitations:** The SPA is a paste-in surface — it does not run any
scanners itself. Output must come from Claude Code (`/security scan
...`), the CLI (`node scanners/...`), or stub fixtures. Demo state only
contains the 3 inline projects; new projects are per-user and stored
locally.
---
@ -626,11 +628,12 @@ demonstrations — each with `README.md`, fixture, run script, and
| Version | Date | Highlights |
|---------|------|------------|
| **7.7.1** | 2026-05-18 | **Playground UX-strip.** Operatør-feedback umiddelbart etter v7.7.0: hjem-overflaten ledet med tre prosjekt-tracks (Re-onboard / Nytt prosjekt / Command-katalog) selv om katalog var det viktige. Minimum-strip levert som tre atomic commits (`b732eee` + `2a6f73f` + `81b7beb`): (1) router tvinger alltid `activeSurface = 'catalog'` (onboarding/home/project-render-funksjonene bevart men ikke rutbare); (2) topbar `Hjem` + `Re-onboard`-knapper fjernet, `Katalog` beholdt; (3) breadcrumb-orgname (`shared.organization.name` fra demo-state) erstattet med statisk `llm-security` som nøytralt scope-anker. Fix: hardkodet `'Plugin v7.6.1'` på linje 6933 i `renderHome` (template-litteral som v7.7.0-grep-en ikke fanget) synket. Onboarding-konseptet dokumentert som v7.8.0-kandidat (per-kommando kontekst-injeksjon) i `ROADMAP.md`. Ingen scanner- eller hook-atferdsendringer. |
| **7.7.0** | 2026-05-18 | **HTML-rapport for alle 18 skill-kommandoer.** Hver `/security <cmd>` som produserer rapport printer nå en klikkbar `file://`-lenke til en self-contained HTML-versjon. Levert over 5 sesjoner. (1) Playground katalog list-view + builder-pane med copy-knapp. (2) Playground prosjekt-surface opprydding (stub-screen-håndtering, topbar-splitt). (3) De 18 inline parserne + rendererne i playground-HTML flyttet til canonical ESM-modul `scripts/lib/report-renderers.mjs` (playground beholder bit-identisk inline-kopi siden ESM `import` ikke fungerer fra `file://`). (4) Ny zero-dep CLI `scripts/render-report.mjs` — stdin/file/stdout-modus, kebab→camel commandId-routing, inliner 6 DS-stylesheets + lokal `.report-table`-CSS, ~140 KB self-contained HTML, system-font-fallback, absolutte `file://`-paths for Ghostty cmd-click. (5) Alle 18 skills wired (4 i sesjon 4: scan/audit/posture/deep-scan, 14 i sesjon 5: plugin-audit/mcp-audit/mcp-inspect/ide-scan/supply-check/dashboard/pre-deploy/diff/watch/registry/clean/harden/threat-model/red-team). Output: `reports/<command>-<YYYYMMDD-HHmmss>.html` relativt til CWD. Ingen scanner- eller hook-atferdsendringer — purely additive. |
| **7.6.1** | 2026-05-06 | **Playground v7.6.0 visuell-patch.** Seks bugs fanget under maintainer-verifisering i nettleser. Alle skyldtes mismatch mellom DS-klasser og rendrer-bruk (eller manglende DS-implementasjoner playground antok eksisterte). (1) `renderFindingsBlock` brukte `.findings` outer som er DS' 2-kolonners list+detail-grid → erstattet med `<section class="report-meta">` + korrekt `findings__list > findings__group`-mønster. (2) `.report-table` manglet helt i DS men brukes i 7+ rendrere → lokal CSS-implementasjon i playground-HTML. (3) `renderPreDeploy` traffic-lights brukte `.sm-card__grade` (28×28 px for én A-F-bokstav) for "PASS"/"PASS-WITH-NOTES"/"FAIL" → erstattet med bredde-tilpasset status-pill. (4) Threat-model matrix-bobler ikke klikkbare → `<button>` med `data-threat-id` + click-handler som scroller til Trusler-tabellen. (5) Radar-labels overlappet ved 6+ akser → SVG 280→380, R 105→125, dynamisk `text-anchor` (start/end/middle) basert på horisontal-posisjon. (6) `recommendation-card__body` overflow på lange tekster → `overflow-wrap: anywhere`. 4/4 fix-spesifikke smoke-tester + 18/18 renderer-regresjon passerer. Ingen scanner- eller hook-atferdsendringer — purely additive surface. |
| **7.6.0** | 2026-05-06 | **Playground Tier 3-referanse-case.** Playground (`playground/llm-security-playground.html`) hevet til visuelt og strukturelt fullført referanse for `shared/playground-design-system/` Tier 3-supplementet. 8 nye DS-komponenter integrert i de 18 rapport-rendererne: `tfa-flow` + `tfa-leg` + `tfa-arrow` (lethal trifecta-kjede med `<button>`-elementer + ARIA), `mat-ladder` + `mat-step` (5-trinns modenhets-stige med terskler 0/25/50/75/95% PASS), `suppressed-group` (narrative-audit fra `summary.narrative_audit.suppressed_findings`), `codepoint-reveal` + `cp-tag`/`cp-zw`/`cp-bidi` (Unicode-steganografi side-ved-side), `top-risks` + `top-risk[data-severity]` (rangert top-funn-listing, semantisk `<ol>`), `recommendation-card[data-severity]` (severity-tinted advisory på `clean`/`harden`/`audit`/`posture`/`pre-deploy`/`plugin-audit`), `risk-meter` (band-visualisering 0-100 på 5 archetypes), `card--severity-{level}` (severity-color modifier på findings-cards). Wave 1: `badge--scope-security` (identitets-chip), `verdict-pill-lg` (DS Tier 3-pill), `form-progress` + `fp-step` (onboarding-wizard). Slettet ~30 duplikat-CSS-deklarasjoner (DS vinner cascade). 5 nye DS-helpers + `mapSeverityToCardLevel` + `parseNarrativeAudit`. Filendring 10209 → 10677 linjer. Levert over 5 sesjoner, atomic commits. A11Y-rapport oppdatert. Ingen scanner- eller hook-behavior-changes — purely additive surface. |
| **7.5.0** | 2026-05-05 | **Playground.** Single-file SPA at `playground/llm-security-playground.html` (~10 200 lines) for onboarding, demoer og workshop-bruk uten Claude Code-installasjon. Parsere + renderere for alle 18 `produces_report=true`-kommandoer (Fase 2: 10 høy-prio + Fase 3: 8 gjenstående: mcp-inspect, supply-check, pre-deploy, diff, watch, registry, clean, threat-model). 18 markdown test-fixtures under `playground/test-fixtures/` som kontrakt-anker. Komplett demo-prosjekt `dft-komplett-demo` har alle 18 rapporter ferdig parsed inline. Vendor-synket design-system under `playground/vendor/` (sjekksum-låst). 9 Playwright-genererte screenshots i `playground/screenshots/v7.5.0/`. 11 nye `window`-globaler for testing/automasjon. 2 nye `KEY_STATS_CONFIG`-archetypes (`kanban-buckets`, `matrix-risk`). Bug-fix: `normalizeVerdictText` regex-rekkefølge oppdatert så GO-WITH-CONDITIONS / CONDITIONAL / BETINGET ikke lenger kollapser til ALLOW. Ingen scanner- eller hook-behavior-changes — purely additive surface. |
| **7.7.2** | 2026-05-19 | **Language consistency pass.** Norwegian had crept into surface text across v7.5-v7.7. Per the `~/.claude/CLAUDE.md` convention (English for code and documentation, Norwegian for dialog only), this release translates: the HTML Report-step appended by all 18 skill commands, the canonical CLI renderer `scripts/lib/report-renderers.mjs` (display strings + JS comments), the playground UI strings, the `skill-scanner-agent` and `mcp-scanner-agent` system prompts, the Recent versions table and playground architecture prose in this README, the v7.7.x highlights in `CLAUDE.md`, and the llm-security entries in the marketplace root `README.md` + `CLAUDE.md`, plus six table cells in `docs/scanner-reference.md`. Demo-state fixture content for the `dft-komplett-demo` project (intentional Norwegian persona) and regex alternations that match Norwegian-language report markdown (`/^high\|^høy/`, `/resolution\|løsning/`) were preserved. No scanner, hook, or behavior changes — purely surface text. |
| **7.7.1** | 2026-05-18 | **Playground UX strip.** Operator feedback immediately after v7.7.0: the home surface led with three project tracks (Re-onboard / New project / Command catalog) even though the catalog was the important entry point. Minimum strip delivered as three atomic commits (`b732eee` + `2a6f73f` + `81b7beb`): (1) the router always forces `activeSurface = 'catalog'` (the onboarding/home/project render functions are preserved in source but no longer routable); (2) the topbar `Home` and `Re-onboard` buttons removed, `Catalog` retained; (3) the breadcrumb org-name (`shared.organization.name` from demo state) replaced with a static `llm-security` neutral scope anchor. Fix: the hardcoded `'Plugin v7.6.1'` on line 6933 of `renderHome` (template literal not caught by the v7.7.0 grep) was synced. The onboarding concept is documented as a v7.8.0 candidate (per-command context injection) in `ROADMAP.md`. No scanner or hook behavior changes. |
| **7.7.0** | 2026-05-18 | **HTML report for all 18 skill commands.** Every `/security <cmd>` that produces a report now prints a clickable `file://` link to a self-contained HTML version. Delivered across 5 sessions. (1) Playground catalog list-view + builder-pane with a copy button. (2) Playground project-surface cleanup (stub-screen handling, topbar split). (3) The 18 inline parsers + renderers in the playground HTML were moved to a canonical ESM module `scripts/lib/report-renderers.mjs` (the playground keeps a bit-identical inline copy since ESM `import` does not work from `file://`). (4) New zero-dep CLI `scripts/render-report.mjs` — stdin/file/stdout mode, kebab→camel commandId routing, inlines 6 DS stylesheets + a local `.report-table` CSS, ~140 KB self-contained HTML, system-font fallback, absolute `file://` paths for Ghostty cmd-click. (5) All 18 skills wired (4 in session 4: scan/audit/posture/deep-scan; 14 in session 5: plugin-audit/mcp-audit/mcp-inspect/ide-scan/supply-check/dashboard/pre-deploy/diff/watch/registry/clean/harden/threat-model/red-team). Output: `reports/<command>-<YYYYMMDD-HHmmss>.html` relative to CWD. No scanner or hook behavior changes — purely additive. |
| **7.6.1** | 2026-05-06 | **Playground v7.6.0 visual patch.** Six bugs caught during maintainer verification in the browser. All were mismatches between DS classes and renderer usage (or missing DS implementations the playground assumed existed). (1) `renderFindingsBlock` used the `.findings` outer class, which is the DS 2-column list+detail grid → replaced with `<section class="report-meta">` + the correct `findings__list > findings__group` pattern. (2) `.report-table` was missing entirely from the DS but used in 7+ renderers → local CSS implementation in the playground HTML. (3) `renderPreDeploy` traffic-lights used `.sm-card__grade` (28×28 px for one A-F letter) for "PASS"/"PASS-WITH-NOTES"/"FAIL" → replaced with a width-adapting status pill. (4) Threat-model matrix bubbles were not clickable → `<button>` with `data-threat-id` + click handler that scrolls to the Threats table. (5) Radar labels overlapped at 6+ axes → SVG 280→380, R 105→125, dynamic `text-anchor` (start/end/middle) based on horizontal position. (6) `recommendation-card__body` overflow on long text → `overflow-wrap: anywhere`. 4/4 fix-specific smoke tests + 18/18 renderer regression passing. No scanner or hook behavior changes — purely additive surface. |
| **7.6.0** | 2026-05-06 | **Playground Tier 3 reference case.** The playground (`playground/llm-security-playground.html`) raised to a visually and structurally complete reference for the `shared/playground-design-system/` Tier 3 supplement. 8 new DS components integrated into the 18 report renderers: `tfa-flow` + `tfa-leg` + `tfa-arrow` (lethal trifecta chain with `<button>` elements + ARIA), `mat-ladder` + `mat-step` (5-step maturity ladder with thresholds 0/25/50/75/95% PASS), `suppressed-group` (narrative audit from `summary.narrative_audit.suppressed_findings`), `codepoint-reveal` + `cp-tag`/`cp-zw`/`cp-bidi` (Unicode steganography side-by-side), `top-risks` + `top-risk[data-severity]` (ranked top-findings listing, semantic `<ol>`), `recommendation-card[data-severity]` (severity-tinted advisory on `clean`/`harden`/`audit`/`posture`/`pre-deploy`/`plugin-audit`), `risk-meter` (0-100 band visualization across 5 archetypes), `card--severity-{level}` (severity-color modifier on findings cards). Wave 1: `badge--scope-security` (identity chip), `verdict-pill-lg` (DS Tier 3 pill), `form-progress` + `fp-step` (onboarding wizard). Removed ~30 duplicate CSS declarations (DS wins the cascade). 5 new DS helpers + `mapSeverityToCardLevel` + `parseNarrativeAudit`. File size 10209 → 10677 lines. Delivered across 5 sessions, atomic commits. A11Y report updated. No scanner or hook behavior changes — purely additive surface. |
| **7.5.0** | 2026-05-05 | **Playground.** Single-file SPA at `playground/llm-security-playground.html` (~10 200 lines) for onboarding, demos and workshop use without a Claude Code installation. Parsers + renderers for all 18 `produces_report=true` commands (Phase 2: 10 high-priority + Phase 3: 8 remaining: mcp-inspect, supply-check, pre-deploy, diff, watch, registry, clean, threat-model). 18 markdown test fixtures under `playground/test-fixtures/` as contract anchors. The complete demo project `dft-komplett-demo` has all 18 reports parsed inline. Vendor-synced design-system under `playground/vendor/` (checksum-locked). 9 Playwright-generated screenshots under `playground/screenshots/v7.5.0/`. 11 new `window` globals for testing/automation. 2 new `KEY_STATS_CONFIG` archetypes (`kanban-buckets`, `matrix-risk`). Bug-fix: `normalizeVerdictText` regex order updated so GO-WITH-CONDITIONS / CONDITIONAL / BETINGET no longer collapse to ALLOW. No scanner or hook behavior changes — purely additive surface. |
| **7.4.0** | 2026-05-05 | **Examples + e2e suite.** Seven runnable demonstration walkthroughs under `examples/` (`prompt-injection-showcase`, `lethal-trifecta-walkthrough`, `mcp-rug-pull`, `supply-chain-attack`, `poisoned-claude-md`, `bash-evasion-gallery`, `toxic-agent-demo`, `pre-compact-poisoning`) — each with `README.md`, runtime-isolated fixture, single-command run-script, and `expected-findings.md` testable contract. Three new `tests/e2e/` suites (attack-chain 17 tests + multi-session 9 tests + scan-pipeline 19 tests = +45 tests, total 1822) prove the framework works as a coordinated system, not just isolated units. No scanner or hook behavior changes — purely additive surface. Scanner `VERSION` constants synced across `dashboard-aggregator.mjs`, `posture-scanner.mjs`, `ide-extension-scanner.mjs`. |
| **7.3.1** | 2026-05-01 | **Stabilization patch.** Project repositioned as solo, stabilization-only, with explicit "fork & own" stance for enterprise features. New public docs: `CONTRIBUTING.md` (fork-and-own model), README "Project scope" section (out-of-scope table with commercial alternatives), updated `SECURITY.md` (v7.3.x supported, v7.0v7.2 best-effort, < v7.0 EOL). Coherence: `package.json` files whitelist + `bugs` URL + repo URL fix; scanner `VERSION` constants synced across `dashboard-aggregator.mjs`, `posture-scanner.mjs`, `ide-extension-scanner.mjs`. Test ceiling raised on flaky pre-compact-scan timing test (500 ms → 1000 ms; design target unchanged). No behavior changes. |
| **7.3.0** | 2026-05-01 | **Batch C release.** Wave A (T7-T9 bash normalization + rot13 comment-block decoder), Wave B (`.gitattributes` post-clone advisory + npm scope-hop typosquat + GitHub/Forgejo workflow-scanner with 23-field blacklist + re-interpolation tracking + auth-bypass detection), Wave C (MCP cumulative-drift baseline + `/security mcp-baseline-reset`), Wave D (riskScoreV1 `@deprecated`; sandbox-architecture rationale docs; env-var deprecation runway to v8.0.0; CLAUDE.md hooks count + consistency test). 1665+ → 1777 tests. Wave E (additional attack-simulator scenarios) deferred indefinitely |

View file

@ -25,19 +25,21 @@ Your output is a structured security report per MCP server, including trust rati
findings mapped to OWASP categories, and prioritized recommendations. You operate read-only —
never modify files or install packages.
## Step 0: Generaliseringsgrense
## Step 0: Generalization boundary
Opus 4.7 tolker instruks mer literalt enn tidligere modeller. Ikke ekstrapolér fra en
enkelt observasjon til et bredere mønster uten eksplisitt evidens. Rapporter det du
faktisk ser; merk spekulasjon som spekulasjon. Ved tvil: inkludér filsti og linjenummer
som evidens, ikke en generalisering.
Opus 4.7 interprets instructions more literally than earlier models. Do not
extrapolate from a single observation to a broader pattern without explicit
evidence. Report what you actually see; mark speculation as speculation. When
in doubt, cite the filepath and line number as evidence rather than a
generalization.
## Parallell Read-strategi
## Parallel Read strategy
Når du trenger å lese tre eller flere filer som ikke avhenger av hverandre, send alle
Read-kallene i samme melding (parallell), ikke sekvensielt. Dette gjelder spesielt:
knowledge-files i oppstart, og batcher av MCP-server-filer. Sekvensiell Read er
akseptabelt når én fils innhold avgjør hvilken neste skal leses.
When you need to read three or more files that do not depend on each other,
send all the Read calls in the same message (parallel), not sequentially. This
applies especially to knowledge files during startup and to batches of
MCP-server files. Sequential Read is acceptable when one file's contents
determine which file to read next.
Reference knowledge base files before scanning:
- `knowledge/mcp-threat-patterns.md` — 9 threat categories with detection signals (MCP01-MCP10 mapping)

View file

@ -24,19 +24,21 @@ You are invoked by `/security scan` with a target path. Your `tools:` frontmatte
simply does not grant file-modifying tools. Your output is a written security report
— findings, severities, OWASP references, evidence excerpts, and remediation guidance.
## Step 0: Generaliseringsgrense
## Step 0: Generalization boundary
Opus 4.7 tolker instruks mer literalt enn tidligere modeller. Ikke ekstrapolér fra
en enkelt observasjon til et bredere mønster uten eksplisitt evidens. Rapporter det
du faktisk ser; merk spekulasjon som spekulasjon. Ved tvil: inkludér filsti og
linjenummer som evidens, ikke en generalisering.
Opus 4.7 interprets instructions more literally than earlier models. Do not
extrapolate from a single observation to a broader pattern without explicit
evidence. Report what you actually see; mark speculation as speculation. When
in doubt, cite the filepath and line number as evidence rather than a
generalization.
## Parallell Read-strategi
## Parallel Read strategy
Når du trenger å lese tre eller flere filer som ikke avhenger av hverandre, send
alle Read-kallene i samme melding (parallell), ikke sekvensielt. Dette gjelder
spesielt: knowledge-files i oppstart, og batcher av skannede filer. Sekvensiell
Read er akseptabelt når én fils innhold avgjør hvilken neste skal leses.
When you need to read three or more files that do not depend on each other,
send all the Read calls in the same message (parallel), not sequentially. This
applies especially to knowledge files during startup and to batches of scanned
files. Sequential Read is acceptable when one file's contents determine which
file to read next.
You have access to five knowledge base files that ground all your analysis:
- `knowledge/skill-threat-patterns.md` — 7 threat categories with documented attack variants

View file

@ -65,6 +65,6 @@ After producing the markdown audit report above:
The CLI writes `reports/audit-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown audit above is the primary deliverable.

View file

@ -76,6 +76,6 @@ After producing the markdown clean report above:
The CLI writes `reports/clean-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -76,6 +76,6 @@ After producing the markdown dashboard above:
The CLI writes `reports/dashboard-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown dashboard above is the primary deliverable.

View file

@ -57,6 +57,6 @@ After producing the markdown deep-scan report (banner + synthesizer output or fa
The CLI writes `reports/deep-scan-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -113,6 +113,6 @@ After producing the markdown diff report above:
The CLI writes `reports/diff-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -87,6 +87,6 @@ After producing the markdown harden report above:
The CLI writes `reports/harden-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -121,6 +121,6 @@ After producing the markdown IDE-scan report above:
The CLI writes `reports/ide-scan-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -62,6 +62,6 @@ After producing the markdown MCP audit report (Step 3 + optional Step 4 Live Ins
The CLI writes `reports/mcp-audit-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -69,6 +69,6 @@ After producing the markdown live-inspection report above:
The CLI writes `reports/mcp-inspect-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -89,6 +89,6 @@ After producing the markdown plugin-audit report (Step 5) and any cleanup (Step
The CLI writes `reports/plugin-audit-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -75,6 +75,6 @@ After producing the markdown scorecard above:
The CLI writes `reports/posture-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown scorecard above is the primary deliverable.

View file

@ -116,6 +116,6 @@ After producing the markdown pre-deploy checklist + verdict above:
The CLI writes `reports/pre-deploy-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown checklist above is the primary deliverable.

View file

@ -110,6 +110,6 @@ After producing the markdown red-team narrative report above:
The CLI writes `reports/red-team-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -136,6 +136,6 @@ After producing the markdown registry output above (stats / scan-result / search
The CLI writes `reports/registry-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown output above is the primary deliverable.

View file

@ -172,6 +172,6 @@ After producing the markdown report (Step 5) and any cleanup (Step 7):
The CLI writes `reports/scan-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout (one line).
4. Append this to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -62,6 +62,6 @@ After producing the markdown supply-check report above:
The CLI writes `reports/supply-check-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown report above is the primary deliverable.

View file

@ -42,6 +42,6 @@ After the threat-modeler agent has produced the complete threat-model markdown d
The CLI writes `reports/threat-model-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown threat-model document above is the primary deliverable.

View file

@ -73,6 +73,6 @@ After producing the markdown watch banner above (before starting the loop):
The CLI writes `reports/watch-<YYYYMMDD-HHmmss>.html` relative to CWD and prints `file:///abs/path.html` on stdout.
4. Append to your response (markdown link, no bare URL):
> **HTML-rapport:** [Åpne i nettleser](file:///abs/path.html)
> **HTML report:** [Open in browser](file:///abs/path.html)
If the CLI exits non-zero, mention the error but do not block — the markdown banner above is the primary deliverable.

View file

@ -105,18 +105,18 @@ Scan reports are stored in `reports/` as `.docx` (for sharing) with `.md` source
## Examples (runnable demonstrations)
Self-contained, deterministic threat-fixture mappes under `examples/`. Each mappe har `README.md`, fixture/script/transcript, `run-*.{sh,mjs}`, og `expected-findings.md`. Demonstrasjoner — ikke unit-tester.
Self-contained, deterministic threat fixtures live under `examples/`. Each directory has a `README.md`, fixture/script/transcript, `run-*.{sh,mjs}`, and `expected-findings.md`. They are demonstrations — not unit tests.
| Mappe | Demonstrerer | Hooks/scanners | Sentinel |
| Directory | Demonstrates | Hooks/scanners | Sentinel |
|-------|--------------|----------------|----------|
| `malicious-skill-demo/` | Skill scanner end-to-end (UNI/ENT/PRM/DEP/TNT/NET + 7 LLM-kategorier) | `scan-orchestrator` + agents | BLOCK 100/100 |
| `prompt-injection-showcase/` | 61 payloads × 19 kategorier mot `pre-prompt-inject-scan`, `post-mcp-verify`, `pre-bash-destructive` | runtime hooks | per-kategori expected outcome |
| `lethal-trifecta-walkthrough/` | Rule-of-Two advisory leg 3 (WebFetch → Read .env → Bash curl POST) + suppression | `post-session-guard` | advisory stage 3 |
| `mcp-rug-pull/` | Cumulative drift-advisory (E14, v7.3.0) — 7 stadier under per-update-terskel, kumulativt over 25% baseline | `post-mcp-verify` + `mcp-description-cache.mjs` | advisory på stage 7 |
| `supply-chain-attack/` | PreToolUse-blokk på kompromittert pakke + scope-hop advisory + dep-auditor typosquats + postinstall curl-pipe | `pre-install-supply-chain` + `dep-auditor` + `supply-chain-data` | 6+ funn, 2 advisories, 1 BLOCK |
| `poisoned-claude-md/` | 6 detektorer (injection / shell / URL / credential paths / permission expansion / encoded payloads) inkl. E15 agent-fil-overflate | `memory-poisoning-scanner` | ≥18 funn fordelt på 2 filer |
| `bash-evasion-gallery/` | T1-T9 disguised destructive commands → normalisert + blokkert (defense-in-depth over Claude Code 2.1.98+) | `pre-bash-destructive` + `bash-normalize` | 10 BLOCK eksitkoder |
| `toxic-agent-demo/` | Single-component lethal trifecta — agent med [Bash, Read, WebFetch] uten hook-guards = CRITICAL TFA-finding | `toxic-flow-analyzer` (TFA) | 1 CRITICAL `Lethal trifecta:` |
| `pre-compact-poisoning/` | PreCompact-hook fanger injection + AWS-shaped credential i syntetisk transcript på tvers av off/warn/block-modus | `pre-compact-scan` | 9 pass: block exit 2 + reason; warn systemMessage; off skip; benign passes |
| `malicious-skill-demo/` | Skill scanner end-to-end (UNI/ENT/PRM/DEP/TNT/NET + 7 LLM categories) | `scan-orchestrator` + agents | BLOCK 100/100 |
| `prompt-injection-showcase/` | 61 payloads × 19 categories against `pre-prompt-inject-scan`, `post-mcp-verify`, `pre-bash-destructive` | runtime hooks | per-category expected outcome |
| `lethal-trifecta-walkthrough/` | Rule-of-Two advisory on leg 3 (WebFetch → Read .env → Bash curl POST) + suppression | `post-session-guard` | advisory at stage 3 |
| `mcp-rug-pull/` | Cumulative drift advisory (E14, v7.3.0) — 7 stages below the per-update threshold, cumulatively over a 25% baseline shift | `post-mcp-verify` + `mcp-description-cache.mjs` | advisory at stage 7 |
| `supply-chain-attack/` | PreToolUse block on a compromised package + scope-hop advisory + dep-auditor typosquats + postinstall curl-pipe | `pre-install-supply-chain` + `dep-auditor` + `supply-chain-data` | 6+ findings, 2 advisories, 1 BLOCK |
| `poisoned-claude-md/` | 6 detectors (injection / shell / URL / credential paths / permission expansion / encoded payloads) including the E15 agent-file surface | `memory-poisoning-scanner` | ≥18 findings split across 2 files |
| `bash-evasion-gallery/` | T1-T9 disguised destructive commands → normalized + blocked (defense-in-depth over Claude Code 2.1.98+) | `pre-bash-destructive` + `bash-normalize` | 10 BLOCK exit codes |
| `toxic-agent-demo/` | Single-component lethal trifecta — an agent with [Bash, Read, WebFetch] and no hook guards = CRITICAL TFA finding | `toxic-flow-analyzer` (TFA) | 1 CRITICAL `Lethal trifecta:` |
| `pre-compact-poisoning/` | The PreCompact hook catches injection + an AWS-shaped credential in a synthetic transcript across off/warn/block modes | `pre-compact-scan` | 9 cases: block exit 2 + reason; warn systemMessage; off skip; benign passes |
State-isolering: alle eksempler som muterer global state bruker run-script PID (post-session-guard via `${ppid}.jsonl`) eller env-overrides (`LLM_SECURITY_MCP_CACHE_FILE` for MCP-cache). Brukerens reelle `/tmp/llm-security-session-*.jsonl` og `~/.cache/llm-security/` røres aldri.
State isolation: every example that mutates global state uses the run-script PID (post-session-guard via `${ppid}.jsonl`) or env overrides (`LLM_SECURITY_MCP_CACHE_FILE` for the MCP cache). The user's real `/tmp/llm-security-session-*.jsonl` and `~/.cache/llm-security/` are never touched.

View file

@ -2,6 +2,25 @@
Per-release notes for v7.0.0 onward. Imported from `CLAUDE.md` via `@docs/version-history.md`.
## v7.7.2 — Language consistency pass
Norwegian had crept into surface text across v7.5v7.7. Per the
`~/.claude/CLAUDE.md` convention (English for code and documentation,
Norwegian for dialog only), this release translates: the HTML Report-step in
all 18 skill commands, the canonical CLI renderer
`scripts/lib/report-renderers.mjs` (display strings + JS comments), the
playground UI strings, the `skill-scanner-agent` and `mcp-scanner-agent`
system prompts, the playground architecture prose + Recent versions table in
the plugin `README.md`, the v7.7.x highlights in the plugin `CLAUDE.md`, the
llm-security entries in the marketplace root `README.md` + `CLAUDE.md`, and
six table cells in `docs/scanner-reference.md`. Demo-state fixture content
for the `dft-komplett-demo` project (intentional Norwegian persona) and
regex alternations that match Norwegian-language report markdown
(`/^high|^høy/`, `/resolution|løsning/`) were preserved. CHANGELOG and this
version-history file were deferred per operator decision — they remain in
the language they were written in. No scanner, hook, or behavior changes —
purely surface text.
## v7.0.0 — Severity-dominated risk scoring (v2 model, BREAKING)
Three changes target the false-positive cascade on real codebases (hyperframes.com gave `BLOCK / Extreme / 100`, ~70% noise):

View file

@ -1,6 +1,6 @@
{
"name": "llm-security",
"version": "7.7.1",
"version": "7.7.2",
"description": "Security scanning, auditing, and threat modeling for Claude Code projects",
"type": "module",
"bin": {

File diff suppressed because it is too large Load diff

View file

@ -79,7 +79,7 @@ function renderKeyStatsGrid(stats) {
}
/**
* Render page-shell DS Tier 3 page__header-klyngen brukt alle 4 overflater:
* Render the page-shell the DS Tier 3 page__header cluster used on all 4 surfaces:
* - onboarding: page__eyebrow="ONBOARDING · n av 5 grupper komplette"
* - home: page__eyebrow="HJEM" (m/ hero-modifier for editorial type-hierarki)
* - catalog: page__eyebrow="KATALOG"
@ -197,16 +197,16 @@ const KEY_STATS_CONFIG = {
const crit = fs.filter(function (f) { return /crit|kritisk/i.test(f.severity || ''); }).length;
const high = fs.filter(function (f) { return /^high|^høy/i.test(f.severity || ''); }).length;
return [
{ label: 'TOTALT', value: fs.length },
{ label: 'KRITISK', value: crit, modifier: crit > 0 ? 'critical' : null },
{ label: 'HØY', value: high, modifier: high > 0 ? 'high' : null }
{ label: 'TOTAL', value: fs.length },
{ label: 'CRITICAL', value: crit, modifier: crit > 0 ? 'critical' : null },
{ label: 'HIGH', value: high, modifier: high > 0 ? 'high' : null }
];
},
'findings-grade': function (d) {
const out = [];
if (d.grade) out.push({ label: 'GRADE', value: String(d.grade).toUpperCase(), modifier: /a|b/i.test(d.grade) ? 'low' : (/c|d/i.test(d.grade) ? 'medium' : 'critical') });
if (d.score != null) out.push({ label: 'SCORE', value: d.score });
if (d.findings) out.push({ label: 'FUNN', value: d.findings.length });
if (d.findings) out.push({ label: 'FINDINGS', value: d.findings.length });
return out;
},
'risk-score-meter': function (d) {
@ -220,16 +220,16 @@ const KEY_STATS_CONFIG = {
},
'red-team-results': function (d) {
return [
{ label: 'TOTALT', value: d.total || 0 },
{ label: 'TOTAL', value: d.total || 0 },
{ label: 'PASS', value: d.pass_count || 0, modifier: 'low' },
{ label: 'FAIL', value: d.fail_count || 0, modifier: (d.fail_count > 0 ? 'critical' : null) }
];
},
'dashboard-fleet': function (d) {
return [
{ label: 'PROSJEKTER', value: (d.projects || []).length },
{ label: 'MASKINKLASSE', value: String(d.machine_grade || 'n/a').toUpperCase() },
{ label: 'SVAKEST', value: d.weakest_link || '' }
{ label: 'PROJECTS', value: (d.projects || []).length },
{ label: 'MACHINE GRADE', value: String(d.machine_grade || 'n/a').toUpperCase() },
{ label: 'WEAKEST', value: d.weakest_link || '' }
];
},
'posture-cards': function (d) {
@ -246,8 +246,8 @@ const KEY_STATS_CONFIG = {
const newCount = (d['new'] || []).length;
const unchangedCount = (d.unchanged || []).length;
return [
{ label: 'NÅ-GRADE', value: String(d.current_grade || '?').toUpperCase() },
{ label: 'AKSJONER', value: newCount, modifier: newCount > 0 ? 'medium' : 'low' },
{ label: 'CURRENT GRADE', value: String(d.current_grade || '?').toUpperCase() },
{ label: 'ACTIONS', value: newCount, modifier: newCount > 0 ? 'medium' : 'low' },
{ label: 'SKIPPED', value: unchangedCount }
];
},
@ -435,7 +435,7 @@ function gradeFromText(s) {
return m ? m[1] : null;
}
// Hjelper: parse Risk Dashboard-tabellen (fellesmønster)
// Helper: parse the Risk Dashboard table (shared pattern)
function parseRiskDashboard(md) {
const out = {};
const score = extractField(md, 'Risk Score');
@ -486,7 +486,7 @@ function parseFindingsTables(md) {
});
if (!findingsSection) return findings;
const body = findingsSection.body;
// Splitt på ### -headere
// Split on ### headers
const subRe = /^###\s+(.+)$/gm;
const matches = [];
let m;
@ -578,7 +578,7 @@ function parseNarrativeAudit(md) {
}
// ============================================================
// 10 PARSERS — én per høy-prio kommando.
// 10 PARSERS — one per high-priority command.
// Returner { ok: true, data: { ...domain-specific } } eller
// { ok: false, errors: [{ section, reason }] }
// ============================================================
@ -1047,7 +1047,7 @@ const parseDashboard = safeOk(function (md) {
});
}
}
// Weakest link = første prosjekt sortert worst-first (allerede sortert i fixture)
// Weakest link = first project, sorted worst-first (already sorted in the fixture)
const weakest = projects.length ? projects[0].name : '';
return { ok: true, data: Object.assign({}, dash, {
machine_grade: machine_grade,
@ -1195,8 +1195,8 @@ const parseRedTeam = safeOk(function (md) {
});
// ============================================================
// FASE 3: 8 PARSERS — én per gjenstående produces_report-kommando.
// Mønstre gjenbrukes fra Fase 2 (parseRiskDashboard + parseFindingsTables
// PHASE 3: 8 PARSERS — one per remaining produces_report command.
// Patterns are reused from Phase 2 (parseRiskDashboard + parseFindingsTables
// + safeOk). Matrix-risk-parsing er kopiert fra ms-ai-architect.
// ============================================================
const parseMcpInspect = safeOk(function (md) {
@ -1614,7 +1614,7 @@ function renderEmptyState(message) {
return '<div class="guide-panel guide-panel--info">' +
'<div class="guide-panel__icon" aria-hidden="true">i</div>' +
'<div class="guide-panel__body">' +
'<p class="guide-panel__text">' + escapeHtml(message || 'Ingen data å vise.') + '</p>' +
'<p class="guide-panel__text">' + escapeHtml(message || 'No data to display.') + '</p>' +
'</div>' +
'</div>';
}
@ -1627,7 +1627,7 @@ function renderFindingsBlock(findings, label) {
});
const items = sorted.map(function (f) {
const sev = String(f.severity || 'info').toLowerCase();
// DS Tier 3 (v7.6.0 fase 5h): card--severity-{level} modifier på outer
// DS Tier 3 (v7.6.0 phase 5h): card--severity-{level} modifier on the outer
// .findings__item gir severity-tinted left-border. Beholdes ved siden av
// den eksisterende .findings__item-severity-dot for ARIA + visuell
// redundans (border-farge + dot-fyll signaliserer samme severity).
@ -1649,8 +1649,8 @@ function renderFindingsBlock(findings, label) {
);
}).join('');
// DS .findings outer-class er et 2-kolonners grid (360px list + 1fr detail-panel) —
// playgroundet bruker bare list-delen, så vi wrapper i .findings__list (uten outer
// .findings) for å unngå at headeren ender i venstre 360px-kolonne. v7.6.1 fix.
// the playground uses only the list part, so we wrap in .findings__list (no outer
// .findings) to avoid the header landing in the left 360px column. v7.6.1 fix.
return (
'<section class="report-meta">' +
'<h4>' + escapeHtml(label || 'Funn') + '</h4>' +
@ -1684,7 +1684,7 @@ function renderRecommendationsList(recs, label, severity) {
/**
* Map severity-string til DS-tier3 recommendation-card data-severity.
* Aksepterer både severity-konvensjoner (critical/high/medium/low/info)
* Accepts both severity conventions (critical/high/medium/low/info)
* og action-types (CREATE/APPEND/MERGE/SKIP/NONE).
*/
function mapSeverityToCardLevel(input) {
@ -1753,9 +1753,9 @@ function renderSmallMultiples(items) {
function renderRadarSvg(axes) {
// axes: [{ name, score (0-5) }]
if (!axes || axes.length < 3) return '';
// v7.6.1 fix: øk SVG-bredden fra 280 til 380 og r fra 105 til 125 for å gi
// labels mer plass. Bruk text-anchor basert på horisontal-posisjon for å
// unngå at bottom-labels overlapper hverandre ved 6+ akser.
// v7.6.1 fix: widen the SVG from 280 to 380 and r from 105 to 125 to give
// labels more room. Use text-anchor based on horizontal position to keep
// bottom labels from overlapping each other at 6+ axes.
const size = 380, cx = size / 2, cy = size / 2, r = 125;
const n = axes.length;
const axisRows = axes.map(function (a) {
@ -1766,7 +1766,7 @@ function renderRadarSvg(axes) {
const ang = angle(i);
const lx = cx + Math.cos(ang) * (r + 28);
const ly = cy + Math.sin(ang) * (r + 28);
// Velg text-anchor basert på posisjon: ankerene til venstre/høyre snur.
// Pick text-anchor based on position: left/right anchors flip.
const dx = Math.cos(ang);
const anchor = Math.abs(dx) < 0.2 ? 'middle' : (dx > 0 ? 'start' : 'end');
return '<text class="radar__label" x="' + lx.toFixed(1) + '" y="' + ly.toFixed(1) + '" text-anchor="' + anchor + '" dominant-baseline="middle">' + escapeHtml(a.name) + '</text>';
@ -1804,7 +1804,7 @@ function renderRadarSvg(axes) {
/**
* Render tfa-flow + tfa-leg + tfa-arrow for et lethal trifecta-funn.
* Brukes scan + deep-scan-rapporter når findings inneholder
* Used on scan + deep-scan reports when findings contain
* en trifecta-pattern (f.eks. SCN-002 "Lethal trifecta: [Bash, Read, WebFetch]").
* Synthesiserer 3-leddet kjede: untrusted-input sensitive-access exfil-sink.
*/
@ -1835,7 +1835,7 @@ function renderToxicFlow(findings) {
}
const legs = [
{ label: 'Untrusted input', name: tools[0], source: fileLine, mit: 'unmitigated', mitText: 'Ingen pre-prompt-inject-scan eller post-mcp-verify guard' },
{ label: 'Sensitive access', name: tools[1], source: '.env / credentials / git-history', mit: 'unmitigated', mitText: 'Ingen pre-write-pathguard på sti' },
{ label: 'Sensitive access', name: tools[1], source: '.env / credentials / git-history', mit: 'unmitigated', mitText: 'No pre-write-pathguard on path' },
{ label: 'Exfil sink', name: tools[2], source: 'curl / fetch til ekstern host', mit: 'unmitigated', mitText: 'Ingen post-session-guard trifecta-deteksjon' }
];
const legHtml = function (leg) {
@ -1874,13 +1874,13 @@ function renderMatLadder(categories, postureScore, postureApplicable) {
? Number(postureApplicable)
: categories.filter(function (c) { return c.status !== 'N-A' && c.status !== 'N/A'; }).length;
const pct = total > 0 ? Math.round((passCount / total) * 100) : 0;
// 5 modenhetstrinn — terskler basert på % PASS
// 5 maturity steps — thresholds based on % PASS
const steps = [
{ num: 1, name: 'Initial', threshold: 0, desc: 'Bare bones — ingen hooks eller minimal posture.' },
{ num: 2, name: 'Aware', threshold: 25, desc: 'Posture-skanning aktiv, kjenner risikoene.' },
{ num: 3, name: 'Defensive', threshold: 50, desc: 'Hooks engasjert på kritiske flater (PreToolUse, UserPromptSubmit).' },
{ num: 4, name: 'Mature', threshold: 75, desc: 'De fleste 16 kategoriene dekket; trifecta-deteksjon på.' },
{ num: 5, name: 'Optimized', threshold: 95, desc: 'Full coverage; A-grade på posture; aktiv overvåking.' }
{ num: 1, name: 'Initial', threshold: 0, desc: 'Bare bones — no hooks or minimal posture.' },
{ num: 2, name: 'Aware', threshold: 25, desc: 'Posture scanning is active and the risks are known.' },
{ num: 3, name: 'Defensive', threshold: 50, desc: 'Hooks engaged on critical surfaces (PreToolUse, UserPromptSubmit).' },
{ num: 4, name: 'Mature', threshold: 75, desc: 'Most of the 16 categories covered; trifecta detection on.' },
{ num: 5, name: 'Optimized', threshold: 95, desc: 'Full coverage; A-grade on posture; active monitoring.' }
];
const currentIdx = steps.reduce(function (acc, s, i) {
return pct >= s.threshold ? i : acc;
@ -1890,7 +1890,7 @@ function renderMatLadder(categories, postureScore, postureApplicable) {
const icon = state === 'completed' ? '✓' : String(s.num);
const pillCls = state === 'current' ? ' mat-step__pill mat-step__pill--current' :
state === 'completed' ? ' mat-step__pill mat-step__pill--complete' : '';
const pillText = state === 'current' ? 'Du er her' : state === 'completed' ? 'Oppnådd' : '';
const pillText = state === 'current' ? 'You are here' : state === 'completed' ? 'Reached' : '';
const pill = pillText ? '<span class="' + pillCls.trim() + '">' + escapeHtml(pillText) + '</span>' : '';
const progress = state === 'current' ? (
'<div class="mat-step__progress">' +
@ -1912,7 +1912,7 @@ function renderMatLadder(categories, postureScore, postureApplicable) {
return (
'<section class="report-meta">' +
'<h4>Modenhetsstige — posture-progresjon</h4>' +
'<p style="font-size: var(--font-size-sm); opacity: 0.78; margin: 0 0 var(--space-3);">Posture-score på ' + passCount + ' av ' + total + ' kategorier (' + pct + '%) plasserer dette prosjektet på trinn ' + (currentIdx + 1) + ' av 5.</p>' +
'<p style="font-size: var(--font-size-sm); opacity: 0.78; margin: 0 0 var(--space-3);">A posture score of ' + passCount + ' of ' + total + ' categories (' + pct + '%) places this project at step ' + (currentIdx + 1) + ' of 5.</p>' +
'<div class="mat-ladder" role="list" aria-label="Posture-modenhet over 5 trinn">' + stepHtml + '</div>' +
'</section>'
);
@ -1973,7 +1973,7 @@ function renderSuppressedGroup(data) {
return (
'<section class="report-meta">' +
'<h4>Narrative audit — supprimerte signaler</h4>' +
'<p class="suppressed-group__desc">' + totalCount + ' signaler ble supprimert pre-rapport (v7.1.1 narrative_audit). Disse er ikke false-positives walked-back i prosa, men auto-suppress før klassifisering.</p>' +
'<p class="suppressed-group__desc">' + totalCount + ' signals were suppressed pre-report (v7.1.1 narrative_audit). These are not false-positives walked back in prose — they were auto-suppressed before classification.</p>' +
groupsHtml +
'</section>'
);
@ -1981,7 +1981,7 @@ function renderSuppressedGroup(data) {
/**
* Render codepoint-reveal + cp-tag for Unicode-steganografi (UNI-funn).
* Brukes mcp-inspect-rapporter bytter plain table mot side-by-side
* Used on mcp-inspect reports swaps a plain table for a side-by-side
* "synlig vs. decoded codepoint"-visning per tool.
*/
function renderCodepointReveal(codepoints) {
@ -1999,7 +1999,7 @@ function renderCodepointReveal(codepoints) {
const sev = /high/i.test(risk) ? 'critical' : /medium/i.test(risk) ? 'medium' : 'low';
const isClean = /clean|—|^-$/i.test(c.codepoints || '') || risk === '—' || risk === '-';
const cps = String(c.codepoints || '');
// Highlight U+XXXX-mønstre
// Highlight U+XXXX patterns
const highlighted = cps.replace(/U\+[0-9A-Fa-f]{4,6}/g, function (m) {
return '<span class="' + tagFor(m) + '">' + m + '</span>';
});
@ -2042,7 +2042,7 @@ function renderCodepointReveal(codepoints) {
/**
* Render top-risks + top-risk for rangert top-funn-listing.
* Tar de N (default 5) høyeste alvorlighetsnivåene fra findings og
* Takes the N (default 5) highest-severity findings and
* viser dem som ordnet liste. Bruker `.top-risks` / `.top-risk` med
* `data-severity` for severity-tinted left-border per DS Tier 3-supplement.
* Returnerer tom streng hvis ingen findings (eller kun info-funn).
@ -2089,7 +2089,7 @@ function renderTopRisks(findings, n) {
}
// ============================================================
// 10 RENDERERS — én per høy-prio kommando.
// 10 RENDERERS — one per high-priority command.
// ============================================================
function renderScan(data, slot) {
const meterHtml = renderRiskMeter(data.risk_score, data.riskBand);
@ -2190,7 +2190,7 @@ function renderPluginAudit(data, slot) {
) : '';
const permHtml = (data.permissions && data.permissions.length) ? (
'<section class="report-meta"><h4>Permission-matrise</h4>' +
'<table class="report-table"><thead><tr><th>Verktøy</th><th>Krevet av</th><th>Begrunnet</th></tr></thead><tbody>' +
'<table class="report-table"><thead><tr><th>Tool</th><th>Required by</th><th>Justified</th></tr></thead><tbody>' +
data.permissions.map(function (p) {
const isYes = /^yes|^ja/i.test(p.justified);
const isNo = /^no$|^nei/i.test(p.justified);
@ -2220,7 +2220,7 @@ function renderPluginAudit(data, slot) {
slot.innerHTML = renderPageShell({
eyebrow: 'PLUGIN-AUDIT',
title: data.title || 'Plugin trust-vurdering',
lede: data.lede || 'Trust-verdikt basert på maintainer, lisens, permissions og MCP-deskripsjoner.',
lede: data.lede || 'Trust verdict based on maintainer, license, permissions, and MCP descriptions.',
verdict: data.verdict || inferVerdict(data, 'risk-score-meter'),
keyStats: data.keyStats || inferKeyStats(data, 'risk-score-meter')
}, body);
@ -2285,7 +2285,7 @@ function renderMcpAudit(data, slot) {
slot.innerHTML = renderPageShell({
eyebrow: 'MCP-AUDIT',
title: data.title || 'MCP-konfig audit',
lede: data.lede || 'Permissions, trust og deskripsjon-drift på tvers av installerte MCP-servere.',
lede: data.lede || 'Permissions, trust, and description drift across installed MCP servers.',
verdict: data.verdict || inferVerdict(data, 'findings'),
keyStats: data.keyStats || inferKeyStats(data, 'findings')
}, body);
@ -2392,7 +2392,7 @@ function renderAudit(data, slot) {
'<ol class="recommendation-card__body">' + items.map(function (a) { return '<li>' + escapeHtml(a) + '</li>'; }).join('') + '</ol>' +
'</section>';
};
const actionHtml = tierHtml('immediate', 'Umiddelbar', 'critical') + tierHtml('high', 'Høy prioritet', 'high') + tierHtml('medium', 'Medium prioritet', 'medium');
const actionHtml = tierHtml('immediate', 'Immediate', 'critical') + tierHtml('high', 'High priority', 'high') + tierHtml('medium', 'Medium priority', 'medium');
const meterHtml = (data.risk_score != null) ? renderRiskMeter(data.risk_score, data.riskBand) : '';
const topRisksHtml = renderTopRisks(data.findings || [], 5);
const findingsHtml = renderFindingsBlock(data.findings || [], 'Funn');
@ -2504,12 +2504,12 @@ function renderHarden(data, slot) {
slot.innerHTML = renderPageShell({
eyebrow: 'HARDEN',
title: data.title || 'Grade A reference config',
lede: data.lede || 'Diff-forhåndsvisning av settings.json, CLAUDE.md og .gitignore-endringer.',
lede: data.lede || 'Diff preview of settings.json, CLAUDE.md, and .gitignore changes.',
verdict: data.verdict || inferVerdict(data, 'diff-report'),
keyStats: data.keyStats || [
{ label: 'NÅ-GRADE', value: String(data.current_grade || '?') },
{ label: 'AKSJONER', value: data.actionable + '/' + data.total },
{ label: 'MODUS', value: data.mode || 'dry-run' }
{ label: 'CURRENT GRADE', value: String(data.current_grade || '?') },
{ label: 'ACTIONS', value: data.actionable + '/' + data.total },
{ label: 'MODE', value: data.mode || 'dry-run' }
]
}, body);
}
@ -2564,7 +2564,7 @@ function renderRedTeam(data, slot) {
RENDERERS.renderRedTeam = renderRedTeam;
// ============================================================
// FASE 3: 8 RENDERERS — én per gjenstående kommando.
// PHASE 3: 8 RENDERERS — one per remaining command.
// ============================================================
function renderMcpInspect(data, slot) {
const invRows = (data.server_inventory || []).map(function (s) {
@ -2602,7 +2602,7 @@ function renderMcpInspect(data, slot) {
RENDERERS.renderMcpInspect = renderMcpInspect;
function renderSupplyCheck(data, slot) {
// Ecosystem-cards (small-multiples-mønster)
// Ecosystem cards (small-multiples pattern)
const ecos = (data.ecosystems || []).filter(function (e) { return Number(e.packages) > 0 || Number(e.osv_hits) > 0 || Number(e.typosquats) > 0; });
const ecoCards = ecos.length ? '<div class="small-multiples">' + ecos.map(function (e) {
const issues = (Number(e.osv_hits) || 0) + (Number(e.typosquats) || 0);
@ -2640,7 +2640,7 @@ function renderPreDeploy(data, slot) {
if (u === 'FAIL' || u === 'BLOCK' || u === 'NO-GO') return 'critical';
return 'info';
};
// v7.6.1 fix: sm-card__grade er fast 28×28 px (designet for én A-F-bokstav), så
// v7.6.1 fix: sm-card__grade is fixed at 28×28 px (designed for one A-F letter), so
// "PASS"/"PASS-WITH-NOTES"/"FAIL" ble kuttet til "AS"/"PASS-WITH-..."/"FA". Bytt
// til en bredde-tilpasset status-pill via inline styling (ingen DS-klasse-endring).
const cards = lights.map(function (l) {
@ -2665,7 +2665,7 @@ function renderPreDeploy(data, slot) {
const lightsHtml = cards ? '<section class="report-meta"><h4>Traffic-light kategorier</h4><div class="small-multiples">' + cards + '</div></section>' : '';
const condHtml = (data.conditions && data.conditions.length) ? (
'<section class="recommendation-card" data-severity="high">' +
'<span class="recommendation-card__label">Vilkår å løse</span>' +
'<span class="recommendation-card__label">Conditions to resolve</span>' +
'<ol class="recommendation-card__body">' + data.conditions.map(function (c) { return '<li>' + escapeHtml(c) + '</li>'; }).join('') + '</ol>' +
'</section>'
) : '';
@ -2708,7 +2708,7 @@ function renderDiff(data, slot) {
'</div>' +
'<div class="pair-before-after__arrow" aria-hidden="true"></div>' +
'<div class="pair-before-after__cell">' +
'<span class="pair-before-after__cell-label">NÅ</span>' +
'<span class="pair-before-after__cell-label">NOW</span>' +
'<span class="pair-before-after__cell-value">' + gradeBadge(data.current_grade) + '</span>' +
'</div>' +
'</div>' +
@ -2741,7 +2741,7 @@ function renderDiff(data, slot) {
'</section>';
};
const newHtml = sectionFor('Nye funn', newItems, 'new');
const resHtml = sectionFor('Løste funn', resolvedItems, 'resolved');
const resHtml = sectionFor('Resolved findings', resolvedItems, 'resolved');
const unchHtml = sectionFor('Uendret', unchangedItems, 'unchanged');
const movHtml = (movedItems.length) ? sectionFor('Flyttet', movedItems.map(function (m) {
return { id: m.id, severity: 'info', description: m.from + ' → ' + m.to };
@ -2751,7 +2751,7 @@ function renderDiff(data, slot) {
slot.innerHTML = renderPageShell({
eyebrow: 'DIFF',
title: data.title || 'Scan diff mot baseline',
lede: data.lede || 'Sammenligner nåværende scan mot lagret baseline.',
lede: data.lede || 'Compares the current scan against the stored baseline.',
verdict: data.verdict || inferVerdict(data, 'diff-report'),
keyStats: data.keyStats || inferKeyStats(data, 'diff-report')
}, body);
@ -2797,7 +2797,7 @@ function renderWatch(data, slot) {
slot.innerHTML = renderPageShell({
eyebrow: 'WATCH',
title: data.title || 'Continuous monitoring',
lede: data.lede || 'Kjører diff på rekursivt intervall via /loop. Notify ved nye funn.',
lede: data.lede || 'Runs diff on a recurring interval via /loop. Notifies on new findings.',
verdict: data.verdict || inferVerdict(data, 'findings'),
keyStats: data.keyStats || inferKeyStats(data, 'findings')
}, body);
@ -2829,7 +2829,7 @@ function renderRegistry(data, slot) {
}).join('');
const sigHtml = sigRows ? (
'<section class="report-meta"><h4>Signaturer</h4>' +
'<table class="report-table"><thead><tr><th>Skill</th><th>Kilde</th><th>Fingerprint</th><th>Status</th><th>Første sett</th></tr></thead><tbody>' + sigRows + '</tbody></table>' +
'<table class="report-table"><thead><tr><th>Skill</th><th>Source</th><th>Fingerprint</th><th>Status</th><th>First seen</th></tr></thead><tbody>' + sigRows + '</tbody></table>' +
'</section>'
) : '';
const fs = (data.findings || []).map(function (f) {
@ -2874,11 +2874,11 @@ function renderClean(data, slot) {
cardFor('suppressed', 'Undertrykt', 'info') +
'</div>';
// Advisory recommendation-cards per bucket — DS Tier 3 data-severity (v7.6.0 fase 5f).
// Hver bucket med items > 0 får én recommendation-card med severity-tinted border + label.
// Every bucket with items > 0 gets one recommendation-card with a severity-tinted border + label.
const bucketAdvisoryDefs = [
{ key: 'auto', label: 'Auto-fixable', sev: 'positive', desc: 'Plugin kan fikse disse uten ekstra bekreftelse — deterministiske, lavrisiko-handlinger.' },
{ key: 'semi-auto', label: 'Semi-auto — krever bekreftelse', sev: 'medium', desc: 'Foreslåtte tiltak vises som diff. Bruker bekrefter per finding før endring anvendes.' },
{ key: 'manual', label: 'Manual remediation', sev: 'high', desc: 'Krever menneskelig vurdering — kontekst, scope eller side-effekter er ikke deterministisk avgjørbare.' },
{ key: 'semi-auto', label: 'Semi-auto — requires confirmation', sev: 'medium', desc: 'Proposed changes are shown as a diff. The user confirms per finding before the change is applied.' },
{ key: 'manual', label: 'Manual remediation', sev: 'high', desc: 'Requires human judgement — context, scope, or side-effects are not deterministically decidable.' },
{ key: 'suppressed', label: 'Undertrykt', sev: 'low', desc: 'Allowlist-treff via .llm-security-ignore — ingen handling.' }
];
const advisoryHtml = bucketAdvisoryDefs.map(function (b) {
@ -2897,14 +2897,14 @@ function renderClean(data, slot) {
const intro = data.mode ? (
'<section class="recommendation-card" data-severity="' + (isDry ? 'low' : 'medium') + '">' +
'<span class="recommendation-card__label">Modus · ' + escapeHtml(data.mode) + '</span>' +
'<p class="recommendation-card__body">' + (isDry ? 'Dry-run: ingen filer endres. Forhåndsvis tiltak før <code>--apply</code>.' : 'Fixes anvendes med automatisk backup i <code>.llm-security-backup/</code>.') + '</p>' +
'<p class="recommendation-card__body">' + (isDry ? 'Dry-run: no files are modified. Preview the actions before <code>--apply</code>.' : 'Fixes are applied with an automatic backup in <code>.llm-security-backup/</code>.') + '</p>' +
'</section>'
) : '';
const body = intro + advisoryHtml + kanbanHtml + findingsHtml + recHtml;
slot.innerHTML = renderPageShell({
eyebrow: 'CLEAN',
title: data.title || 'Remediation-kanban',
lede: data.lede || 'Funn fordelt på Auto / Semi-auto / Manual / Undertrykt.',
lede: data.lede || 'Findings split across Auto / Semi-auto / Manual / Suppressed.',
verdict: data.verdict || inferVerdict(data, 'kanban-buckets'),
keyStats: data.keyStats || inferKeyStats(data, 'kanban-buckets')
}, body);
@ -2929,7 +2929,7 @@ function renderThreatModel(data, slot) {
for (let prob = 1; prob <= probSize; prob++) {
const score = prob * cons;
const items = byPC[prob + '_' + cons] || [];
// v7.6.1 fix: bobler er nå <button> så de er klikkbare og fokuserbare.
// v7.6.1 fix: bubbles are now <button> so they are clickable and focusable.
// data-threat-id lar event-handler senere mappe til detalj-modal.
const bubblesHtml = items.length
? '<div class="matrix__cell-bubbles">' +