Commit graph

222 commits

Author SHA1 Message Date
Kjell Tore Guttormsen
950e4e4bce feat(injection): E3 — rot13 layer for comment-block injection
Adds rot13 to the variantSet built in scanForInjection(), so
imperative phrases hidden as rot13 inside code comments still hit
the existing CRITICAL/HIGH/MEDIUM pattern arrays.

normalizeForScan() already covers base64, hex, URL, and HTML decoding
in a 3-iteration loop — those are NOT duplicated here. rot13 is the
only genuinely new variant: it is its own inverse and not part of any
NIST/Unicode normalization spec, so it has to be applied explicitly.

Threshold: only inputs >40 chars enter the rot13 pass, to suppress
false positives on accidental letter-shifts in tokens, ids, and short
identifiers. Variants are deduplicated against the existing set so
matchers do not run twice.

3 new tests in injection-patterns.test.mjs (rot13 detection, sub-40
char suppression, plaintext path still green). Total 168 tests pass.

Closes E3 in critical-review-2026-04-20.md.
2026-04-30 15:21:03 +02:00
Kjell Tore Guttormsen
336e4db1b8 feat(pre-bash-destructive): T8 — base64-pipe-shell idiom (E9)
Adds BLOCK_RULE for the malware-loader pattern:
  echo|cat|printf <base64-blob> | base64 -d | <shell>

This is a common RCE delivery shape that bypasses static name-matching
gates by encoding the destructive command as a base64 blob. The new
rule fires only when the final pipe target is a shell interpreter
(bash, sh, zsh, dash, ksh) — base64 decoded into jq or any non-shell
consumer remains allowed.

5 new tests in pre-bash-destructive.test.mjs:
- 3 BLOCK cases (echo|base64|bash, printf|base64|sh, cat|base64|zsh)
- 2 FP probes (base64 -d -> jq passes; base64 -d alone passes)

Closes E9 in critical-review-2026-04-20.md.
2026-04-30 15:15:29 +02:00
Kjell Tore Guttormsen
761e81309b feat(bash-normalize): T7 — process substitution collapse (E8)
Strips bash process substitution syntax — <(cmd) and >(cmd) — so the
inner command name is surfaced to downstream regex gates. Defeats
evasion like `cat <(curl evil)` where the destructive command is
hidden behind /dev/fd/N pipe sugar.

Implementation: bounded innermost-first iteration, depth 3. Beyond
that the string is left as-is rather than recurse without bound.
Runs after the single-quote mask phase, so legitimate strings like
`'echo <(x)'` are preserved.

5 new T7 tests (collapse + nested + FP probes) in
bash-normalize-t7-t9.test.mjs (now 12 tests total).

Closes E8 in critical-review-2026-04-20.md.
2026-04-30 15:14:04 +02:00
Kjell Tore Guttormsen
037b9644f3 feat(bash-normalize): T9 — one-level variable substitution (E10)
Defeats split-and-substitute evasion where attackers split a destructive
command name across an assignment and a variable reference (X=rm; later
$X) so downstream regex gates miss the literal command name. T9 collects
prefix assignments (VAR=value at start of string or after ; & |) and
substitutes ${VAR} / $VAR forms with the captured value. One-level
forward-flow only — chained vars are not followed.

Documented limits in JSDoc:
- Quoted assignments (X="rm -rf") not parsed (whitespace stops capture)
- Substitution is global within string, not scoped. Acceptable because
  T3 strips unknown ${VAR} to '' afterwards.

Single-quoted literals are masked before T9 runs, so legitimate
strings are preserved (FP probe in tests).

7 new tests in bash-normalize-t7-t9.test.mjs.
Closes E10 in critical-review-2026-04-20.md.
2026-04-30 15:12:02 +02:00
Kjell Tore Guttormsen
0a0c1fc412 chore(llm-security): stage ignore patterns for session files
Add .local/ and HANDOFF-FINDINGS.local.md to .gitignore so session
handoff artifacts (NEXT-SESSION-PROMPT.local.md, scratch findings)
do not leak into commits.

Pre-flight for Batch C v7.3.0.
2026-04-30 15:07:35 +02:00
Kjell Tore Guttormsen
ae5c784ce7 Revert "feat(ultraplan-local): M0 — profile foundation, no behaviour change"
This reverts commit 0b28f008ae.
2026-04-30 14:33:36 +02:00
Kjell Tore Guttormsen
59f1fe1631 Revert "feat(ultraplan-local): M1 — profile recommendation flow in ultrabrief"
This reverts commit 7e2d9e151e.
2026-04-30 14:33:36 +02:00
Kjell Tore Guttormsen
7e2d9e151e feat(ultraplan-local): M1 — profile recommendation flow in ultrabrief
Adds the profile recommendation step to /ultrabrief-local Phase 4. The
brief stays universal (same questions, same template); the new step is
purely a processing-decision layer that records which profile downstream
commands should apply.

What lands:
- agents/profile-recommender.md — new sonnet agent that scores available
  profiles against the finalized brief (keyword + NFR-signal matching,
  axis bumps, hallucination gate that forbids inventing profile names).
  Emits a fenced JSON block with ranked entries.
- templates/ultrabrief-template.md — frontmatter gains
  recommended_profile, profile_match, profile_rationale (default values
  applied when only `default` is available — true at M1).
- commands/ultrabrief-local.md — Phase 4 gains Step 4h with explicit
  branches: short-circuit when only `default` exists; AskUserQuestion
  confirmation when top score ≥ 0.7; explicit fallback message when below
  threshold; manual selection sub-question on user override. Persists the
  three frontmatter fields to brief.md after user confirmation. JSON
  parser failure falls back to `default` with `profile_match: fallback`
  rather than blocking — silent fallback is the worst outcome, but a
  *visible* fallback is acceptable.
- scripts/profile-loader.mjs — adds selectRecommendation(ranked, opts) +
  RECOMMENDATION_THRESHOLD=0.7 export. Single source of truth for the
  threshold logic so the command spec and the helper agree.
- scripts/profile-loader.test.mjs — 10 new tests for selectRecommendation
  (default-only, empty/malformed input, above/below threshold, custom
  threshold, max-by-score, missing fields). Total now 36/36.
- README.md / CLAUDE.md / marketplace landing — docs reflect M0 + M1
  shipped, M2 + M3 still pending.

In practice nothing changes for users at M1 because only `default` is
available — Step 4h takes the short-circuit path and writes
`profile_match: default-only`. M2 ships the additional profiles that
make the recommender meaningful.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 14:21:54 +02:00
Kjell Tore Guttormsen
0b28f008ae feat(ultraplan-local): M0 — profile foundation, no behaviour change
Introduces a profile-loader infrastructure for runtime-instantiable
ultraplan variants (depth × domain × goal axes). M0 ships only the
`default` profile, which mirrors the current hardcoded Phase 5/9 agent
set — so existing flows are unaffected.

