Claude Code

Installing the Agent Firewall for Claude Code, and what its hooks can and cannot see

Edit on GitHub

Claude Code is the agent the firewall sees the most of. The firewall installs as a set of Claude Code hooks (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart and ConfigChange). Tested with Claude Code 2.1.289; the event formats were captured live from the installed CLI.

Install

launchsafe-firewall install
launchsafe-firewall doctor
launchsafe-firewall doctor --fix     # turn on Claude Code's sandbox with a profile from your policy (passphrase)

install asks for an approval passphrase, registers the hooks in ~/.claude/settings.json and pins the approver key in the hook command. Use --scope project|local for the project's files. For a machine you administer, use install --managed so no repository can switch the hooks off. See the Quickstart.

When an action needs you

Claude Code can ask. An action that needs a person appears in the agent's permission prompt, with the firewall's reason and rule id. When nobody can answer (claude -p, CI, dontAsk mode), the action is held in the queue with the command that approves it.

How the hooks decide

  • PreToolUse returns allow, deny or ask. Across hooks the most restrictive wins, and a hook allow never overrides your own deny or ask rules, so the firewall can only tighten.
  • Only exit code 2 or an explicit JSON deny blocks a tool. Exit 1, a crash, a missing script and a timeout all let the tool run. The firewall therefore catches its own errors and the installed command is wrapped ... || exit 2, so even a failure of Node itself fails closed.
  • Claude Code sends mcp_server: { name, source } on PreToolUse. A server whose source is project, dynamic or unknown is untrusted whatever its name, unless your own policy lists it by exact name.
  • Claude Code can replace tool output, so the firewall can redact secrets and the email guard can keep a withheld message from the model. Codex, Gemini CLI and Cursor cannot do this.
  • A change made outside the agent mid-session is blocked by ConfigChange.

What the hooks cannot do

  • Files pulled into a prompt with @ do not pass through PreToolUse. The firewall cannot see those, so it cannot taint on them. Treat an @-included file as trusted input you chose.
  • A repository's own .claude/settings.json can set disableAllHooks, and in a -p run a repository's settings hooks load without the trust dialog. Only managed settings resist this. doctor checks for it and can generate a managed-settings install (with allowManagedHooksOnly) so no user or project setting can switch the firewall off.
  • Hooks are an enforcement point, not a cage. They decide whether a tool runs; they do not contain a process that does run. Run the firewall together with Claude Code's OS sandbox. doctor --fix writes a sandbox profile derived from your policy, behind the approval passphrase.
  • The sandbox sees hosts, not methods. A host admitted for fetching (registry.npmjs.org, github.com, pypi.org) is reachable for uploads too. doctor labels such hosts upload-capable. Uploading to them is controlled by the firewall's rules for the commands it can see (git push, npm publish, gh, docker push).

Managed settings

Managed settings files live at /Library/Application Support/ClaudeCode/managed-settings.json (macOS) and /etc/claude-code/managed-settings.json (Linux). A project's disableAllHooks: true turns off user-level hooks; only managed settings resist it, and allowManagedHooksOnly restricts which hooks run at all.

launchsafe-firewall install --managed --print | sudo tee "/Library/Application Support/ClaudeCode/managed-settings.json"

The sandbox profile

launchsafe-firewall doctor --fix writes the sandbox key of ~/.claude/settings.json (--scope project|local for the project's files) and, for package managers it finds, a release-age cooldown in their user-level config, each after a backup and a diff you confirm. doctor --fix --dry-run prints every change without asking for anything. From then on an agent change that weakens the sandbox profile is blocked like removing the firewall's hooks (policy sandbox.protectInstalledProfile). See Policy.

On this page