Skip to content

Security: andresleecom/claude-handoff

Security

SECURITY.md

Security

claude-handoff runs hooks automatically and spawns a background process, so it is designed to fail safe and minimize the data it handles. This document describes the threat model and the mitigations in place.

Reporting a vulnerability

Please open a private GitHub security advisory rather than a public issue. We aim to respond within a few days.

Threat model

The plugin runs on a developer's own machine. The trust boundaries:

  • Trusted: Claude Code itself and the hook inputs it provides (e.g. transcript_path, cwd), and the claude CLI on PATH.
  • Untrusted: the contents of the transcript (which can include text from files or web pages the user opened), the existing HANDOFF.md, the project .gitignore, and environment variables.

A fully compromised shell (attacker can set arbitrary env vars and run processes) is out of scope — at that point the machine is already lost.

Mitigations

Risk Mitigation
Command injection via CLAUDE_HANDOFF_MODEL reaching the spawned claude call The model name is validated against a strict whitelist (/^[A-Za-z0-9._-]+$/); anything else falls back to the default. The prompt is passed via stdin (never parsed by a shell), so the only non-constant argv entry is the validated model.
Secret leakage into HANDOFF.md (and later re-injection) High-confidence secret shapes (AWS/Stripe/GitHub/Slack/Google/OpenAI-Anthropic keys, JWTs, PEM private keys, bearer tokens, URL credentials) are redacted from the digest and git block before the model call. The distiller is also instructed never to emit credentials. tool_result blocks (API responses, file dumps) are excluded from the digest entirely.
Secrets committed by accident .claude/HANDOFF.md is added to .gitignore automatically. If that write fails (restricted permissions), it is logged; verify with git status and add the entry manually if needed.
Path traversal via CLAUDE_PROJECT_DIR The project directory is normalized with path.resolve(), collapsing .. segments.
Second-order prompt injection via a tampered HANDOFF.md The re-injected content is explicitly framed as reference data, not instructions to obey, and is clearly labeled as coming from a previous session.
Resource exhaustion / hangs in the background worker The claude -p call has a timeout; only the tail of pathologically large transcripts is read into memory; the digest is capped well under the stdin limit.
Recursion (the distiller triggering the plugin again) The headless call uses --bare (no plugin auto-discovery) and a CLAUDE_HANDOFF_NESTED env guard.
A failing hook breaking the session Every hook is wrapped in try/catch and always exits 0 with safe/empty output.

Residual risks

  • Secret redaction is best-effort pattern matching, not a guarantee. Do not rely on it for high-value secrets; treat .claude/HANDOFF.md as sensitive.
  • The background safety net sends a redacted transcript digest to the model. If you do not want any automatic model call, set CLAUDE_HANDOFF_AUTO=0 and use /handoff only.
  • Uncommitted filenames (not contents) may appear in the git state section.

There aren't any published security advisories