What lands:
- profiles/default.yaml — schema v1, lists current 8 exploration agents
  + 2 review agents, captures today's adversarial regime
- scripts/profile-loader.mjs — null-deps Node loader with limited-subset
  YAML parser, listProfiles(), loadProfile(), validateProfile() that
  cross-checks every referenced agent exists in agents/
- scripts/profile-loader.test.mjs — 26 node:test cases (parser, validation,
  loader, integration with built-in default.yaml)
- commands/ultraplan-local.md — Phase 1 gains a "Resolve the profile"
  step (--profile flag → brief.recommended_profile → default fallback)
  and prints profile + source in the mode report. Phase 5/9 unchanged.
- README.md, CLAUDE.md, marketplace README — documentation of the M0
  foundation, the universal-brief design principle, and the M1/M2/M3
  milestones to come.

M1 (next) wires profile recommendation into ultrabrief Phase 4. M2
ships the additional built-in profiles (quick, bugfix, feature, refactor,
security-deep, research-heavy) and replaces the hardcoded Phase 5 agent
table with profile-driven selection. M3 adds user-extensible profiles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 14:14:20 +02:00
Kjell Tore Guttormsen
3b57dfbf6d chore(release): bump to v7.2.0
Batch B release — closes critical-review B-tier scanner defects
(B3, B5, B6, B7) and the v7.2.0 evasion-arsenal hardening patches
(E1, E4, E5, E7, E15, E16, E17, E18). Tests 1522 → 1665+, attack
simulator 64 → 72 (100 % pass).

Version updates across the 6 sync targets:

  - package.json
  - .claude-plugin/plugin.json
  - CLAUDE.md (header + test count: 1511 → 1665+)
  - README.md (badge + Version History row)
  - scanners/ide-extension-scanner.mjs (VERSION constant)
  - ../../README.md (marketplace root)

CHANGELOG [7.2.0] entry per Keep a Changelog with full Added /
Changed / Documentation / Tests / Notes breakdown.

Refs: Batch B Wave 6 / Step 15
2026-04-29 15:40:15 +02:00
Kjell Tore Guttormsen
8d8d4e7002 feat(red-team): 8 new evasion-arsenal scenarios for v7.2.0 (E1/E4/E5/E7/E16/E17)
Adds attack-simulator coverage for the new defenses landed earlier in
Batch B. All eight scenarios pass against the current hooks (72/72,
zero gaps). E15 (memory-poisoning glob) and E18 (entropy markdown-image
CDN allowlist) are scanner-only and have unit/integration coverage in
their respective scanner test files.

  unicode-evasion (pre-prompt-inject-scan):
    UNI-007  E1  PUA-A range hidden Unicode               → HIGH advisory
    UNI-008  E1  PUA-B range hidden Unicode               → HIGH advisory
    UNI-009  E16 Greek-Latin homoglyph fold               → CRITICAL block

  mcp-output (post-mcp-verify):
    MCP-005  E4  Markdown link-title injection            → markdown-link-title-injection
    MCP-006  E5  SVG <desc> injection                     → svg-element-injection
    MCP-007  E5  SVG <foreignObject> injection            → svg-element-injection
    MCP-008  E7  HTML comment-node injection (no marker)  → html-comment-injection

  session-trifecta (post-session-guard):
    TRI-004  E17 Escalation-after-input (WebFetch → Task) → escalation-after-input advisory

Payload helpers `buildPuaAPayload` / `buildPuaBPayload` shift each
character into Supplementary Private Use Area-A / -B respectively.
The Greek-fold payload uses Greek ι (U+03B9 → i) and ο (U+03BF → o)
so foldHomoglyphs reproduces the canonical "ignore previous
instructions" CRITICAL pattern.

Total: 64 → 72 scenarios.

