Claude Code¶
GuardLayer can guard a Claude Code session as a hook: every tool call is checked before it runs, and what tools return is scanned before Claude reads it.
guardlayer hook claude-code --print-config # merge the output into .claude/settings.json
guardlayer --preset strict hook claude-code --print-config # the same, with a stricter posture
| Event | What GuardLayer does |
|---|---|
PreToolUse |
Runs the tool policy and session taint. Returns deny (Claude sees the reason) or ask (you get a permission prompt). Otherwise it returns nothing and Claude Code's own permission rules decide. |
PostToolUse |
Scans what WebFetch, Bash, Read and MCP tools returned. If it finds an injection, it marks the session hostile, tells Claude to treat that output as untrusted, and sends auto mode's classifier a short note (classifierContext: the tool, its origin and the rules that fired, never the output itself). Claude still sees the output unless you add --withhold-injections, which replaces it in the tool's own output shape. |
UserPromptSubmit |
Fingerprints secrets you paste, so they can't later leave in a tool call. Blocks prompts only with --block-prompts: you are trusted. |
It can only tighten
The hook never returns allow, so it can't widen Claude Code's own permission settings, even if it's
misconfigured or attacked.
Details:
-
Warn or withhold. Claude Code's
decision: "block"on a tool result only adds a reason next to it; Claude still reads the output. The default follows Claude Code's own design (a warning to Claude, a note to the classifier, which never reads tool results).--withhold-injectionsreplaces the output so Claude never reads it, at the cost that a false alarm on your own document hides that document too. -
Claude Code's built-in tools come pre-tagged:
Bashis exec,WebFetchis network,Editis write,TodoWriteis harmless. ForWriteandEdit, only the target path is checked, not the file content, so writing security tests or shell scripts doesn't trip the command rules. - State is kept per Claude Code
session_idin~/.guardlayer/sessions(orGUARDLAYER_STATE_DIR), in the file store. - The hook fails open if something goes wrong, unless
fail_closedis set (thestrictpreset sets it).
Faster: the hook server¶
The command hook starts Python for every event: about 0.6 s on Windows, and Claude Code calls it before and after each tool. The hook server is a long-running GuardLayer that Claude Code calls over HTTP instead, with the same checks:
guardlayer --config pilot.toml hook claude-code --server --print-config # merge into .claude/settings.json
| Measured on Windows (laptop, idle; medians) | Command hook | Hook server |
|---|---|---|
Before a tool call (PreToolUse) |
608 ms | 10 ms (p95 31 ms) |
After a tool call, 4 KB result (PostToolUse) |
about 680 ms | 67 ms |
GuardLayer's own handling inside the server, measured again 2026-10-04 with the destination check on (the default): a 1 KB result 27 ms, 4 KB 48 ms, 8 KB 75 ms; a tool call 5 ms on a long session. The detectors take about 5 ms per KB; the rest is session bookkeeping. On a loaded machine (builds, a local model) expect several times more.
How it runs:
- The snippet adds a
SessionStartcommand hook (--ensure-server) that starts the server in the background if it isn't running, and HTTP hooks for the other events. The port is derived from the config and preset, so projects with different configs get different servers. - It listens on 127.0.0.1 only and refuses anything that looks like a browser (an
Originheader, a non-loopbackHost, a body that isn't JSON), so a web page can't reach it. On a shared machine add--token-env NAME: the server then requiresAuthorization: Bearer $NAME, and Claude Code sends it from that environment variable. - Editing the config is picked up on the next event (a broken edit keeps the previous config, with a message in
~/.guardlayer/hookserver.log).
If the server isn't running, tool calls are not checked
Claude Code treats a failed connection to an HTTP hook as a non-blocking error and lets the tool call go ahead.
--ensure-server starts the server at the beginning of every session and shows a warning if it can't. If the
server is stopped during a session, calls go unchecked until the next session starts. The command hook has no such
gap; use it where that matters more than speed.