@yawlabs/ctxlint 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,7 +32,7 @@ ctxlint is a linter purpose-built for this. It reads your context files, cross-r
32
32
  - **Token-aware** — shows how much context window your files consume and flags redundant content
33
33
  - **Every AI tool** — supports Claude Code, Cursor, Copilot, Windsurf, Gemini, Cline, Aider, and 9 more
34
34
  - **Multiple outputs** — text, JSON, and SARIF (GitHub Code Scanning)
35
- - **MCP server** — 6 tools for IDE/agent integration with tool annotations for auto-approval
35
+ - **MCP server** — 7 tools for IDE/agent integration with tool annotations for auto-approval
36
36
  - **Watch mode** — `--watch` re-lints automatically when context files change
37
37
 
38
38
  ## Install
@@ -82,6 +82,7 @@ Useful if you want `ctxlint` available in every project without per-project setu
82
82
  | **Frontmatter** | Invalid or missing YAML frontmatter in Cursor .mdc, Copilot instructions, and Windsurf rules |
83
83
  | **CI coverage** | Release/deploy workflows in `.github/workflows/` not documented in any context file |
84
84
  | **CI secrets** | Secrets used in CI workflows (`${{ secrets.X }}`) not mentioned in context files |
85
+ | **Inline secrets** | Real secrets pasted into context files — private-key headers, AWS/GitHub/Anthropic/OpenAI/npm/Slack/Google/Stripe token shapes. Messages never echo the secret (6-char redacted prefix only) |
85
86
  | **Dead hooks** | PreToolUse hooks / permissions entries in `.claude/settings.json` pointing at scripts that no longer exist (a dead gate silently no-ops). Scans project `.claude/settings.json[.local]` by default; pass `--hooks-global` to also scan the user-global `~/.claude/settings.json` |
86
87
  | **Missing secrets** | GitHub secrets set on sibling repos but missing from current project |
87
88
  | **Diverged configs** | Canonical config files (CI, tsconfig, etc.) drifting across sibling projects |
@@ -89,6 +90,7 @@ Useful if you want `ctxlint` available in every project without per-project setu
89
90
  | **Stale memory** | Claude Code memory entries referencing paths that no longer exist |
90
91
  | **Duplicate memory** | Near-duplicate memories across projects (>60% content overlap) |
91
92
  | **Loop detection** | Agent stuck in loops — repeated commands or cyclic patterns in session history |
93
+ | **Memory overflow** | `MEMORY.md` past Claude Code's 200-line / 25KB session-load cap — entries beyond it are invisible to the agent |
92
94
 
93
95
  ## Supported Context Files
94
96
 
@@ -215,7 +217,7 @@ Session checks are **opt-in** because they access files outside the project dire
215
217
 
216
218
  ### Session Linting Specification
217
219
 