Refs: Batch B Wave 6 / Step 14 / v7.2.0
2026-04-29 15:35:32 +02:00
Kjell Tore Guttormsen
f0fb7505fb fix(entropy): E18 — rule 18 markdown-image CDN-aware + secret pre-check
The v7.0.0 entropy-scanner rule 18 suppressed every line whose pattern
matched ![…](https?://…) — regardless of the URL host or what the URL
carried. A markdown image URL pointing at a non-CDN host (or carrying a
secret-shaped token in its query string) would therefore mask a real
high-entropy credential.

Refactor:

  * MARKDOWN_IMAGE now captures the full URL (was a host-only prefix
    matcher), so rule 18 can inspect host and query.
  * MARKDOWN_IMAGE_CDN_HOSTS allowlist constant covers cdn./images./
    media./assets./static./*.cdn./*.amazonaws.com/{s3,cloudfront}/
    *.cloudflare./*.fastly./*.akamaized./raw.githubusercontent.com/
    *.imgix.net/*.cloudinary.com/.
  * MARKDOWN_IMAGE_QUERY_SECRET catches secret-shaped query keys
    (token, key, secret, password, api_key, access_token, auth) plus
    well-known provider prefixes (AKIA, Bearer, sk_live_, ghp_, ghs_,
    ghu_, gho_, ghr_, npm_).
  * Rule 18 now suppresses iff (host matches CDN allowlist) AND
    (query has no secret-shaped token). Anything else falls through
    to entropy classification.

+4 tests in tests/scanners/entropy-context.test.mjs (29 → 33).
Existing rule 18 fixture (cdn.example.com, no secret query) still
suppresses, so no regression on the legitimate path.

Refs: Batch B Wave 5 / Step 13 / v7.2.0
critical-review-2026-04-20.md §E18
2026-04-29 15:18:37 +02:00
Kjell Tore Guttormsen
04f1593df3 refactor(entropy): B5 — two-stage context-classified suppression pipeline
The v7.0.0 entropy-scanner ran rules 11-13 (GLSL/CSS-in-JS/inline-markup
line-proximity suppressions) for every line regardless of file type. A
polyglot `.ts` file with an embedded fragment-shader template literal
could therefore mask a real high-entropy credential when the credential
literal happened to share a line with a GLSL keyword. Critical-review
B5 documented the false-negative class.

Refactor:

  * New `classifyFileContext(absPath, lines)` returns
    `'shader-dominant' | 'markup-dominant' | 'code-dominant' | 'mixed'`,
    keyed off file extension with a content-density fallback for
    code-extension files (≥50% of sampled non-blank lines matching
    GLSL/inline-markup → downgrade to `mixed`).

  * `isFalsePositive(str, line, absPath, context)` gates rules 11-13
    on `context !== 'code-dominant'`. Rules 1-10 and 14-19 still run
    unconditionally, so URL/path/test-fixture/ffmpeg/UA/SQL/error-
    template suppression behaves identically.

  * `scanFileContent` computes `fileContext` once per file and threads
    it through every per-string suppression check.

Conservative defaults to keep the regression surface minimal:

  * Files with `<5` sampled non-blank lines fall back to `mixed`
    (preserves the existing rule-11/12/13 behaviour for the single-
    line .js fixtures used by entropy-context.test.mjs).
  * Unknown extensions fall back to `mixed`.
  * Code-extension files densely populated with shader/markup
    content fall back to `mixed`.

Net effect: a `.ts` file with an embedded GLSL block but mostly TS
code on the surrounding lines now surfaces credentials that the
v7.0.0 line-proximity heuristic suppressed. Pure shader/markup
files are unaffected (extension skip / mixed default).

New fixture: tests/fixtures/entropy/polyglot-ts-with-glsl.ts (with
runtime placeholder so it does not commit a high-entropy literal).

+3 tests in tests/scanners/entropy-context.test.mjs (26 → 29).
Existing entropy.test.mjs and entropy-context.test.mjs all remain
green. Full suite 1658 → 1661.

Refs: Batch B Wave 5 / Step 12 / v7.2.0
critical-review-2026-04-20.md §B5
2026-04-29 15:13:13 +02:00
Kjell Tore Guttormsen
d441abba20 feat(post-mcp-verify): E7 — scan HTML comment nodes for injection
The existing CRITICAL pattern in injection-patterns.mjs only fires when
a comment body contains AGENT/AI/HIDDEN markers. Adversaries can drop
the marker and still hide instructions inside <!-- ... --> for any
agent that reads page source. This generalizes the comment scan: every
comment body is HTML-entity-decoded and run through the full
injection rule set. The existing keyword-restricted pattern still
fires (defense-in-depth).

Emits at the strongest tier with category html-comment-injection.

+3 tests (65 → 68).

Refs: Batch B Wave 4 / Step 11 / v7.2.0
2026-04-29 15:01:56 +02:00
Kjell Tore Guttormsen
716c8384d9 feat(post-mcp-verify): E5 — scan SVG desc/title/metadata/foreignObject
SVG containers carry text that is invisible in the rendered image but
fully parsed by an agent reading the source. <desc>, <title>,
<metadata>, and <foreignObject> are all valid surfaces for adversarial
injection.

Adds a per-element extractor inside the existing HTML-tag gate, gated
on /<svg[\s>]/i so it only fires for actual SVG content. Inner text is
HTML-entity-decoded then run through scanForInjection. Emits at the
strongest tier with category svg-element-injection.

+3 tests (62 → 65).

Refs: Batch B Wave 4 / Step 10 / v7.2.0
2026-04-29 14:54:58 +02:00
Kjell Tore Guttormsen
b95d85bb4c feat(post-mcp-verify): E4 — scan markdown link titles for injection
Adversarial payloads in markdown link title attributes (rendered as
tooltips, parsed by agents) bypassed the existing HTML-content checks
which gated on `<tag>` presence. Pattern: [text](url "title").

Adds linkTitleRegex extraction to the HTML-content block, runs each
captured title through scanForInjection, emits at the strongest tier
encountered with category markdown-link-title-injection.

+3 tests (62 → 62 in post-mcp-verify.test.mjs file, was 59).

Refs: Batch B Wave 4 / Step 9 / v7.2.0
2026-04-29 14:52:30 +02:00
Kjell Tore Guttormsen
6073952b97 fix(injection): E16 ASCII fast-path + UNI-003 expectation update (v7.2.0)
Two follow-up fixes after E16 + E17 landed:

1. foldHomoglyphs ASCII fast-path
   - scanForInjection calls foldHomoglyphs on every scan (raw + normalized).
   - Pre-fix: NFKC normalization runs unconditionally, even on pure
     ASCII inputs where it's a no-op.
   - Result: benchmark.test.mjs timed out at 120s on the full suite.
   - Fix: charCodeAt sweep for >=128, short-circuit return s when
     all ASCII. NFKC and HOMOGLYPH_MAP iteration only run when
     non-ASCII chars are present (the actual attack case).
   - Verified: benchmark.test.mjs passes within timeout.

2. Attack-scenario UNI-003 expectation
   - Pre-E16: "Homoglyph Cyrillic-Latin mixing" payload triggered only
     a MEDIUM "obfuscation present" advisory (exit 0, stdout match
     "MEDIUM").
   - Post-E16: the same payload is folded to Latin BEFORE pattern
     matching, so it now matches CRITICAL "ignore previous instructions"
     and blocks (exit 2).
   - This is the intended v7.2.0 behavior — not a regression. Updated
     expectation: exit_code 2, stdout_match "block". Renamed scenario
     to "now blocked via E16 fold, v7.2.0".

Suite: pre-compact-scan flake remains (perf-budget under load,
passes isolated). All other tests green.
2026-04-29 14:44:41 +02:00
Kjell Tore Guttormsen
f0a1d4024a feat(post-session-guard): E17 — configurable escalation window + 20-call MEDIUM advisory
Critical-review §4 E17 finding: pre-v7.2.0 the delegation-after-input
advisory fired only within a 5-call window. Attackers who deliberately
waited 6+ calls before delegating bypassed detection. Window was also
hardcoded — operators couldn't tune it for their environment.

Two coordinated changes:

1. LLM_SECURITY_ESCALATION_WINDOW env var (primary window override)
   - parseInt(env) || getPolicyValue('trifecta', 'escalation_window', 5)
   - Mirrors the established pattern from
     LLM_SECURITY_TRIFECTA_MODE et al.
   - Setting env=3 narrows; env=8 expands.

2. Secondary 20-call MEDIUM advisory (slow-burn variant)
   - DELEGATION_ESCALATION_WINDOW_MEDIUM = 20 (hardcoded — same value
     for all operators; tunable in a future patch if needed)
   - checkEscalationAfterInput now returns `tier: 'primary'|'secondary'|null`
   - formatEscalationWarning emits a different message for secondary —
     mentions "slow-burn", references env-var, distinct from the
     primary "DeepMind Category 4" framing

Hook reads max(WINDOW_SIZE, secondary+5) entries to cover the wider
window. Existing duplicate-suppression (`escalation_warning` state
entry) covers both tiers. Audit-trail event captures `tier` field.

Tests: +5 cases in tests/hooks/post-session-guard.test.mjs:
- secondary window catches 9-call distance (slow-burn)
- secondary boundary at exactly 20 calls
- primary regression guard (1-call distance)
- env=3 narrows primary (4-call distance becomes secondary)
- env=8 expands primary (7-call distance stays primary)

Updated existing test "does NOT trigger when input_source is >5 calls
ago" — now requires >20 calls (secondary window catches 6-20).

Suite: 1644 → 1672 (+28 from new tests + extended scope). All green.

CLAUDE.md hooks table updated to document both windows and the env var.
2026-04-29 14:26:18 +02:00
Kjell Tore Guttormsen
ec4ae268da feat(injection): E16 — homoglyph NFKC fold before every pattern match
Critical-review §4 E16 finding: pre-v7.2.0 homoglyph normalization fired
ONLY for the MEDIUM-advisory "obfuscation present" signal. Pattern
matchers in scanForInjection compared against raw + decoded variants
only — they did NOT compare against a fold-normalized variant. As a
result, "ignоre previous instructions" (Cyrillic о, U+043E) bypassed
the CRITICAL "ignore previous" pattern.

Two coordinated edits:

scanners/lib/string-utils.mjs
- Adds HOMOGLYPH_MAP (frozen) — surgical Cyrillic/Greek → Latin map.
  ~25 entries focused on injection-vocabulary letters
  (a, e, o, c, p, x, y, i, j, s, l, A, E, O, C, P, X, Y, T).
- Adds foldHomoglyphs(s) — pipeline: NFKC → apply HOMOGLYPH_MAP.
  NFKC handles Mathematical Alphanumeric (U+1D400 block), fullwidth
  Latin (U+FF21 block), ligatures, width variants.

Excluded by design from HOMOGLYPH_MAP:
- Latin Extended (æ, ø, å, é, è, ñ, ü, ö, ä, ç, ß, þ, ð) — legitimate
  Norwegian/German/French/Spanish letters. Map them and we false-positive
  on every non-English source file.
- Greek letters not visually overlapping (β, γ, δ, ...)
- Cyrillic letters not visually overlapping (б, г, д, ж, ...)

scanners/lib/injection-patterns.mjs
- scanForInjection now builds a 4-variant set: raw, normalized,
  folded(raw), folded(normalized). Set deduplication skips redundant
  identical variants. Existing dedup-by-label (seenLabels Set) prevents
  double-counts when the same pattern matches in multiple variants.
- foldHomoglyphs added to the imports.

Tests: +27 cases in tests/lib/string-utils-homoglyph.test.mjs:
- 6 Cyrillic → Latin (lowercase, uppercase, multiple substitutions,
  Palochka U+04CF)
- 3 Greek → Latin
- 2 NFKC normalization (Math Bold, Fullwidth)
- 8 preserves-non-confusable (Norwegian æøå, German umlauts, French
  accents, Spanish ñ, emoji, CJK, Arabic/Hebrew)
- 3 edge cases (empty, null/undefined, idempotency)
- 5 scanForInjection integration (Cyrillic ignore, Cyrillic Assistant,
  Norwegian non-trigger, benign "ignore" comment, mixed Cyrillic+Greek)

Test-development found: U+1D5DC is "I" not "A" (test pin caught my
codepoint mistake — fixed during dev).

Suite: 1617 → 1644 (+27). All green.
2026-04-29 14:22:05 +02:00
Kjell Tore Guttormsen
6cef80c640 feat(unicode): E1 — extend hidden-Unicode detection to PUA-A and PUA-B
Critical-review §4 E1 finding: pre-v7.2.0 the Unicode-stego detector
(`containsUnicodeTags`) covered only U+E0001-E007F (Tag block). Private
Use Areas — also invisible in most terminals and surviving normalization
— were not detected. Attackers could encode payloads in PUA codepoints
that pass through `scanForInjection` undetected.

Coverage extended to:
- U+E0001-E007F  Unicode Tag block       (existing — DeepMind kat. 1)
- U+F0000-FFFFD  Supplementary PUA-A      (NEW — E1)
- U+100000-10FFFD Supplementary PUA-B     (NEW — E1)

Detection-only for PUA: PUA characters have NO standard ASCII mapping,
so `decodeUnicodeTags` leaves them unchanged. Detection alone is
sufficient — `scanForInjection` emits HIGH on any presence, regardless
of decoded content.

Function name `containsUnicodeTags` preserved for back-compat. All
existing call sites (injection-patterns.mjs:259, etc.) work unchanged.
Semantically the function is now "containsHiddenUnicode".

Tests: +21 cases in tests/lib/string-utils-hidden-unicode.test.mjs:
- 5 Tag-block regression guards
- 4 PUA-A range cases (start, just-inside, end, buried-in-ASCII)
- 3 PUA-B range cases
- 5 boundary cases (gap U+E0080-EFFFF, U+10FFFE noncharacter, emoji,
  CJK, Latin Extended — all must be FALSE)
- 4 decodeUnicodeTags passthrough cases (PUA-A unchanged, PUA-B
  unchanged, Tag block still decodes, mixed Tag+PUA)

Suite: 1596 → 1617 (+21). All green.
2026-04-29 14:18:49 +02:00
Kjell Tore Guttormsen
b0f1a9abfd fix(memory-poisoning): E15 — add .claude/agents/*.md to target glob
Critical-review §4 E15 finding: agent files in .claude/agents/ are loaded
as Claude Code subagent system prompts and are a direct memory-poisoning
surface. Pre-v7.2.0 the scanner covered CLAUDE.md, .claude/rules/*.md,
memory/*.md, REMEMBER.md, .local.md, and .claude-plugin/plugin.json —
but not .claude/agents/*.md.

Single-line addition to MEMORY_FILE_PATTERNS:
  /(?:^|\/)\.claude\/agents\/[^/]+\.md$/

The existing scan loop, scanForInjection integration, and severity-
mapping logic all apply unchanged. STRICT_FILES_PATTERN intentionally
NOT extended — agents may legitimately quote shell commands as examples
(consistent with CLAUDE.md treatment).

Tests: +3 cases in tests/scanners/memory-poisoning.test.mjs:
- "scans .claude/agents/*.md" (smoke test — at least one finding from
  the new fixture)
- "agent file injection pattern detected"
- "agent file credential path detected"

New fixture: tests/fixtures/memory-scan/poisoned-project/.claude/agents/
poisoned-agent.md — agent with injection, credential ref, permission
expansion, and exfil URL. Triggers all 4 detection categories.

Suite: 1591 → 1594 (+3). All green.
2026-04-29 14:13:01 +02:00
Kjell Tore Guttormsen
5f8f2d3c41 fix(dep): B7 — token-overlap typosquat heuristic alongside Levenshtein
Critical-review §2 B7 finding: pure Levenshtein <=2 misses the most common
modern typosquat pattern — popular-name + token-injection suffix. Examples:
  lodash → lodash-utils    (edit distance 6, not flagged pre-B7)
  react  → react-helper    (edit distance 7, not flagged pre-B7)
  express → express-wrapper (edit distance 8, not flagged pre-B7)

Three coordinated edits:

scanners/lib/string-utils.mjs
- Adds tokenize(name): string[]    splits on -/_, lowercases
- Adds tokenOverlap(a, b): number  intersection.size / min(|a|,|b|)
- Adds TYPOSQUAT_SUSPICIOUS_TOKENS frozen list of common typosquat
  suffixes. Excludes language-extension tokens (js, jsx, ts, tsx) — the
  v7.0.0 allowlist contains `tsx` as a legit package and including the
  same token in the suspicious set creates a contradiction. Caught by
  the new allowlist-intersection-guard test. Also excludes 'pro'
  (legitimate edition marker).

scanners/dep-auditor.mjs + scanners/supply-chain-recheck.mjs
- New checkTyposquatTokenOverlap() helper — fires AFTER Levenshtein 1/2
  branches, only when:
    1. popular package's tokens ⊆ declared name's tokens (strict superset)
    2. declared name has at least one suspicious suffix
    3. popular package is in topCutoff window
  All three conditions required — conservative by design. Allowlist
  precedence preserved (existing 22 npm + 13 PyPI entries always pass).
  MEDIUM severity, NOT block. New finding title prefix:
  "Possible typosquatting via token-overlap".

Tests: +21 cases across two new files
- tests/lib/string-utils-tokens.test.mjs (15) — tokenize, tokenOverlap,
  TYPOSQUAT_SUSPICIOUS_TOKENS frozen contract, allowlist-intersection
  guard (caught the tsx conflict on first run)
- tests/scanners/dep-token-overlap.test.mjs (7) — integration via
  in-memory tmpdir fixtures: lodash-utils flagged, react-helper flagged,
  express-wrapper flagged, lodash exact NOT flagged, allowlist tools
  (knip/tsx/nx/rimraf) NOT flagged, react-router-dom (no suspicious
  suffix) NOT flagged, react itself (equal token set, not superset)
  NOT flagged.

Existing dep.test.mjs and supply-chain-recheck.test.mjs unchanged —
all green (149 → 149 regression guard).

Suite: 1570 → 1591 (+21). All green.
2026-04-29 14:10:53 +02:00
Kjell Tore Guttormsen
68b9ea2692 fix(taint-tracer): B6 — recognize destructuring + spread + rest patterns
Critical-review §2 B6 finding: extractAssignedVariable handled
`const X = ...` and `X = ...` but missed every modern JS/TS
destructuring pattern. Sinks downstream of destructured/spread vars
produced false negatives at the propagation step.

Patterns now recognized:
- `const { x } = source`               object destructuring
- `const { x, y } = source`            multi-key
- `const { secret: alias } = source`   renamed (key NOT bound)
- `const { x, ...spread } = source`    object rest
- `const { a, b: { c } } = source`     nested object (key NOT bound)
- `const [a, b] = source`              array destructuring
- `const [first, ...rest] = source`    array rest
- `const [a, [b, c]] = source`         nested array
- `const { user: { id }, ...rest }`    mixed nested

Implementation: regex-based two-pass walker. Pass 1 detects whether
the LHS is a destructuring pattern (`{...}` or `[...]`). If yes, the
new `extractDestructuredNames` helper walks the pattern body via a
balanced-bracket depth counter, recurses into nested patterns, and
distinguishes keys (`key:`) from bindings. If no, the plain-decl
branch matches `\b(?:const|let|var)\s+(\w+)`.

Plain-assignment branch (`X = ...` without keyword) and Python-style
patterns are unchanged.

The function is now exported for direct unit testing — same pattern
as `_resetCacheForTest` in policy-loader. The internal walker
(`extractDestructuredNames`) remains module-private.

Tests: +19 cases in tests/scanners/taint-destructuring.test.mjs:
  - 5 pre-B6 patterns (regression guard: plain decl, plain assign,
    no-match on equality)
  - 12 destructuring patterns covering object/array/rest/nested
  - 2 non-destructuring regressions (return literal, arrow param)

Existing taint-tracer.test.mjs and taint.test.mjs unchanged — both
green (14 → 14, fixture-based integration tests not affected).

Suite: 1551 → 1570 (+19). All green.
2026-04-29 14:05:34 +02:00
Kjell Tore Guttormsen
d3b1157a08 docs(scoring): unify scan/audit/mcp-scanner/posture-assessor to v2 formula
Closes the v7.1.1 out-of-scope item: commands/scan.md:113-114 retained
the v1 formula. Exploration found two more v1 surfaces that v7.1.1
missed: commands/audit.md:46 and agents/mcp-scanner-agent.md:419, plus
agents/posture-assessor-agent.md:376 (caught by the new doc-consistency
test). Four files unified to v2 in one atomic commit.

Three-way → four-way verdict-divergence is now closed:
- scanners/lib/severity.mjs (v2, BLOCK ≥65, WARNING ≥15) — authoritative
- agents/skill-scanner-agent.md (v2 since v7.1.1)
- templates/unified-report.md (v2 since v7.1.1)
- commands/scan.md (v2 — this commit)
- commands/audit.md (v2 — this commit)
- agents/mcp-scanner-agent.md (v2 — this commit)
- agents/posture-assessor-agent.md (v2 — this commit)

New: tests/lib/doc-consistency.test.mjs walks commands/ + agents/ and
asserts NO file contains v1 formula tokens. Pinned regex set:
  - score >= 61, score >= 21, score ≥ 61, score ≥ 21
  - critical * 25, Critical × 25
  - min(100, critical*25 ...)

Plus three v2-cutoff anchors asserting commands/scan.md, commands/audit.md,
and agents/mcp-scanner-agent.md document the v2 BLOCK ≥65 cutoff (or
reference riskScore() helper).

Tests: 1523 → 1551 (+28 from doc-consistency: 25 file walks + 3 anchors).
All green.
2026-04-29 13:58:25 +02:00
Kjell Tore Guttormsen
3cd68dc9fb docs(severity): B3 — document info as scoring-inert (v7.2.0 prep)
Critical-review §2 B3 finding: `riskScore({info: N}) = 0` silently masks
info-volume findings. The behavior was correct (info is scoring-inert by
design) but undocumented. Operators reading a report with N info findings
had no way to know they contribute zero to verdict/band.

Three coordinated edits:
- scanners/lib/severity.mjs JSDoc — explicit "Info severity" subsection
  spelling out: scoring-inert, surfaced in owaspCategorize aggregates,
  treat as observability telemetry not verdict input. @param updated to
  mark info as accepted but ignored.
- CLAUDE.md v7.0.0 risk-score-v2 line — one-sentence anchor pointing to
  severity.mjs JSDoc.
- tests/lib/severity.test.mjs — anchor test alongside the existing
  4-critical=93 anchor: asserts riskScore({info: 50}) === 0,
  riskScore({info: 1000}) === 0, verdict({info: 100}) === 'ALLOW',
  riskBand(riskScore({info: 500})) === 'Low'.

Decision: skip the optional `infoScore()` helper from the brief. No
current consumer would use it; doc-only fix keeps API surface minimal.
Revisit if a consumer emerges.

Tests: 1522 → 1523 (+1 anchor block, 4 assertions). All green.
2026-04-29 13:56:11 +02:00
Kjell Tore Guttormsen
b18cb329ef docs(llm-security): v7.1.1 — narrative coherence patch
Documents the v7.1.1 narrative-coherence patch in CLAUDE.md (mini-block
appended after the v7.0.0 paragraph) and CHANGELOG.md (new [7.1.1]
section per Keep a Changelog convention, placed above [7.1.0]).

Plan: .claude/plans/ultraplan-2026-04-29-report-coherence.md
Brief: .claude/ultraplan-spec-2026-04-29-report-coherence.md

Verification gates passed:
- npm test: 1522/1522 (was 1511; +11 from new narrative test)
- node --test tests/lib/severity.test.mjs: 86/86 (co-monotonicity sweep
  at lines 252-303 unchanged and green)
- node --test tests/scanners/skill-scanner-narrative.test.mjs: 11/11
- Orchestrator against fixture: WARNING / 48 / 1 HIGH (HITL trap caught
  correctly, no whiplash)
- SARIF inline check via toSARIF import: sarif-version 2.1.0, runs: 1
- Zero remaining v1 cutoffs in agent + template

Out of scope but flagged for Batch B (deferred to v7.2.0):
- commands/scan.md:113-114 retains v1 risk formula

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 12:57:54 +02:00
Kjell Tore Guttormsen
5cfbc70472 test(llm-security): narrative-coherence contract test (v7.1.1)
11 assertions across 4 describe groups against tests/fixtures/skill-scan/
hyperframes-like/. Tests the deterministic input layer that feeds
skill-scanner-agent — does NOT invoke the LLM (no precedent in 1511 tests).

Coverage:
- content-extractor (5 it): exit 0 on animation markup; exactly 1 HIGH
  HITL trap; >= 2 process.env credential refs; has_injection=true (any
  injection signal flips it); has_critical_injection=false (no CRITICAL
  in fixture).
- entropy scanner (2 it): calibration block present; <= 1 finding (rest
  suppressed via line-context rules).
- co-monotonicity (2 it): {high:1} → WARNING/High; {high:1, info:1} →
  WARNING (info scoring-inert). Inline guard mirrors the sweep at
  tests/lib/severity.test.mjs:252-303 so this file fails fast if the
  invariant drifts.
- agent prompt contract (2 it): static asserts that
  agents/skill-scanner-agent.md contains 'Step 2.5: Context-First
  Severity Assignment', 'summary.narrative_audit.suppressed_findings',
  'score>=65', AND zero remaining 'score >= 61' references; same v2-
  cutoff + narrative-audit contract on templates/unified-report.md.

Part of v7.1.1 narrative-coherence patch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 12:50:27 +02:00
Kjell Tore Guttormsen
3abd7ffeab test(llm-security): hyperframes-like fixture for narrative coherence
Synthetic skill content mimicking the noise profile of frontend
animation projects (HTML5 canvas, framework env-vars, inline SVG data
URIs, CSS keyframes) plus exactly one genuine HITL trap signal.

Used by tests/scanners/skill-scanner-narrative.test.mjs (added in
v7.1.1) to exercise:
- content-extractor: HIGH HITL trap signal + framework env-var
  references (process.env.REACT_APP_*, VITE_PUBLIC_*)
- entropy scanner: inline SVG data URI suppressed via line-context rules

The .llm-security-ignore file uses the SCANNER:glob format
(scanners/scan-orchestrator.mjs:34-40) — ENT:**/*.md suppresses any
entropy-scanner findings when the fixture is run through scan-orchestrator
in the Step 6 smoke test.

Part of v7.1.1 narrative-coherence patch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 12:49:19 +02:00
Kjell Tore Guttormsen
67ffff13a4 fix(llm-security): skill-scanner-agent — context-first severity, v2 alignment, Suppressed Signals section
Five coordinated edits to address scan-rapport whiplash at the agent
prompt level:

- Step 2.5 (NEW): Context-First Severity Assignment. Every signal has
  exactly one disposition — suppressed (counted only) or reported (full
  finding). The split happens BEFORE severity is assigned. Forbids
  'false positive', 'legitimate framework', 'no action required' in
  finding-body text; reserves them for the Suppressed Signals section.
- Verdict Logic: replaces stale v1 sum-and-cap formula (BLOCK >=61) with
  v2 reference (severity-dominated, BLOCK >=65) matching severity.mjs
  since v7.0.0. Documents that severity counts MUST exclude suppressed
  signals; introduces verdict_rationale field for descriptive context
  when suppressed >= 5 AND reported <= 1 high.
- Output Format: adds Suppressed Signals as required section #4 with
  category-level bullet format. Documents the trailing JSON shape
  including summary.narrative_audit.suppressed_findings.{count,
  by_category} and verdict_rationale fields.
- Comment block before Category 2 suppression rules clarifies that
  'false positive' as taxonomy language is OK; only finding-body
  description fields are forbidden from using the phrase.
- Step 0 (Norwegian generaliseringsgrense) preserved unchanged.

Part of v7.1.1 narrative-coherence patch (plan: .claude/plans/ultraplan-2026-04-29-report-coherence.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 12:47:58 +02:00
Kjell Tore Guttormsen
899cb5c121 fix(llm-security): template — v1 → v2 risk constants + narrative_audit block
Updates the HTML-comment risk-formula reference at lines 55-66 from the
stale v1 sum-and-cap formula to the v2 severity-dominated tiers that
have been authoritative in scanners/lib/severity.mjs since v7.0.0. Adds
a Narrative Audit block inside the Executive Summary section surfacing
summary.narrative_audit.suppressed_findings.{count,by_category} from
the agent's trailing JSON. The block is transparency only — it does
NOT affect risk_score, riskBand, or verdict.

Part of v7.1.1 narrative-coherence patch (plan: .claude/plans/ultraplan-2026-04-29-report-coherence.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 12:45:28 +02:00
Kjell Tore Guttormsen
1e555b6833 docs(llm-security): add v7.1.0 row to README version history
The v7.1.0 release commit (621db14) bumped the version badge and added a
CHANGELOG entry, but missed the README Version History table. Adding
the row now so the public-facing version history at
git.fromaitochitta.com/open/ktg-plugin-marketplace reflects v7.1.0.

Row covers: B1 + B2 + B4 fixes, A3 honesty-sweep (7 phrases), B8 CaMeL
nedton, test count 1487 → 1511, "why" framing tied to critical-review
§F CISO perspective.
2026-04-29 12:03:10 +02:00
Kjell Tore Guttormsen
621db144bd chore(release): bump llm-security to v7.1.0
Closes A4 of v7.1.0 critical-review patch — release artefacts.

- Version bump 7.0.0 → 7.1.0 across active version sources:
  * package.json
  * .claude-plugin/plugin.json
  * CLAUDE.md header
  * README.md badge
  * scanners/ide-extension-scanner.mjs (VERSION constant)
  * marketplace root README plugin entry
- Marketplace root README test count: 1487 → 1511.
- CHANGELOG.md: new [7.1.0] - 2026-04-29 section above [7.0.0],
  documenting B1, B2, B4, B8, honesty-sweep (7 phrases), and
  test-count delta (+24 → 1511 total).
- docs/security-hardening-guide.md: §6 last-updated bump + new
  v7.1.0 calibration note on hook-level fixes (pathguard regex
  hole, distributed-trifecta block-mode bypass).

Historical references to "7.0.0" intentionally preserved in:
- CHANGELOG [7.0.0] entries (history)
- README.md version-history table v5.0.0/v7.0.0 rows (history)
- CLAUDE.md §"v7.0.0 — Severity-dominated risk scoring" (describes
  what changed at v7.0.0 release)
- scanners/ JSDoc comments noting "v7.0.0+" formula provenance
- agents/ + tests/ + knowledge/ provenance comments

Pre-existing untracked/modified tracker noise (.gitignore,
marketplace.json, config-audit/docs, ultraplan-local/docs) is not
part of this commit per the v7.1.0 NEXT-SESSION-PROMPT handoff.

Tests: 1511/1511 green.
2026-04-29 11:57:16 +02:00
Kjell Tore Guttormsen
a46308b1e9 docs(llm-security): A3 honesty-sweep — 7 sitater nedtonet (critical-review §9)
Closes A3 of v7.1.0 critical-review patch. Each rewrite preserves the underlying
claim where it is accurate but removes hype/overreach language. Historical
CHANGELOG/README version-table rows are intentionally left as-is (they document
what was claimed at the time of release, not what is true today).

Changes (CLAUDE.md, commands/ide-scan.md, knowledge/mitigation-matrix.md,
docs/security-hardening-guide.md):

- "Trustworthy scoring (BREAKING)" → "Severity-dominated risk scoring
  (v2 model, BREAKING)". Removes hype framing; describes the actual mechanism.
- "Context-aware entropy scanner" → "Rule-based entropy scanner with
  file-extension skip, 8 line-level suppression rules, and configurable policy".
  No ML/context inference; just rules.
- "1487 tests" → "1511 unit and integration tests; mutation-testing coverage
  not published". Updated count after A1+A2 (+24) and added qualifier.
- "Fully Schrems II compatible" → "Schrems II compatible in default offline
  mode. Optional OSV.dev enrichment (`supply-chain-recheck --online`)
  transmits package identifiers to a Google-operated API and is a separate
  compliance consideration." Acknowledges the OSV.dev opt-in caveat.
- "Rule of Two enforcement" → "Rule of Two detection (configurable; default
  warn; blocks on high-confidence trifectas in opt-in `block` mode; distributed
  trifectas detected but not blocked by default)". "Enforcement" implied
  block; default is warn.
- "Hardened ZIP extractor" → suffix " — no fuzz-testing results published
  to date". Caps and class-of-attacks rejected are accurate; absence of
  formal fuzz coverage now stated.
- "defense-in-depth" — preserved as framing, but quantified in
  security-hardening-guide §4: "three independent detection layers with
  documented bypass classes". Each layer named, each layer's known bypasses
  pointed to (critical-review §4 evasion arsenal).

Tests: 1511/1511 green (no behavioural change).
2026-04-29 11:52:55 +02:00
Kjell Tore Guttormsen
4aa5318bcb fix(llm-security): A2 batch — JSDoc arithmetic + co-monotonicity test + CaMeL nedton
Closes A2 of v7.1.0 critical-review patch (docs/critical-review-2026-04-20.md):

- B4 (severity JSDoc): 4 critical = 93, not 90. Fixed in scanners/lib/severity.mjs:23
  and CHANGELOG.md v7.0.0 tier description. The actual computation has always been
  93 (70 + log2(5)*10 = 93.22 → round); only the docs were wrong.

- §5.4 co-monotonicity: new sweep test in tests/lib/severity.test.mjs over 15
  representative count vectors. Asserts that (verdict, riskBand) agree under the
  v7.0.0 contract for every case — catches future drift between riskScore tiers,
  verdict cutoffs, and riskBand cutoffs. Includes a B4 anchor test (riskScore
  {critical: 4} === 93) so doc/code drift fails loudly.

- B8 (CaMeL claims toned down): post-session-guard.mjs:646 comment block and
  CLAUDE.md:184 Defense Philosophy bullet now describe the implementation
  honestly — opportunistic byte-matching of truncated output fingerprints
  (first 200 bytes, SHA-256/16-hex), not semantic data-flow tracking.
  Trivially bypassed by mutation, summarisation, or re-encoding. Inspired by
  CaMeL (DeepMind 2025), but not a CaMeL capability-tracking implementation.

Tests: 1495 → 1511 (+16: 15 sweep cases + 1 B4 anchor). All green.
2026-04-29 11:49:08 +02:00
Kjell Tore Guttormsen
36be963d4d fix(llm-security): B2 block-mode blocks all detected trifectas, not only high-confidence
Previously, `LLM_SECURITY_TRIFECTA_MODE=block` only exited 2 when the
detected trifecta was MCP-concentrated (all three legs via the same MCP
server) or involved sensitive-path + exfil. Distributed trifectas —
three legs originating from different tools, with a non-sensitive data
path and a non-sensitive exfiltration sink — were detected and warned
but not blocked. This mismatched the documented semantics of block mode
and gave operators a false sense of enforcement.

Change: remove the `(mcpInfo.concentrated || sensitiveExfil)` AND-gate
in the `TRIFECTA_MODE === 'block'` branch so any detected trifecta
blocks in block mode. Audit event `severity` still differentiates
critical (concentrated / sensitive-exfil) from high (distributed); the
blocked stderr message now explicitly names "Distributed trifecta:
three legs from different sources" when the confidence sub-signals
are absent.

Addresses critical review 2026-04-20 §2 B2 (HIGH) and §9 row 1
("enforces the Rule of Two").

Tests: 1 added (distributed trifecta in block mode now exits 2).
All 1495 tests pass.
2026-04-20 00:04:36 +02:00
Kjell Tore Guttormsen
751f1199c8 fix(llm-security): B1 pathguard regex — match multi-segment .env.*.*
The previous ENV regex `/[\\/]\.env\.[a-z]+$/` only matched a single
lowercase segment after `.env`. Multi-segment and mixed-case variants
such as `.env.production.local.backup`, `.env.stage-1.local`, and
`.env.CI.secret` slipped past the hook. Replaced with
`/[\\/]\.env(\.[A-Za-z0-9._-]+)*$/` which matches `.env` plus any
number of dot-separated alphanumeric/dot/hyphen/underscore segments.
`.envrc` (direnv config, no dot separator) is still allowed.

Addresses critical review 2026-04-20 §2 B1 (HIGH).

Tests: 7 added (6 new multi-segment BLOCK cases + 1 .envrc ALLOW).
All 1494 tests pass.
2026-04-19 23:59:38 +02:00
Kjell Tore Guttormsen
a6e2c939ef docs(llm-security): add critical review 2026-04-20 (v7.0.0 adversarial audit)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 23:27:52 +02:00
Kjell Tore Guttormsen
0333cf1d5b docs(config-audit): straggler sweep — 7 → 8 quality areas in agent + command config-audit-v4.0.0
feature-gap-agent and /posture command both reference quality area count.
Update both to reflect Token Efficiency as the 8th area.

Tests: 543 passing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:58:50 +02:00
Kjell Tore Guttormsen
73dc42f257 docs: bump config-audit to v4.0.0 in marketplace README
Reflect 9 scanners, 17 commands, 543+ tests, new TOK scanner, and
/config-audit tokens command.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:57:56 +02:00
Kjell Tore Guttormsen
d2c4084ff5 docs(config-audit): update CLAUDE.md and README test count for v4.0.0
- scanner-agent + verifier-agent rows: haiku → sonnet
- Add /config-audit tokens row to commands
- Add token-hotspots.mjs row to scanners
- Add token-hotspots-cli.mjs row to action engines
- Add opus-4.7-patterns.md to knowledge base table
- Test count: 486 → 543 across 31 files (real run pass count)
- README test badge: 498+ → 543+

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:57:31 +02:00
Kjell Tore Guttormsen
1f4bbd3b52 docs(config-audit): update README for v4.0.0
- Version badge 3.1.0 → 4.0.0, scanners 8 → 9, commands 16 → 17
- New /config-audit tokens row in commands table
- TOK scanner row in deterministic scanners table
- Token Hotspots CLI in CLI tools list
- scanner-agent + verifier-agent rows updated to Sonnet
- Orchestration ASCII diagram updated (haiku → sonnet)
- v4.0.0 entry added to version history

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:55:26 +02:00
Kjell Tore Guttormsen
b9269f853d feat(graceful-handoff): initial plugin with /graceful-handoff command
New plugin that produces a complete session handoff in under 60s:
NEXT-SESSION artifact, commit+push, and copy-paste prompt for next
session. Built for context-constrained models like Opus 4.7 where
sessions fill fast.

- Single declarative command, no hooks/agents/skills
- Detects handoff type: multi-session / plugin-work / single-task
- Default filename NEXT-SESSION-PROMPT.local.md; slug-override
- Flags: --no-commit, --dry-run
- Auto-generated Conventional Commits message from git diff --stat
- Respects pre-commit hooks (secrets, pathguard) — never bypasses

Also: add *.local.md to root .gitignore (existing NEXT-SESSION files
were untracked but not ignored) and list plugin in marketplace
README + CLAUDE.md per docs-convention.
2026-04-19 22:54:10 +02:00
Kjell Tore Guttormsen
f2080adf35 docs(config-audit): add v4.0.0 CHANGELOG entry
Document the Opus 4.7 era upgrade: TOK scanner, /config-audit tokens,
Token Efficiency 8th area, scanner/verifier agent migration to sonnet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:53:30 +02:00
Kjell Tore Guttormsen
e7bd0eba1b chore(config-audit): bump version to 4.0.0
v4.0.0 = MAJOR (Opus 4.7 era):
- New TOK scanner (Token Hotspots, 4 patterns)
- New /config-audit tokens command surface
- 8th quality area: Token Efficiency
- scanner-agent + verifier-agent migrated haiku → sonnet

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:52:28 +02:00
Kjell Tore Guttormsen
52d16d8711 docs(config-audit): refresh knowledge/ from Topic 2 Claude Code changelog research
Add 2026-04 deltas (v2.1.83-v2.1.111) verified against
research/03-claude-code-changes-config-surfaces.md (2026-04-19):
- Opus 4.7 + token-efficiency surfaces (env vars, attribution.commit/pr)
- Sandbox isolation (sandbox.* keys)
- Managed-only enterprise lockdown flags
- disableSkillShellExecution (v2.1.91)
- forceRemoteSettingsRefresh (v2.1.92)

No new hook events in this range — noted in hook-events-reference.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:52:11 +02:00
Kjell Tore Guttormsen
8d8e833028 refactor(config-audit): migrate scanner-agent + verifier-agent from haiku to Sonnet 4.6 2026-04-19 22:48:18 +02:00
Kjell Tore Guttormsen
97a1585dbc feat(config-audit): add /config-audit tokens command (ranked hotspots + recommendations) 2026-04-19 22:47:16 +02:00
Kjell Tore Guttormsen
cbc889f603 feat(config-audit): add token-hotspots CLI (node scanners/token-hotspots-cli.mjs) 2026-04-19 22:46:25 +02:00
Kjell Tore Guttormsen
295a6289b4 test(config-audit): extend grade-stability test to assert Token Efficiency A/B on baseline 2026-04-19 22:45:34 +02:00
Kjell Tore Guttormsen
4b385bf456 feat(config-audit): wire TOK into posture scorecard as 8th quality area (Token Efficiency) 2026-04-19 22:45:12 +02:00