v1.6.0 — Hardening release
11 community PRs merged plus a hardened security model for the flag file. This release fixes two real crash bugs that were silently breaking installs, two local-file-clobber vulnerabilities, and several portability gaps.
Critical fixes
-
Hooks no longer crash when an ancestor
package.jsondeclares"type": "module". Before this fix, any user with~/.claude/package.jsonset to ESM (common with several Claude Code plugins) hitReferenceError: require is not defined in ES module scopeon every session. SessionStart and UserPromptSubmit hooks both silently failed, the flag file was never written, and the statusline badge disappeared. Fixed by shippinghooks/package.jsonwith{"type": "commonjs"}so the hooks pin themselves to CJS regardless of ancestor settings. (Thanks @malakhov-dmitrii — #174) -
${CLAUDE_PLUGIN_ROOT}is now quoted inplugin.json. Plugin install paths containing spaces (e.g.~/Library/Application Support/...on macOS,C:\Users\Some Name\...on Windows) used to break thenodeinvocation entirely. (Thanks @MukundaKatta — #171) -
Codex hook config shape updated to the current spec. The old shape silently no-op'd on Codex with hooks enabled. New shape uses the
matcher+ nestedhooksarray. Repo also now ships.codex/config.tomlwithcodex_hooks = trueso auto-activation actually works inside this repo on macOS/Linux. (Thanks @davidbits — #148)
Security fixes
-
safeWriteFlag()helper hardens all flag file writes against symlink attacks. The flag file at~/.claude/.caveman-activeis at a predictable path. Before this fix, a local attacker (or any sibling process running as the same user but with a smaller blast radius — sandboxed extension, container with a~/.claudebind mount, etc.) could replace the flag with a symlink to any user-writable file (~/.ssh/authorized_keys,~/.bashrc) and the next hook write would clobber the symlink target. The new helper, defined once inhooks/caveman-config.jsand used by every write site:- Refuses if the flag's parent directory is itself a symlink
- Refuses if the existing flag target is a symlink
- Opens with
O_NOFOLLOWwhere supported (Linux/macOS) - Writes atomically via temp file +
rename() - Sets
0600permissions on creation
Verified against a real symlink-redirect attack: the target file is left untouched. (Thanks @tuanaiseo — #70 + #71, consolidated into one shared helper)
Quality of life
-
CLAUDE_CONFIG_DIRenv var is now respected across all hooks, statusline scripts, and install/uninstall scripts. Users with non-default Claude Code config locations no longer have to symlink things into~/.claude/. (Thanks @BrendanIzu — #146) -
Natural-language activation actually works now. The README has long claimed
talk like cavemanandcaveman modeactivate caveman, andstop caveman/normal modedeactivate it. The mode tracker hook only ever matched/cavemanslash commands, so the natural-language phrasings were a lie. Now they're not. (Thanks @hummat — #120) -
Per-turn caveman reinforcement. SessionStart injects the full ruleset once, but other plugins inject competing style instructions every turn. The mode tracker now emits a small
hookSpecificOutputreminder on every UserPromptSubmit so the model keeps caveman style even after context drift. Skipped for independent modes (commit,review,compress) which have their own behavior. (Thanks @hummat — #119) -
skills/compress/scripts/is now real Python files instead of broken Git symlinks. Previously symlinks inside the skills directory broke on Windows and on platforms that don't follow them. The compress sub-skill now ships as proper modules:compress.py,detect.py,validate.py,benchmark.py, pluscli.pyand__main__.py. (Thanks @gladkia — #153) -
README link fixes — several broken anchors corrected. (Thanks @nervana21 — #169)
Internal
- Consolidated all flag file writes through
safeWriteFlag(). Directfs.writeFileSyncon predictable user-owned paths is now an anti-pattern documented inCLAUDE.md. CLAUDE.mdupdated to document the new helper,CLAUDE_CONFIG_DIRsupport, thehooks/package.jsonCJS marker, natural-language activation, and per-turn reinforcement.
Known gaps (tracked for v1.6.1)
The flag file read path in the new per-turn reinforcement still trusts whatever's in the file, so a sibling process that can write the flag could inject arbitrary content into the model's context as a "mode name." Real exploit precondition is narrow (attacker needs write access to ~/.claude/ but no read on other user files — i.e. a sandboxed sibling process), but the fix is cheap and will land in a follow-up patch: lstat + length cap + VALID_MODES whitelist on the read.
The statusline scripts (caveman-statusline.sh/.ps1) read the flag without validation either, which on a hostile flag could render terminal escape sequences. Same fix pattern, same patch.
Upgrade
Plugin users:
claude plugin update caveman
Standalone hook users:
bash hooks/install.sh --force
Other agents (Cursor/Windsurf/Cline/Copilot/etc.):
npx skills add JuliusBrussee/caveman