github JuliusBrussee/caveman v1.6.0
v1.6.0 — Hardening release: hook crash fixes + symlink-safe flag writes

one day ago

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.json declares "type": "module". Before this fix, any user with ~/.claude/package.json set to ESM (common with several Claude Code plugins) hit ReferenceError: require is not defined in ES module scope on every session. SessionStart and UserPromptSubmit hooks both silently failed, the flag file was never written, and the statusline badge disappeared. Fixed by shipping hooks/package.json with {"type": "commonjs"} so the hooks pin themselves to CJS regardless of ancestor settings. (Thanks @malakhov-dmitrii#174)

  • ${CLAUDE_PLUGIN_ROOT} is now quoted in plugin.json. Plugin install paths containing spaces (e.g. ~/Library/Application Support/... on macOS, C:\Users\Some Name\... on Windows) used to break the node invocation 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 + nested hooks array. Repo also now ships .codex/config.toml with codex_hooks = true so 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-active is 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 ~/.claude bind 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 in hooks/caveman-config.js and 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_NOFOLLOW where supported (Linux/macOS)
    • Writes atomically via temp file + rename()
    • Sets 0600 permissions 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_DIR env 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 caveman and caveman mode activate caveman, and stop caveman / normal mode deactivate it. The mode tracker hook only ever matched /caveman slash 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 hookSpecificOutput reminder 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, plus cli.py and __main__.py. (Thanks @gladkia#153)

  • README link fixes — several broken anchors corrected. (Thanks @nervana21#169)

Internal

  • Consolidated all flag file writes through safeWriteFlag(). Direct fs.writeFileSync on predictable user-owned paths is now an anti-pattern documented in CLAUDE.md.
  • CLAUDE.md updated to document the new helper, CLAUDE_CONFIG_DIR support, the hooks/package.json CJS 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

Don't miss a new caveman release

NewReleases is sending notifications on new releases.