claudemd-cli 0.17.2 → 0.17.4

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.
Files changed (2) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,145 @@ All notable changes to the `claudemd` plugin. This changelog tracks plugin artif
8
8
  - **Canonical spec version source**: `spec/CLAUDE.md` top-line title (`# AI-CODING-SPEC vX.Y.Z — Core`) + `spec/CLAUDE-changelog.md` top `##` entry.
9
9
  - **Plugin semver vs spec semver** are independent: plugin patch (0.2.0 → 0.2.1) may ship when spec is unchanged (this release); plugin minor (0.1.9 → 0.2.0) ships when spec minor updates (v0.2.0 shipped spec v6.10.0).
10
10
 
11
+ ## [0.17.4] - 2026-05-14
12
+
13
+ **Patch — fix: `banned-vocab-check.sh` + `ship-baseline-check.sh` trigger filter false-positives on `git commit` / `git push` substrings inside shell comments and heredoc bodies. Ports the v0.9.28 `memory-read-check.sh` segment-anchor regex (CMD flatten + `^|[[:space:]]*[;&|]+[[:space:]]*` separator) to both hooks. Closes the last two raw-`$CMD`-grep sites identified in the v0.17.3 sister-pattern sweep.**
14
+
15
+ ### Background
16
+
17
+ After v0.17.3 closed the multi-line CMD §8 bypass in `pre-bash-safety-check.sh`, a sister-pattern grep across all hooks revealed two remaining raw-`$CMD` trigger sites:
18
+
19
+ ```
20
+ hook → reads CMD → has sanitize → has flatten
21
+ pre-bash-safety-check.sh ✓ ✓ ✓ (v0.17.1 + v0.17.3)
22
+ memory-read-check.sh ✓ ✓ ✓ (v0.9.28 + v0.17.1)
23
+ banned-vocab-check.sh ✓ ✗ ✗ ← Bug 16
24
+ ship-baseline-check.sh ✓ ✗ ✗ ← Bug 15
25
+ ```
26
+
27
+ Both used the loose prefix `(^|[[:space:];&|])` — which accepts ANY whitespace as a separator. That lets a space after `#` (comment) or a space inside a heredoc body line satisfy the prefix, so:
28
+
29
+ - `# git commit -m "significantly faster"` (comment) → banned-vocab fires, message-extract finds `-m "..."`, hook denies a non-existent commit.
30
+ - `cat <<EOF\ngit push origin main\nEOF` (heredoc body) → ship-baseline fires when CI is red, denying a `cat` command that doesn't push anything.
31
+
32
+ `memory-read-check.sh` already solved this in v0.9.28 by flattening CMD to a single line and tightening the prefix to `(^|[[:space:]]*[;&|]+[[:space:]]*)` — real shell separator only. This release ports that fix verbatim to the two remaining hooks.
33
+
34
+ ### What changed
35
+
36
+ - `[fix MED]` **`hooks/banned-vocab-check.sh`** — trigger filter now flattens `$CMD` with `tr '\n' ' '` and uses the segment-anchor `TRIGGER_RE='(^|[[:space:]]*[;&|]+[[:space:]]*)git([[:space:]]+-c[[:space:]]+[^[:space:]]+)*[[:space:]]+commit([[:space:]]|$)'`. Message extraction below the trigger gate is unchanged — its `-m "..."` regex was already quote-aware and only ran AFTER trigger fired, so the upstream tightening alone is enough. `tests/hooks/banned-vocab.test.sh` 20 → 24 (+3 FP-anchor + 1 non-regression on chained `make && git commit`).
37
+
38
+ - `[fix LOW]` **`hooks/ship-baseline-check.sh`** — same shape: `CMD_FLAT=$(printf '%s' "$CMD" | tr '\n' ' ')` + segment-anchor `TRIGGER_RE='(^|[[:space:]]*[;&|]+[[:space:]]*)git[[:space:]]+push([[:space:]]|$)'`. The `--help` short-circuit on line 29 also reads `CMD_FLAT` for consistency. `tests/hooks/ship-baseline.test.sh` 11 → 15 (+3 FP-anchor + 1 non-regression on chained `make && git push`).
39
+
40
+ ### Why patch
41
+
42
+ Both changes restore intended hook behavior — comments and heredoc bodies are not shell-executable contexts; the spec rules (§10-V banned-vocab on real commits, §7 ship-baseline on real pushes) were never supposed to fire on them. `memory-read-check.sh` already shipped this design; the sister hooks were inconsistent. CHANGELOG `fix:` not `change:` — no new behavior, just tighter scoping of an existing rule.
43
+
44
+ Severity:
45
+
46
+ - **Bug 16 (banned-vocab) — MED FP**: blocks legitimate commits whenever a previous bash command on the same Claude tool call contained a `# git commit -m "..."` example in a comment or a `cat <<EOF ... git commit ... EOF` shell snippet in a heredoc. Users would see deny on the very next real commit because the agent's running bash CMD included an example-as-text.
47
+
48
+ - **Bug 15 (ship-baseline) — LOW FP**: only surfaces when CI is currently red; the hook then denies the `cat`/`echo`/`ls` command for containing a `git push` substring. No security impact (FP makes hook overly strict, never overly permissive).
49
+
50
+ ### Non-regression anchors
51
+
52
+ Each hook test gained an explicit case for chained-real shape (`make && git commit -m "..."` / `make && git push origin main`) that fires the trigger via `&&` separator. These lock that the tightened regex still matches real shell-separator chains, only rejecting whitespace-only prefixes.
53
+
54
+ ### Tests
55
+
56
+ - `bash tests/run-all.sh`: 411 node-test + 2 integration suites pass.
57
+ - `bash tests/hooks/banned-vocab.test.sh`: 24/24 (was 20).
58
+ - `bash tests/hooks/ship-baseline.test.sh`: 15/15 (was 11).
59
+ - `bash tests/hooks/pre-bash-safety.test.sh`: 76/76 (unchanged).
60
+ - `bash tests/hooks/memory-read-check.test.sh`: 29/29 (unchanged).
61
+ - Manual end-to-end: 8 FP probes (4 per hook: full-comment / inline-comment / heredoc body / quoted string) verified post-fix; 2 non-regression chained-real probes verified.
62
+
63
+ ### Operator notes
64
+
65
+ - Update path: plugin marketplace update + `/reload-plugins`. `${CLAUDE_PLUGIN_ROOT}` expansion means installed plugin picks up the new hook bodies automatically.
66
+ - Bypass tokens unchanged: `[allow-banned-vocab]` and `known-red baseline: <reason>` continue to work the same.
67
+ - If you'd intentionally been writing bash with `# git commit -m "..."` example comments and getting denied — this release is your fix; no `[allow-banned-vocab]` needed for non-commit contexts.
68
+
69
+ ### Sister-pattern sweep — final state
70
+
71
+ After this release, all four `*-check.sh` hooks that read `tool_input.command` and act on it (`pre-bash-safety`, `memory-read-check`, `banned-vocab-check`, `ship-baseline-check`) use the same defense-in-depth shape: (1) `tr '\n' ' '` flatten, (2) segment-anchor trigger regex, (3) sanitize (where applicable for tag/message extraction). No remaining raw-multi-line-`$CMD` grep sites in the hook fleet.
72
+
73
+ ## [0.17.3] - 2026-05-14
74
+
75
+ **Patch — fix: CRITICAL — `pre-bash-safety-check.sh` multi-line CMD §8 SAFETY bypass closure. Multi-line bash commands containing `rm -rf $UNSAFE_VAR` on any line other than the first silently passed the hook; the matching multi-line `npx pkg@PIN` case wrongly denied as unpinned. One root cause, two opposite-direction defects.**
76
+
77
+ ### Background
78
+
79
+ v0.17.2 shipped the bare-`rm -rf $HOME` whitelist closure (Bug 5 / Steam-disaster class). End-to-end dogfood Round 4 — running the *just-shipped* hook against realistic multi-line bash scripts — surfaced that the §8 SAFETY hook's pattern extraction was completely broken for any multi-line CMD where the rm/npx call wasn't on line 1. The fix landed in v0.17.2 reduced bare-`$HOME` to a denied case for *single-line* commands but the hook was systematically failing to fire on multi-line ones.
80
+
81
+ Minimal repro for the CRITICAL leg (pre-fix):
82
+
83
+ ```
84
+ CMD = "TMP=$(mktemp -d)\nrm -rf $UNSAFE_VAR"
85
+ → hook output: <empty> (no decision, allow)
86
+ ```
87
+
88
+ The single-line equivalent `rm -rf $UNSAFE_VAR` correctly denied. The multi-line form bypassed §8 SAFETY entirely — the exact spec rule the hook exists to enforce. An agent that issues a heredoc-style bash command with any setup line before the destructive call would slip through.
89
+
90
+ Reverse leg (false-DENY, also pre-fix):
91
+
92
+ ```
93
+ CMD = "TMP=$(mktemp -d)\nnpx prettier@3.0.0 --check ."
94
+ → deny: "npx unpinned package: TMP=$(mktemp"
95
+ ```
96
+
97
+ Pinned `prettier@3.0.0` flagged as `TMP=$(mktemp` unpinned. Innocent scripts denied; users would either disable the hook or wrap every npx call in a one-liner — eroding §8 trust.
98
+
99
+ ### Root cause
100
+
101
+ `pre-bash-safety-check.sh` extracted rm-target and npx-package via per-line sed:
102
+
103
+ ```bash
104
+ rm_tail=$(echo "$SANITIZED_CMD" | sed -E "s/.*${RM_FLAG_REGEX}//" | head -n1)
105
+ npx_tail=$(echo "$SANITIZED_CMD" | sed -E "s/.*${NPX_REGEX}//" | ...)
106
+ ```
107
+
108
+ `sed -E` processes line-by-line. Lines without `rm`/`npx` passed through unchanged; only the rm/npx line had its prefix stripped. Downstream:
109
+
110
+ - `head -n1` (rm path): always returned line 1 regardless of where the rm sat. If rm was on line ≥2, line 1 had no rm content → `rm_target` empty → deny path never fired. **§8 SAFETY bypass.**
111
+ - `for tok in $npx_tail` (npx path): bash word-splitting iterated tokens from ALL lines. Line 1's first token (typically `TMP=$(mktemp`) became `pkg_token` → flagged as unpinned. **False deny on innocent scripts.**
112
+
113
+ Sanitize already stripped heredoc bodies / line comments / quoted bodies, so the remaining newlines were between *independent command lines* — safe to flatten.
114
+
115
+ ### What changed
116
+
117
+ - `[fix CRITICAL]` **`hooks/pre-bash-safety-check.sh`** — new `SANITIZED_CMD_FLAT=$(printf '%s' "$SANITIZED_CMD" | tr '\n' ' ')` computed once after sanitize. Both extraction passes (`rm_tail` and `npx_tail`) now read `SANITIZED_CMD_FLAT` instead of `SANITIZED_CMD`. The `head -n1` on the rm extraction is removed (no longer needed; whole flat string contains all targets after the sed strip).
118
+
119
+ - `[test]` **`tests/fixtures/bash-safety/corpus.tsv`** +7 multi-line cases (`__NL__` LF marker per corpus convention):
120
+ - `deny`: multi-line `rm -rf $UNSAFE` (was false-ALLOW — the CRITICAL leg).
121
+ - `deny`: multi-line bare `rm -rf $HOME` on line 2 (v0.17.2 Bug 5 cross-check — confirms the prior fix is intact when rm is on a later line).
122
+ - `pass`: multi-line `rm -rf $HOME/cache` on line 2 (whitelist subpath survives multi-line).
123
+ - `pass`: multi-line `npx prettier@3.0.0` on line 2 (was false-DENY).
124
+ - `pass`: multi-line `npx ./node_modules/.bin/foo` on line 2 (local path survives).
125
+ - `deny`: multi-line `npx prettier` unpinned on line 2 (real catch preserved).
126
+ - `deny`: `rm -rf $UNSAFE` on line 3 of 5 (cross-checks the head -n1 removal — earlier lines no longer hide a deeper rm).
127
+
128
+ Corpus 69 → 76. `bash tests/hooks/pre-bash-safety.test.sh` 76/76.
129
+
130
+ - `[fix LOW]` **`commands/claudemd-rules.md`** — `--verbose` was documented as if it were a script flag (`$ARGS --verbose`), but `scripts/hard-rules-audit.js` rejects it as `Unknown argument`. The command's intent was for `--verbose` to be an agent presentation directive (show full `rules` array in output), not a script flag. A user typing `/claudemd-rules --verbose` would have `CLAUDEMD_RULES_DAYS="--verbose"` set as env, then the script would crash with `--days requires a positive integer (got '--verbose')`. The md now documents a 4-row parsing table (`""` / `90` / `--verbose` / `90 --verbose` → env + agent output) that the LLM follows to split numeric and presentation tokens cleanly.
131
+
132
+ ### Why patch (not minor)
133
+
134
+ Restores documented §8 SAFETY behavior — the hook was supposed to deny `rm -rf $VAR without validating VAR` per spec §8 (immutable), but multi-line CMDs bypassed it. CHANGELOG `fix:` not `change:` or `feat:`. No new hook surface, no new flag, no new spec rule. The corpus addition is regression-anchor only.
135
+
136
+ Severity disclosure: the CRITICAL leg means any user on plugin v0.17.2 or earlier had a §8 SAFETY hole for multi-line bash CMDs that included `rm -rf $UNSAFE_VAR` on lines ≥2. We have no telemetry indicating active exploitation, but the spec-coverage gap is real — recommend immediate update.
137
+
138
+ ### Tests
139
+
140
+ - `bash tests/run-all.sh`: 411 node-test + 2 integration suites pass.
141
+ - `bash tests/hooks/pre-bash-safety.test.sh`: 76/76 (was 69/69 after v0.17.2; +7 multi-line cases this release).
142
+ - Manual end-to-end: 6-scenario rm/npx matrix (multi-line + single-line × bare/subpath/glob/pinned/local/unpinned) verified post-fix; v0.17.2's bare-`$HOME` denials still fire correctly when `$HOME` sits on line ≥2.
143
+
144
+ ### Operator notes
145
+
146
+ - Update path: plugin marketplace update + `/reload-plugins`. Installed plugin picks up the new hook body via `${CLAUDE_PLUGIN_ROOT}` expansion.
147
+ - **If you have v0.17.2 or earlier, agent-issued multi-line bash with `rm -rf $UNSAFE` was silently allowed**. Audit your `~/.claude/logs/claudemd.jsonl` for the absence of `§8-rm-rf-var` rows on sessions where you expect the hook should have fired.
148
+ - The per-cmd escape hatch `[allow-rm-rf-var]` / `[allow-npx-unpinned]` continues to work in multi-line CMDs (already operates on raw `$CMD`, not sanitized).
149
+
11
150
  ## [0.17.2] - 2026-05-14
12
151
 
13
152
  **Patch — fix: 6-bug end-to-end dogfood pass. §8 SAFETY `rm -rf $VAR` whitelist closure (closes bare-`$HOME` Steam-disaster class); `transcript-vocab-scan` multi-paragraph false-negative; CLI `lint` whitespace-in-positional misclassified as path; CLI `audit` silent-OK on non-JSONL files; manifest `spec_version` drift v6.11.12 → v6.11.16; `update.js` raw Node stack trace on bogus env value.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemd-cli",
3
- "version": "0.17.2",
3
+ "version": "0.17.4",
4
4
  "description": "Standalone CLI for §10-V banned-vocab + transcript scanning. Companion to the claudemd Claude Code plugin (github.com/sdsrss/claudemd) for use in git pre-commit hooks, GitHub Actions, and other agents.",
5
5
  "type": "module",
6
6
  "bin": {