guardHooks. That is the only labeled deny for unwrapped built-in tools; canUseTool and a bare allowedTools name are not a policy gate.How do you block Bash in Claude Agent SDK?
Deny it on PreToolUse. Bash is a built-in tool. It has no local execute for guardTool. PreToolUse is the only labeled, application-context deny for unwrapped tools: Bash, Write, and the MCP tools that you didn't wrap.
guardTool wraps an authored tool() that you wrote. Don't use it on Bash. Don't apply both helpers to the same authored tool.
import { guardHooks } from "@arcjet/guard/claude-agent-sdk/v0";import { tokenBucket } from "@arcjet/guard";import { arcjet } from "./arcjet.js";
const bashLimit = tokenBucket({ bucket: "bash", refillRate: 20, intervalSeconds: 60, maxTokens: 20,});
const conversationId = "..."; // your conversation id
export const hooks = guardHooks(arcjet, { sessionId: conversationId, action: ({ toolName }) => `${toolName}.invoked`, rules: ({ toolName }) => toolName === "Bash" ? [bashLimit({ key: "Bash", requested: 1 })] : [],});Pass hooks to query({ options: { hooks } }). protect() is the HTTP check on a route. This deny is the agent-side gate on the shell.
Why isn't canUseTool or allowedTools enough?
canUseTool is the ask path, not a check on every tool. A bare allowedTools name such as Bash auto-approves every call unless an ask rule or plan mode sends it back. Narrow documented exceptions apply, such as tools that require user interaction and critical-path rm. If you list Bash and put the check in the callback, then the callback never runs.
That is the whole article at canUseTool is not a policy gate. Don't put the deny on the callback.
Hiding Bash with a bare disallowedTools name removes the tool from the request, so that Claude doesn't see it. That is a prompt-surface control, not the PreToolUse deny on a call that still reaches the hook. A scoped deny rule such as Bash(rm *) does block matching calls at run time, in every mode, but it isn't a labeled decision on this call with application context.
Where does PreToolUse sit?
Hooks run before every other permission step. A PreToolUse deny applies even in bypassPermissions. It still sees a bare allowedTools name.
On deny, the hook returns permissionDecision: "deny" and the tool doesn't run. PostToolUse is capture only. It can't un-send.
Correlate the decision with a sessionId that you already have. claudeAgentContext reads that ID. It never mints one.
Does a sandbox replace the deny?
No. The Bash sandbox isolates the file system and network for the shell and its child processes. The operating system enforces that boundary. It doesn't decide whether this command should run.
A jailed curl | sh is still a send if egress is open. A jailed cat ~/.ssh/id_rsa is still a read if the path is allowed. A sandbox is not a tool policy. Isolation isn't authorization.
Keep the sandbox and tighten its egress. Put the deny on PreToolUse.
What if Arcjet is down on PreToolUse?
Helpers default to onGuardError: "deny". If Guard can't be evaluated, then the tool doesn't run.
Timeout already fail-closes, so the tool doesn't run while you wait. "allow" is a legitimate choice on inbound prompt screening, because failing closed there would stop the agent answering during an outage. It isn't the default on this tool.
Frequently asked questions
How do I block Bash in Claude Agent SDK?
Deny Bash on PreToolUse with guardHooks. Bash is a built-in tool with no local execute for guardTool. PreToolUse is the only labeled, application-context deny for unwrapped built-in tools and for MCP you did not wrap; a scoped disallowedTools rule such as Bash(rm *) also denies matching calls at runtime.
Is canUseTool enough to block Bash?
No. canUseTool is not a policy gate. It runs when no earlier step resolved the call. A bare allowedTools name auto-approves every Bash call, so the callback never sees it.
Does allowedTools deny Bash?
No. A bare name is an allow rule. allowedTools: ["Bash"] auto-approves every Bash call unless an ask rule or plan mode sends it back. Unlisted tools can still run under bypassPermissions.
What if Arcjet is down on PreToolUse?
Helpers default to onGuardError: "deny". If Guard cannot be evaluated, Bash does not run. Timeout already fail-closes.
Does a sandbox replace the deny?
No. The Bash sandbox isolates filesystem and network after the call is approved. It does not decide whether this command should run. Isolation is not authorization.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Get allow, deny, and redact on agent actions before the side effect.