12.14 PermissionRequest Hook
This is the most powerful security extension point—it can programmatically approve or deny tool usage:
// Hook input
{
tool_name: string,
tool_input: Record<string, unknown>,
session_id: string,
cwd: string,
permission_mode: PermissionMode
}
// Hook output
{
behavior: 'allow' | 'deny',
updatedInput?: Record<string, unknown>, // Modify input
updatedPermissions?: PermissionRule[], // Persist rules
message?: string, // Feedback message
interrupt?: boolean // Interrupt current operation
}Key capability: The PermissionRequest Hook can not only make decisions, but also modify tool input and dynamically inject permission rules—this enables enterprises to implement custom security policies.
Enterprise Scenario Examples
Scenario 1: Custom CI/CD Security Policy
{
"hooks": {
"PermissionRequest": [{
"command": "python3 /path/to/security-policy.py",
"timeout": 5000
}]
}
}The security policy script can check whether commands contain production environment URLs, database connection strings, deployment commands, etc., and make allow or deny decisions based on enterprise security policies.
Scenario 2: Dynamic Permission Rule Injection
{
"behavior": "allow",
"updatedPermissions": [
{ "tool": "Bash", "pattern": "npm test", "behavior": "allow" }
]
}When a Hook approves an operation, it can simultaneously inject new persistent permission rules. For example, after the security policy script verifies that the npm test command is safe, it can inject a rule so that subsequent identical commands pass automatically.
Scenario 3: Emergency Interrupt
{
"behavior": "deny",
"interrupt": true,
"message": "Detected potential security issue - operation chain terminated"
}interrupt: true not only denies the current operation but also interrupts the entire operation chain. A normal deny only rejects the current tool call, and the model may continue trying in a different way; a denial with interrupt directly terminates the current conversation turn, forcing the user to re-initiate the request. This is very useful when a suspicious operation sequence is detected (e.g., the model first reads .env then attempts to send a network request).
Design Decision: Why multiple layers instead of a single unified permission check?
The core philosophy of defense in depth is "assume each layer may be bypassed." The problem with a single permission check is—the attack surface is concentrated. If the security check for Bash commands is only done at the tool level, then a clever command injection could bypass the entire security mechanism. In a multi-layer architecture, even if the model generates a command that bypasses AST semantic analysis, path constraints and user confirmation can still intercept it. The 23 Bash static validators in the source code are an extreme manifestation of this philosophy—they each check independently, and any single trigger results in rejection.
Design Decision: Why are the denial tracking thresholds 3 consecutive / 20 total?
DENIAL_LIMITS = \{ maxConsecutive: 3, maxTotal: 20 \}(src/utils/permissions/denialTracking.ts). This mechanism prevents auto mode (auto mode / headless agents) from falling into infinite denial loops: if the classifier denies the same type of request 3 times consecutively, it indicates that the current task may require human judgment; the 20 total limit prevents the entire session from accumulating too many silent denials. After exceeding the threshold, the system falls back to interactive confirmation (prompting), letting the user intervene in decisions.