218
- - **[`AGENT_SESSION_LINT_SPEC.md`](./AGENT_SESSION_LINT_SPEC.md)** — 8 lint rules, the agent session data landscape across 8 agents, sibling detection strategy, and implementation guidance.
220
+ - **[`AGENT_SESSION_LINT_SPEC.md`](./AGENT_SESSION_LINT_SPEC.md)** — the full lint-rule set (rule count in the [Specifications](#specifications) family table), the agent session data landscape across 8 agents, sibling detection strategy, and implementation guidance.
219
221
  - **[`agent-session-lint-rules.json`](./agent-session-lint-rules.json)** — Machine-readable rule catalog.
220
222
 
221
223
  ## Example Output
@@ -269,6 +271,10 @@ Options:
269
271
  --mcp-global Also scan user/global MCP config files (implies --mcp)
270
272
  --session Enable session audit checks (cross-project consistency)
271
273
  --session-only Run only session checks, skip context and MCP checks
274
+ --skills Run agent-skill checks (~/.claude/skills + ~/.claude/agents)
275
+ --skills-only Run only agent-skill checks, skip everything else
276
+ --hooks-global Also scan the user-global ~/.claude/settings.json in the
277
+ dead-hook check (default scans project .claude/ only)
272
278
  --mcp-server Start the MCP server (alias: `serve` subcommand)
273
279
  --watch Re-lint on context file changes
274
280
  -V, --version Output the version number
@@ -278,9 +284,9 @@ Commands:
278
284
  init Set up a git pre-commit hook
279
285
  ```
280
286
 
281
- **Available checks:** `paths`, `commands`, `staleness`, `tokens`, `tier-tokens`, `redundancy`, `contradictions`, `frontmatter`, `ci-coverage`, `ci-secrets`, `mcp-schema`, `mcp-security`, `mcp-commands`, `mcp-deprecated`, `mcp-env`, `mcp-urls`, `mcp-consistency`, `mcp-redundancy`, `session-missing-secret`, `session-diverged-file`, `session-missing-workflow`, `session-stale-memory`, `session-duplicate-memory`, `session-loop-detection`, `session-memory-index-overflow`
287
+ **Available checks:** `paths`, `commands`, `staleness`, `tokens`, `tier-tokens`, `redundancy`, `contradictions`, `frontmatter`, `ci-coverage`, `ci-secrets`, `content-secrets`, `hook-coverage`, `mcp-schema`, `mcp-security`, `mcp-commands`, `mcp-deprecated`, `mcp-env`, `mcp-urls`, `mcp-consistency`, `mcp-redundancy`, `session-missing-secret`, `session-diverged-file`, `session-missing-workflow`, `session-stale-memory`, `session-duplicate-memory`, `session-loop-detection`, `session-memory-index-overflow`, `skill-frontmatter`, `skill-broken-ref`, `skill-trigger-collision`, `skill-orphaned`, `skill-dead-tool-restriction`
282
288
 
283
- Passing any `mcp-*` check name implies `--mcp`. Passing any `session-*` check name implies `--session`.
289
+ Passing any `mcp-*` check name implies `--mcp`. Passing any `session-*` check name implies `--session`. Passing any `skill-*` check name implies `--skills`.
284
290
 
285
291
  ## Watch Mode
286
292
 
@@ -392,7 +398,7 @@ The `contextFiles` array adds custom file patterns to scan alongside the built-i
392
398
 
393
399
  | Field | Type | Default | Meaning |
394
400
  | ------------------------------- | ---------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
395
- | `checks` | `string[]` | all checks | Checks to run. Check names include `paths`, `commands`, `tokens`, `tier-tokens`, `redundancy`, `contradictions`, `frontmatter`, `staleness`, `ci-coverage`, `ci-secrets`, plus any `mcp-*` / `session-*`. |
401
+ | `checks` | `string[]` | all checks | Checks to run. Check names include `paths`, `commands`, `tokens`, `tier-tokens`, `redundancy`, `contradictions`, `frontmatter`, `staleness`, `ci-coverage`, `ci-secrets`, `content-secrets`, `hook-coverage`, plus any `mcp-*` / `session-*` / `skill-*`. |
396
402
  | `ignore` | `string[]` | `[]` | Checks to skip, evaluated after `checks`. |
397
403
  | `strict` | `boolean` | `false` | Exit non-zero on any warning or error. |
398
404
  | `tokenThresholds` | `object` | see below | Per-file and cross-file token thresholds. |
@@ -408,6 +414,8 @@ The `contextFiles` array adds custom file patterns to scan alongside the built-i
408
414
  | `mcpGlobal` | `boolean` | `false` | Also scan user/global MCP configs (same as `--mcp-global`). |
409
415
  | `session` | `boolean` | `false` | Enable session audit checks (cross-project consistency); same as `--session`. |
410
416
  | `sessionOnly` | `boolean` | `false` | Run only session checks, skip context and MCP checks (same as `--session-only`). |
417
+ | `skills` | `boolean` | `false` | Enable agent-skill checks (`~/.claude/skills` + `~/.claude/agents`); same as `--skills`. |
418
+ | `skillsOnly` | `boolean` | `false` | Run only agent-skill checks, skip everything else (same as `--skills-only`). |
411
419
 
412
420
  Config file resolution order: `.ctxlintrc` → `.ctxlintrc.json` in the project root. Use `--config <path>` to point elsewhere. CLI flags override config fields.
413
421
 
@@ -415,7 +423,7 @@ CLI flags override config file settings. Use `--config <path>` to load a config
415
423
 
416
424
  ## Use as MCP Server
417
425
 
418
- ctxlint ships with an MCP server that exposes six tools (`ctxlint_audit`, `ctxlint_mcp_audit`, `ctxlint_session_audit`, `ctxlint_validate_path`, `ctxlint_token_report`, `ctxlint_fix`). All read-only tools declare annotations so MCP clients can skip confirmation dialogs.
426
+ ctxlint ships with an MCP server that exposes seven tools (`ctxlint_audit`, `ctxlint_mcp_audit`, `ctxlint_session_audit`, `ctxlint_skill_audit`, `ctxlint_validate_path`, `ctxlint_token_report`, `ctxlint_fix`). All read-only tools declare annotations so MCP clients can skip confirmation dialogs.
419
427
 
420
428
  Launch it with the `serve` subcommand (or the equivalent `--mcp-server` flag, kept for back-compat):
421
429
 
@@ -505,8 +513,8 @@ ctxlint is the reference implementation of four open specifications for linting
505
513
 
506
514
  | Spec | What it covers |
507
515
  | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
508
- | **[AI Context File Linting Spec](./CONTEXT_LINT_SPEC.md)** | 28 rules for validating context files (CLAUDE.md, .cursorrules, AGENTS.md, etc.) across 17 clients. Covers file formats, frontmatter schemas, path/command validation, staleness, token budgets, redundancy, and contradictions. |
509
- | **[MCP Config Linting Spec](./MCP_CONFIG_LINT_SPEC.md)** | 27 rules for validating MCP server configs (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, etc.) across 8 clients. Covers schema validation, hardcoded secrets, env var syntax, deprecated transports, and cross-file consistency. |
516
+ | **[AI Context File Linting Spec](./CONTEXT_LINT_SPEC.md)** | 39 rules for validating context files (CLAUDE.md, .cursorrules, AGENTS.md, etc.) across 16 clients. Covers file formats, frontmatter schemas, path/command validation, staleness, token budgets, redundancy, and contradictions. |
517
+ | **[MCP Config Linting Spec](./MCP_CONFIG_LINT_SPEC.md)** | 28 rules for validating MCP server configs (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, etc.) across 8 clients. Covers schema validation, hardcoded secrets, env var syntax, deprecated transports, and cross-file consistency. |
510
518
  | **[Agent Session Linting Spec](./AGENT_SESSION_LINT_SPEC.md)** | 8 rules for auditing agent session data (history, memory) across 8 agents. Covers cross-project secret consistency, config drift, stale memory, and loop detection. |
511
519
  | **[Agent Skill Linting Spec](./AGENT_SKILL_LINT_SPEC.md)** | 5 rules for auditing Claude Code skill (`SKILL.md`) and agent (`.md`) definitions under `~/.claude`. Covers frontmatter presence, broken refs, trigger-phrase collisions, orphaned skills, and dead tool restrictions. (v1, experimental) |
512
520
 
package/action.yml CHANGED
@@ -24,4 +24,14 @@ runs:
24
24
 
25
25
  - name: Run ctxlint
26
26
  shell: bash
27
- run: npx -y @yawlabs/ctxlint@${{ inputs.version }} ${{ inputs.args }}
27
+ # Inputs flow through env vars, never interpolated inline into the
28
+ # script body: expression interpolation pastes the raw input into the
29
+ # shell script before it runs, so a consumer workflow that wires
30
+ # attacker-influenceable data (issue title, branch name) into `with:`
31
+ # would hand that data command execution on their runner.
32
+ # CTXLINT_ARGS is expanded unquoted on purpose -- it carries multiple
33
+ # whitespace-separated CLI flags that must word-split.
34
+ env:
35
+ CTXLINT_VERSION: ${{ inputs.version }}
36
+ CTXLINT_ARGS: ${{ inputs.args }}
37
+ run: npx -y "@yawlabs/ctxlint@${CTXLINT_VERSION}" ${CTXLINT_ARGS}
@@ -69,7 +69,7 @@
69
69
  "category": "session",
70
70
  "severity": "info",
71
71
  "description": "Near-duplicate memory entries across projects.",
72
- "trigger": "Two memory files from different projects have >60% line overlap.",
72
+ "trigger": "Two memory files from different projects have >60% line overlap, and at least one of the pair belongs to the current project (otherwise every lint run from any repo would resurface the same unrelated cross-project duplicates).",
73
73
  "message": "\"{file1}\" and \"{file2}\" have {overlap}% content overlap",
74
74
  "fixable": false,
75
75
  "stability": "stable"
@@ -79,7 +79,7 @@
79
79
  "category": "session",
80
80
  "severity": "warning",
81
81
  "description": "Same command run 3+ times consecutively in agent history.",
82
- "trigger": "Agent history for the current project shows the same display string 3+ times in a row.",
82
+ "trigger": "Agent history for the current project (entries lacking a project path or timestamp are excluded), grouped into sessions (by provider + session ID, split at >30-minute timestamp gaps), shows the same display string 3+ times in a row within a single session segment. Single-command sessions are additionally merged per provider into one stream (split at the same gaps) so rapid one-shot respawn loops are caught.",
83
83
  "message": "Command run {count} times consecutively: \"{command}\"",
84
84
  "fixable": false,
85
85
  "stability": "experimental"
@@ -89,7 +89,7 @@
89
89
  "category": "session",
90
90
  "severity": "warning",
91
91
  "description": "Cyclic command pattern detected in agent history.",
92
- "trigger": "Agent history shows a repeating sequence of 2-3 commands (e.g. A,B,A,B) repeated 2+ times.",
92
+ "trigger": "Within a single session segment (same provider + session ID, split at >30-minute gaps; entries lacking a project path or timestamp are excluded), agent history shows a repeating sequence of 2-3 commands (e.g. A,B,A,B) repeated 2+ times. Spans already reported by session/consecutive-repeat are not re-reported. The merged one-shot stream is not scanned for cycles.",
93
93
  "message": "Cyclic pattern repeated {count} times: {cycle}",
94
94
  "fixable": false,
95
95
  "stability": "experimental"
@@ -19,7 +19,7 @@
19
19
  "category": "skill",
20
20
  "severity": "warning",
21
21
  "description": "A SKILL.md or agent .md file is missing required YAML frontmatter, or a required field within it. Skills and agents key off `name` + `description`; the description drives selection, so a missing one means the skill/agent is never invoked.",
22
- "trigger": "The file has no `---`-delimited frontmatter, has unclosed frontmatter, or is missing a required field (name, description). A missing file body altogether is reported as error; a missing single field as warning.",
22
+ "trigger": "The file has no `---`-delimited frontmatter, has unclosed frontmatter, or is missing a required field (name, description). Absent or unclosed frontmatter is reported as error; a missing single field as warning.",
23
23
  "message": "{file}: missing required frontmatter field \"{field}\"",
24
24
  "fixable": false,
25
25
  "stability": "experimental"
@@ -58,8 +58,8 @@
58
58
  "id": "skill/dead-tool-restriction",
59
59
  "category": "skill",
60
60
  "severity": "warning",
61
- "description": "An agent definition's tool restriction (tools / allowed-tools frontmatter) lists a tool name that is not a known Claude Code built-in tool. A dead restriction silently fails to grant/deny the intended tool. MCP-namespaced tools (mcp__server__tool) and wildcards are not validated (their existence depends on loaded MCP servers).",
62
- "trigger": "A non-MCP, non-wildcard entry in an agent's tools/allowed-tools list does not match a known built-in Claude Code tool name (Bash, Read, Edit, Glob, Grep, Write, Task, WebFetch, WebSearch, etc.).",
61
+ "description": "An agent definition's tool restriction (tools / allowed-tools frontmatter) lists a tool name that is not a known Claude Code built-in tool. A dead restriction silently fails to grant/deny the intended tool. MCP-namespaced tools (mcp__server__tool) and wildcards are not validated (their existence depends on loaded MCP servers). Severity is split by name shape: an unknown PascalCase name is reported as info (it may be a built-in newer than the linter's known-tool list, which drifts across Claude Code versions); anything else (lowercase, separators) doesn't match Claude Code's naming and is reported as warning (far more likely a typo).",
62
+ "trigger": "A non-MCP, non-wildcard entry in an agent's tools/allowed-tools list does not match a known built-in Claude Code tool name (Bash, Read, Edit, Glob, Grep, Write, Task, WebFetch, WebSearch, etc.). Unknown PascalCase names downgrade to info; other shapes keep the warning.",
63
63
  "message": "{file}: tool restriction lists \"{tool}\" which is not a known Claude Code tool",
64
64
  "fixable": false,
65
65
  "stability": "experimental"
@@ -70,6 +70,12 @@
70
70
  "name": "Hook Enforcement Coverage",
71
71
  "description": "Validates that PreToolUse hooks and permissions entries in .claude/settings.json reference scripts that exist on disk. The inverse of tier-tokens/hard-enforcement-missing.",
72
72
  "scope": "cross-file"
73
+ },
74
+ {
75
+ "id": "content-secrets",
76
+ "name": "Inline Secret Detection",
77
+ "description": "Detects inline-pasted secrets (API keys, tokens, private-key headers) in context files. Complements mcp-security, which covers MCP config files; this covers the context-file paste surface (CLAUDE.md, AGENTS.md, .cursorrules, etc.).",
78
+ "scope": "per-file"
73
79
  }
74
80
  ],
75
81
  "rules": [
@@ -154,6 +160,16 @@
154
160
  "fixable": false,
155
161
  "stability": "stable"
156
162
  },
163
+ {
164
+ "id": "commands/package-json-missing",
165
+ "category": "commands",
166
+ "severity": "info",
167
+ "description": "package.json is missing or unparseable, so all script/npx/tool command validation silently skips. Emitted once per context file that contains at least one command reference that would otherwise have been validated, so the skip isn't invisible.",
168
+ "trigger": "package.json cannot be loaded AND the context file references at least one npm-run/shorthand/npx/tool command.",
169
+ "message": "package.json missing or unparseable — command checks skipped",
170
+ "fixable": false,
171
+ "stability": "stable"
172
+ },
157
173
  {
158
174
  "id": "staleness/stale",
159
175
  "category": "staleness",
@@ -227,7 +243,7 @@
227
243
  "category": "tier-tokens",
228
244
  "severity": "info",
229
245
  "description": "Reports the heaviest top-level sections in an always-loaded context file and suggests demoting the largest to an on-demand tier (skill, subagent, or memory).",
230
- "trigger": "An always-loaded context file (CLAUDE.md, AGENTS.md, .cursorrules, etc. — excluding path-scoped rules files) exceeds the tierBreakdown threshold (default 1000 tokens) and contains H1 or H2 sections.",
246
+ "trigger": "An always-loaded context file (CLAUDE.md, AGENTS.md, .cursorrules, etc. — excluding path-scoped rules files) reaches or exceeds the tierBreakdown threshold (default 1000 tokens; the boundary is inclusive) and contains H1 or H2 sections.",
231
247
  "message": "{N} tokens loaded every session — heaviest top-level section(s): ...",
232
248
  "fixable": false,
233
249
  "configurable": true,
@@ -240,7 +256,7 @@
240
256
  "category": "tier-tokens",
241
257
  "severity": "warning",
242
258
  "description": "Combined token cost of always-loaded context files exceeds the recommended budget.",
243
- "trigger": "Two or more always-loaded files total more than the tierAggregate threshold (default 4000 tokens).",
259
+ "trigger": "Two or more always-loaded files total the tierAggregate threshold (default 4000 tokens) or more; the boundary is inclusive.",
244
260
  "message": "{count} always-loaded files total {N} tokens — loaded every session",
245
261
  "fixable": false,
246
262
  "configurable": true,
@@ -252,8 +268,8 @@
252
268
  "id": "tier-tokens/hard-enforcement-missing",
253
269
  "category": "tier-tokens",
254
270
  "severity": "info",
255
- "description": "An always-loaded file uses inviolable framing (NEVER / ALWAYS / DO NOT / MUST NOT) paired with a concrete command, but no corresponding PreToolUse hook or permissions.deny entry exists in settings.json. Rules in always-loaded files are advisory — without a hook the agent may still run the command.",
256
- "trigger": "Line in an always-loaded file matches NEVER|ALWAYS|DO NOT|MUST NOT with a backticked command, and .claude/settings.json / ~/.claude/settings.json does not contain a matching PreToolUse hook or permissions.deny entry.",
271
+ "description": "An always-loaded file uses inviolable framing (NEVER / ALWAYS / DO NOT / MUST NOT) paired with a concrete command, but no corresponding PreToolUse or Stop hook, permissions.deny entry, or permissions.ask entry exists in settings.json. Rules in always-loaded files are advisory — without a hook the agent may still run the command.",
272
+ "trigger": "Line in an always-loaded file matches NEVER|ALWAYS|DO NOT|MUST NOT with a backticked command, and the project's .claude/settings.json / .claude/settings.local.json does not contain a matching PreToolUse or Stop hook, permissions.deny entry, or permissions.ask entry (ask gates the command behind a human prompt, so it counts as enforcement). The user-global ~/.claude/settings.json is consulted only on explicit opt-in (the same gate as hook-coverage's --hooks-global).",
257
273
  "message": "Inviolable framing without a hook to back it up",
258
274
  "fixable": false,
259
275
  "source": "code.claude.com/docs/en/hooks-guide",
@@ -313,7 +329,7 @@
313
329
  "id": "frontmatter/missing",
314
330
  "category": "frontmatter",
315
331
  "severity": "warning",
316
- "description": "File format requires frontmatter but none is present.",
332
+ "description": "File format requires or recommends frontmatter but none is present. Severity is per-format: warning for Cursor .mdc (frontmatter is required), info for Copilot instructions and Windsurf rules (frontmatter is optional/recommended).",
317
333
  "trigger": "Cursor .mdc, Copilot instruction, or Windsurf rule file has no --- frontmatter block.",
318
334
  "message": "{format} file is missing frontmatter",
319
335
  "fixable": false,
@@ -322,6 +338,11 @@
322
338
  "copilot-instructions",
323
339
  "windsurf-rules"
324
340
  ],
341
+ "severityByFormat": {
342
+ "cursor-mdc": "warning",
343
+ "copilot-instructions": "info",
344
+ "windsurf-rules": "info"
345
+ },
325
346
  "stability": "stable"
326
347
  },
327
348
  {
@@ -343,8 +364,8 @@
343
364
  "id": "frontmatter/invalid-value",
344
365
  "category": "frontmatter",
345
366
  "severity": "error",
346
- "description": "A frontmatter field has an invalid value.",
347
- "trigger": "alwaysApply is not true/false, trigger is not a valid enum value, globs doesn't look like a pattern.",
367
+ "description": "A frontmatter field has an invalid value. The error severity applies to the unambiguous branches (alwaysApply not a boolean, Windsurf trigger outside the enum); a possibly-malformed globs value (unbalanced brackets or quotes) is reported at warning severity because Cursor accepts bare directory/extension globs.",
368
+ "trigger": "alwaysApply is not true/false (error), trigger is not a valid enum value (error), or globs has unbalanced brackets/quotes (warning).",
348
369
  "message": "Invalid {field} value: \"{value}\"",
349
370
  "fixable": false,
350
371
  "appliesTo": [
@@ -353,6 +374,21 @@
353
374
  ],
354
375
  "stability": "stable"
355
376
  },
377
+ {
378
+ "id": "frontmatter/unclosed",
379
+ "category": "frontmatter",
380
+ "severity": "error",
381
+ "description": "Frontmatter opens with --- but is never closed, so the host loads the file with no frontmatter at all and every parsed field is suspect. Reported instead of frontmatter/missing because the user clearly tried to write frontmatter.",
382
+ "trigger": "File opens with a --- line but no matching closing --- line follows (typical cause: the closing fence typo'd as -- or ___, or indented).",
383
+ "message": "Frontmatter opens with `---` but is never closed",
384
+ "fixable": false,
385
+ "appliesTo": [
386
+ "cursor-mdc",
387
+ "copilot-instructions",
388
+ "windsurf-rules"
389
+ ],
390
+ "stability": "stable"
391
+ },
356
392
  {
357
393
  "id": "frontmatter/no-activation",
358
394
  "category": "frontmatter",
@@ -391,10 +427,100 @@
391
427
  "category": "hook-coverage",
392
428
  "severity": "warning",
393
429
  "description": "A PreToolUse (or other) hook command, or a permissions entry, in .claude/settings.json references a script/path that does not exist on disk. The gate silently no-ops -- Claude Code cannot run a missing script, so the protection the user believes is in place does nothing.",
394
- "trigger": "A hooks.<Event>[].hooks[].command or permissions.{allow,deny,ask}[] entry contains a path-shaped token (resolved through ~, $CLAUDE_PROJECT_DIR, $CLAUDE_CONFIG_DIR, $HOME) that does not exist on disk. Entries with unresolvable env vars are skipped (cannot be verified). Scans project .claude/settings.json + settings.local.json and user ~/.claude/settings.json.",
430
+ "trigger": "A hooks.<Event>[].hooks[].command or permissions.{allow,deny,ask}[] entry contains a path-shaped token (resolved through ~, $CLAUDE_PROJECT_DIR, $CLAUDE_CONFIG_DIR, $HOME) that does not exist on disk. Entries with unresolvable env vars are skipped (cannot be verified). Scans project .claude/settings.json + settings.local.json; the user-global ~/.claude/settings.json is scanned only on explicit opt-in (--hooks-global).",
395
431
  "message": "{origin} references \"{path}\" which does not exist on disk -- the gate silently no-ops",
396
432
  "fixable": false,
397
433
  "stability": "experimental"
434
+ },
435
+ {
436
+ "id": "content-secrets/private-key-header",
437
+ "category": "content-secrets",
438
+ "severity": "error",
439
+ "description": "A PEM private-key header is pasted into a context file. Context files usually end up committed to git, so an inline private key is a leak.",
440
+ "trigger": "A line contains -----BEGIN [RSA |EC |DSA |OPENSSH |PGP ]PRIVATE KEY-----. Lines containing placeholder tokens (example, redacted, xxxx, ...), commented fake/example lines, and explicitly illustrative code fences (text, txt, example, pseudocode, none) are suppressed.",
441
+ "message": "Private key header detected in {file}",
442
+ "fixable": false,
443
+ "stability": "experimental"
444
+ },
445
+ {
446
+ "id": "content-secrets/aws-access-key",
447
+ "category": "content-secrets",
448
+ "severity": "error",
449
+ "description": "An AWS access key ID is pasted into a context file. Emitted messages never include the full secret — only a 6-char redacted prefix.",
450
+ "trigger": "AKIA (long-lived) or ASIA (STS temporary) prefix followed by 16 uppercase alphanumeric characters, outside placeholder/illustrative contexts.",
451
+ "message": "AWS access key detected in {file} ({redacted-prefix}...)",
452
+ "fixable": false,
453
+ "stability": "experimental"
454
+ },
455
+ {
456
+ "id": "content-secrets/github-pat",
457
+ "category": "content-secrets",
458
+ "severity": "error",
459
+ "description": "A GitHub token is pasted into a context file. Covers classic PATs (ghp_), fine-grained PATs (github_pat_), and server-to-server / OAuth / user / refresh tokens (ghs_ / gho_ / ghu_ / ghr_); the non-PAT variants are emitted with the label \"GitHub token\".",
460
+ "trigger": "ghp_{36,}, github_pat_{82,}, or gh[soru]_{36,} token shapes, outside placeholder/illustrative contexts.",
461
+ "message": "GitHub personal access token detected in {file} ({redacted-prefix}...)",
462
+ "fixable": false,
463
+ "stability": "experimental"
464
+ },
465
+ {
466
+ "id": "content-secrets/anthropic-key",
467
+ "category": "content-secrets",
468
+ "severity": "error",
469
+ "description": "An Anthropic API key is pasted into a context file.",
470
+ "trigger": "sk-ant- prefix followed by 20+ key characters, outside placeholder/illustrative contexts. Checked before the OpenAI sk- pattern so the same substring isn't flagged twice.",
471
+ "message": "Anthropic API key detected in {file} ({redacted-prefix}...)",
472
+ "fixable": false,
473
+ "stability": "experimental"
474
+ },
475
+ {
476
+ "id": "content-secrets/openai-key",
477
+ "category": "content-secrets",
478
+ "severity": "error",
479
+ "description": "An OpenAI API key is pasted into a context file.",
480
+ "trigger": "sk- or sk-proj- prefix followed by 20+ key characters (the length floor avoids flagging sk-... ellipses in docs), outside placeholder/illustrative contexts.",
481
+ "message": "OpenAI API key detected in {file} ({redacted-prefix}...)",
482
+ "fixable": false,
483
+ "stability": "experimental"
484
+ },
485
+ {
486
+ "id": "content-secrets/npm-token",
487
+ "category": "content-secrets",
488
+ "severity": "error",
489
+ "description": "An npm automation token is pasted into a context file.",
490
+ "trigger": "npm_ prefix followed by 36+ alphanumeric characters, outside placeholder/illustrative contexts.",
491
+ "message": "npm token detected in {file} ({redacted-prefix}...)",
492
+ "fixable": false,
493
+ "stability": "experimental"
494
+ },
495
+ {
496
+ "id": "content-secrets/slack-token",
497
+ "category": "content-secrets",
498
+ "severity": "error",
499
+ "description": "A Slack token is pasted into a context file (bot, user, app, admin, or refresh token).",
500
+ "trigger": "xox[bpoasr]- prefix followed by 10+ token characters, outside placeholder/illustrative contexts.",
501
+ "message": "Slack token detected in {file} ({redacted-prefix}...)",
502
+ "fixable": false,
503
+ "stability": "experimental"
504
+ },
505
+ {
506
+ "id": "content-secrets/google-api-key",
507
+ "category": "content-secrets",
508
+ "severity": "error",
509
+ "description": "A Google API key is pasted into a context file.",
510
+ "trigger": "AIza prefix followed by exactly 35 key characters, outside placeholder/illustrative contexts.",
511
+ "message": "Google API key detected in {file} ({redacted-prefix}...)",
512
+ "fixable": false,
513
+ "stability": "experimental"
514
+ },
515
+ {
516
+ "id": "content-secrets/stripe-secret",
517
+ "category": "content-secrets",
518
+ "severity": "error",
519
+ "description": "A Stripe live secret key is pasted into a context file.",
520
+ "trigger": "sk_live_ prefix followed by 24+ alphanumeric characters, outside placeholder/illustrative contexts.",
521
+ "message": "Stripe live secret key detected in {file} ({redacted-prefix}...)",
522
+ "fixable": false,
523
+ "stability": "experimental"
398
524
  }
399
525
  ],
400
526
  "formats": [
@@ -484,7 +610,8 @@
484
610
  "always_on",
485
611
  "glob",
486
612
  "manual",
487
- "model"
613
+ "model",
614
+ "model_decision"
488
615
  ]
489
616
  }
490
617
  },