claude-code-cache-fix 3.5.1 → 3.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-cache-fix",
3
- "version": "3.5.1",
3
+ "version": "3.5.2",
4
4
  "description": "Cache optimization proxy and interceptor for Claude Code. Fixes prompt cache bugs, stabilizes prefix, reduces quota burn.",
5
5
  "type": "module",
6
6
  "exports": "./preload.mjs",
@@ -9,11 +9,24 @@
9
9
  # CC pipes hook input as JSON on stdin including `session_id`, which we map to
10
10
  # the per-session filename via the canonical rule (matches the writer in
11
11
  # proxy/extensions/cache-telemetry.mjs:sessionFilename).
12
-
13
- input=$(cat)
12
+ #
13
+ # Security (v3.5.2, #108): the previous version interpolated stdin into a
14
+ # Python triple-quoted literal via "''$input''", which lets a `'''` byte
15
+ # sequence in the payload close the literal early and execute arbitrary
16
+ # Python. CC's hook payload reflects user-controlled paths (cwd,
17
+ # workspace.current_dir, transcript_path), and apostrophes are legal in
18
+ # filenames, so a hostile directory name on disk could trigger code
19
+ # execution on every CC statusline redraw. We capture stdin in bash, export
20
+ # it to the env, and pass the python source through a single-quoted heredoc
21
+ # (`<<'PYEOF'`) which disables ALL bash interpolation in the body. Python
22
+ # reads the JSON via os.environ.get('CC_INPUT'), where the bytes are inert.
23
+
24
+ # Capture stdin in bash before the python heredoc consumes the stdin slot,
25
+ # then export so the python child sees it.
26
+ CC_INPUT=$(cat)
27
+ export CC_INPUT
14
28
 
15
29
  ACCOUNT="$HOME/.claude/quota-status/account.json"
16
- SESSIONS_DIR="$HOME/.claude/quota-status/sessions"
17
30
 
18
31
  # Show quota even if no per-session file exists yet (fresh session, first
19
32
  # request hasn't fired). Per-session block just gets blank.
@@ -21,7 +34,12 @@ if [ ! -f "$ACCOUNT" ]; then
21
34
  exit 0
22
35
  fi
23
36
 
24
- result=$(python3 -c "
37
+ # IMPORTANT: the heredoc tag is single-quoted (`<<'PYEOF'`). This disables
38
+ # all bash interpolation inside the heredoc body. Do NOT change to `<<PYEOF`
39
+ # without a matching audit — that would re-introduce the injection vector
40
+ # the v3.5.2 hotfix closed. The python source must reference CC_INPUT only
41
+ # through os.environ, never via a shell-substituted string.
42
+ result=$(python3 <<'PYEOF' 2>/dev/null
25
43
  import sys, json, os, re, hashlib
26
44
  from datetime import datetime, timezone, timedelta
27
45
 
@@ -34,14 +52,14 @@ sessions_dir = os.path.join(home, '.claude', 'quota-status', 'sessions')
34
52
  # canonical rule decides — the writer maps all those to 'unknown',
35
53
  # the reader must do the same to keep the contract identical.
36
54
  try:
37
- stdin_data = json.loads('''$input''') if '''$input''' else {}
55
+ stdin_data = json.loads(os.environ.get('CC_INPUT') or '{}')
38
56
  except Exception:
39
57
  stdin_data = {}
40
58
  sess_id_raw = stdin_data.get('session_id')
41
59
 
42
60
  # Canonical filename derivation — must match cache-telemetry.mjs:sessionFilename.
43
61
  # Allowlist: [A-Za-z0-9_-]{1,128}; else inv-<sha256(s)[:16]>; null/empty/whitespace -> 'unknown'.
44
- SAFE = re.compile(r'^[A-Za-z0-9_-]{1,128}\$')
62
+ SAFE = re.compile(r'^[A-Za-z0-9_-]{1,128}$')
45
63
  def session_filename(raw):
46
64
  if raw is None:
47
65
  return 'unknown'
@@ -121,6 +139,7 @@ if peak:
121
139
  label += ' | \033[33mPEAK\033[0m'
122
140
 
123
141
  print(label)
124
- " 2>/dev/null)
142
+ PYEOF
143
+ )
125
144
 
126
145
  [ -n "$result" ] && echo "$result"