# 第六阶段 · 模块六 · 第四节:Hooks 与权限系统
Hook 如何影响权限决策?PermissionRequest 的工作流程是什么?如何通过 Hook 实现细粒度权限控制?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ Hooks 系统 │
│ │
│ Hooks ↔ 权限系统 ← 本节 │
│ ├── Hook 拦截权限请求 │
│ ├── 权限规则影响 Hook │
│ └── 协作流程 │
└─────────────────────────────────────────────────────────────────────┘
Claude Code 权限模型:
┌─────────────────────────────────────────────────────────────────────┐
│ 权限类型 │
│ │
│ Read ──> 读取文件/目录 │
│ Write ──> 写入文件 │
│ Bash ──> 执行命令 │
│ Browser ──> 浏览器操作 │
│ MCP ──> MCP 服务器访问 │
└─────────────────────────────────────────────────────────────────────┘
工具调用流程:
Tool Call → Permission Check → [Hook] → Allow/Deny/Prompt
↑
Hook 可以在此处拦截
问 1:Hook 和权限系统谁先执行?
// Hook 先于权限系统执行
// 顺序:PreToolUse Hook → Permission Check → PostToolUse Hook
// 如果 PreToolUse 返回 continue: false
// → 不进入权限系统(直接拒绝)
// 如果 PreToolUse 返回 permissionDecision
// → 影响权限决策
源码位置:`src/types/hooks.ts` 第 121 行
权限检查 → 需要用户授权 → PermissionRequest Hook
type PermissionRequestInput = {
hookEventName: 'PermissionRequest';
toolName: string;
toolInput: Record<string, unknown>;
permission: {
type: string; // 'Read' | 'Write' | 'Bash'...
reason: string; // 为什么需要这个权限
};
prompt?: string; // 给用户看的提示
invocationId: string;
};
源码位置:`src/types/hooks.ts` 第 248 行
type PermissionRequestResult =
| {
behavior: 'allow';
updatedInput?: Record<string, unknown>;
updatedPermissions?: PermissionUpdate[];
}
| {
behavior: 'deny';
message?: string;
interrupt?: boolean;
};
// hooks/permission-controller.ts
export const permissionRequest: HookHandler = async (input, context) => {
const { toolName, toolInput, permission } = input;
// 自动批准安全操作
if (toolName === 'Read') {
const path = toolInput.file_path as string;
// 只允许读取 src 和 docs
if (path.startsWith('/project/src') || path.startsWith('/project/docs')) {
return {
permissionRequestResult: {
behavior: 'allow',
},
};
}
// 阻止读取 .env
if (path.includes('.env')) {
return {
permissionRequestResult: {
behavior: 'deny',
message: 'Cannot read .env files for security reasons',
interrupt: true,
},
};
}
}
// 阻止危险命令
if (toolName === 'Bash') {
const cmd = toolInput.command as string;
if (cmd.includes('sudo') || cmd.includes('chmod 777')) {
return {
permissionRequestResult: {
behavior: 'deny',
message: 'This command is not allowed',
interrupt: true,
},
};
}
}
// 其他情况让权限系统决定
return { continue: true };
};
┌─────────────────────────────────────────────────────────────────────┐
│ 完整权限流程 │
│ │
│ 1. PreToolUse Hook │
│ ├─ 可修改输入 │
│ └─ 可返回 continue: false(阻止) │
│ │
│ 2. 权限检查 │
│ ├─ 检查权限规则 │
│ └─ 决定是否需要用户授权 │
│ │
│ 3. PermissionRequest Hook(如果需要授权) │
│ ├─ 可自动批准 │
│ ├─ 可自动拒绝 │
│ └─ 可修改输入 │
│ │
│ 4. 工具执行 │
│ └─ 用户批准后执行 │
│ │
│ 5. PostToolUse Hook │
│ └─ 处理结果 │
└─────────────────────────────────────────────────────────────────────┘
问 1:Hook 如何影响权限决策?
// 通过 permissionDecision
return {
continue: true,
permissionDecision: {
behavior: 'allow', // 自动批准
// 或
behavior: 'deny', // 自动拒绝
},
};
问 2:Hook 修改的输入会影响权限吗?
// 是的
// 如果 Hook 修改了 toolInput,权限检查会使用修改后的值
// 示例:限制文件路径
{
updatedInput: { file_path: '/safe/path.txt' }
// → 权限检查通过,因为路径已修改为安全的
}
// hooks/path-permissions.ts
const ALLOWED_PATHS = {
Read: ['/project/src', '/project/docs', '/tmp'],
Write: ['/project/src', '/project/build'],
Bash: ['/project/scripts', '/usr/bin'],
};
export const permissionRequest: HookHandler = async (input) => {
const { toolName, toolInput } = input;
const allowed = ALLOWED_PATHS[toolName] || [];
if (toolName === 'Read' || toolName === 'Write') {
const path = toolInput.file_path as string;
if (!allowed.some(p => path.startsWith(p))) {
return {
permissionRequestResult: {
behavior: 'deny',
message: `Path not allowed: ${path}`,
interrupt: true,
},
};
}
}
return { continue: true };
};
// hooks/content-permissions.ts
export const permissionRequest: HookHandler = async (input) => {
const { toolName, toolInput, permission } = input;
// 检查 Write 操作的内容
if (toolName === 'Write') {
const content = toolInput.content as string;
// 阻止写入敏感信息
if (content.includes('password') || content.includes('api_key')) {
return {
permissionRequestResult: {
behavior: 'deny',
message: 'Cannot write sensitive content',
interrupt: true,
},
};
}
}
return { continue: true };
};
// hooks/time-permissions.ts
const WORK_HOURS = { start: 9, end: 18 }; // 9 AM - 6 PM
export const permissionRequest: HookHandler = async (input) => {
const now = new Date();
const hour = now.getHours();
// 非工作时间需要额外确认
if (hour < WORK_HOURS.start || hour > WORK_HOURS.end) {
if (input.toolName === 'Bash' || input.toolName === 'Write') {
return {
permissionRequestResult: {
behavior: 'deny',
message: 'This operation requires approval outside work hours',
interrupt: true,
},
};
}
}
return { continue: true };
};
// 只授权必要的权限
export const permissionRequest: HookHandler = async (input) => {
const { toolName, toolInput } = input;
// Read 默认允许(但限制路径)
if (toolName === 'Read') {
return { continue: true };
}
// Write 需要明确检查
if (toolName === 'Write') {
// 检查是否写入临时目录
const path = toolInput.file_path as string;
if (path.startsWith('/tmp')) {
return { continue: true };
}
// 其他 Write 操作需要授权
return { continue: true };
}
return { continue: true };
};
// hooks/audit.ts
import { appendFile } from 'fs/promises';
import { join } from 'path';
export const permissionRequest: HookHandler = async (input) => {
const logEntry = {
timestamp: new Date().toISOString(),
event: 'permission_request',
tool: input.toolName,
permission: input.permission,
user: input.userIdentity,
};
await appendFile(
'/var/log/claude-audit.json',
JSON.stringify(logEntry) + '\n'
);
return { continue: true };
};
// 错误示例:Hook 不能完全阻止权限系统
export const permissionRequest: HookHandler = async (input) => {
// ❌ 错误:直接返回 allow 可能绕过安全检查
return { permissionRequestResult: { behavior: 'allow' } };
// ✓ 正确:让权限系统做最终决定
return { continue: true };
// ✓ 正确:只在明确危险时拒绝
if (isDangerous(input)) {
return { permissionRequestResult: { behavior: 'deny' } };
}
return { continue: true };
};
答案:
// 如果 Hook 执行出错
// 默认行为取决于配置
// 通常:跳过该 Hook,继续执行
// 但如果在 PreToolUse 中抛出异常
// 可能导致整个操作失败
// 最佳实践:捕获所有可能的错误
export const permissionRequest: HookHandler = async (input) => {
try {
return await processPermission(input);
} catch (error) {
console.error('Hook error:', error);
// 失败时拒绝(保守策略)
return {
permissionRequestResult: {
behavior: 'deny',
message: 'Internal error',
},
};
}
};
答案:
// 使用内存存储临时权限
const tempPermissions = new Map<string, { expires: number }>();
export const permissionRequest: HookHandler = async (input) => {
const sessionId = input.sessionId;
const temp = tempPermissions.get(sessionId);
// 检查临时权限
if (temp && Date.now() < temp.expires) {
return { continue: true };
}
// 检查是否需要授予临时权限
if (input.toolName === 'Bash' && isSafeCommand(input.toolInput)) {
// 授予 5 分钟临时权限
tempPermissions.set(sessionId, {
expires: Date.now() + 5 * 60 * 1000,
});
return { continue: true };
}
return { continue: true };
};
答案:
// 权限规则 > Hook
// 如果权限规则明确拒绝,Hook 无法覆盖
// Hook 的行为:
// 1. Hook 可以拒绝(deny)
// 2. Hook 可以批准(allow)
// 3. Hook 不能覆盖明确的权限规则拒绝
// 优先级(高 → 低):
// 1. 权限规则(deny)
// 2. Hook(deny)
// 3. Hook(allow)
// 4. 权限规则(allow)
| 文件 | 核心内容 |
| `src/types/hooks.ts` | Hook 类型定义 |
| `src/utils/permissions/` | 权限系统实现 |
下一节我们将进入 第七章:MCP 系统:
- MCP 协议概述
- MCP 客户端实现
- MCP 工具集成
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*