Skip to content

Security: Hook writes to predictable path without symlink protection (possible file clobber)#70

Merged
JuliusBrussee merged 2 commits intoJuliusBrussee:mainfrom
tuanaiseo:contribai/fix/security/hook-writes-to-predictable-path-without-
Apr 15, 2026
Merged

Security: Hook writes to predictable path without symlink protection (possible file clobber)#70
JuliusBrussee merged 2 commits intoJuliusBrussee:mainfrom
tuanaiseo:contribai/fix/security/hook-writes-to-predictable-path-without-

Conversation

@tuanaiseo
Copy link
Copy Markdown
Contributor

Problem

The SessionStart hook writes to ~/.claude/.caveman-active using fs.writeFileSync on a predictable path. If that path is replaced with a symlink, Node will follow it and overwrite the symlink target. A local attacker (or another process running as the same user) could abuse this to modify unintended files writable by the user.

Severity: medium
File: hooks/caveman-activate.js

Solution

Before writing, use lstatSync to reject symlinks, open files with flags that avoid following links where possible, and enforce restrictive permissions (e.g., create with mode 0o600). Consider writing to a temporary file and atomic-renaming after validation.

Changes

  • hooks/caveman-activate.js (modified)
  • hooks/caveman-mode-tracker.js (modified)

Testing

  • Existing tests pass
  • Manual review completed
  • No new warnings/errors introduced

The SessionStart hook writes to `~/.claude/.caveman-active` using `fs.writeFileSync` on a predictable path. If that path is replaced with a symlink, Node will follow it and overwrite the symlink target. A local attacker (or another process running as the same user) could abuse this to modify unintended files writable by the user.

Affected files: caveman-activate.js, caveman-mode-tracker.js

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
The SessionStart hook writes to `~/.claude/.caveman-active` using `fs.writeFileSync` on a predictable path. If that path is replaced with a symlink, Node will follow it and overwrite the symlink target. A local attacker (or another process running as the same user) could abuse this to modify unintended files writable by the user.

Affected files: caveman-activate.js, caveman-mode-tracker.js

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
@JuliusBrussee JuliusBrussee merged commit adafaba into JuliusBrussee:main Apr 15, 2026
JuliusBrussee added a commit that referenced this pull request Apr 15, 2026
…ession

Writes were hardened via safeWriteFlag (PRs #70/#71) but readers still
trusted whatever the flag contained. A local attacker with write access
to ~/.claude/ could symlink the flag at a secret file and have the
per-turn reinforcement inject its bytes into model context, or the
statuslines echo ANSI escapes to the terminal on every keystroke.

- caveman-config.js: new readFlag() — lstat symlink refuse, 64-byte cap,
  O_NOFOLLOW, VALID_MODES whitelist. Returns null on any anomaly.
- caveman-mode-tracker.js: per-turn reinforcement routes through
  readFlag() instead of fs.readFileSync.
- caveman-statusline.sh / .ps1: symlink + size refuse, strip to
  [a-z0-9-], whitelist-validate before rendering.
- compress.py (3 synced copies): is_sensitive_path() denylist refuses
  .env*, .netrc, keys/certs, ~/.ssh|.aws|.gnupg|.kube|.docker, and any
  basename containing secret/credential/password/apikey/token/privatekey
  (separator-insensitive). Fails loudly before read — no silent exfil
  of credentials to the Anthropic API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants