访问控制Hook:文件/命令权限管控

精细化权限管控

一、访问控制Hook的设计

访问控制Hook是Claude Code Hooks系统中最核心的安全机制之一,其目标是对Claude Code在执行过程中的各类操作进行精细化权限管控,防止越权操作导致的数据泄露、文件损坏或系统破坏。通过before:tool类型的Hook,可以在工具调用执行之前进行权限拦截和审查,确保只有经过授权的操作才能被执行。

访问控制Hook的设计遵循以下几个核心原则:

核心思想:访问控制Hook本质上是一个"策略执行点"(Policy Enforcement Point),它将权限策略与Claude Code的操作行为解耦,使得安全策略可以独立于业务逻辑进行管理和更新。

访问控制Hook的整体架构采用"策略决策点(PDP)+ 策略执行点(PEP)"的模式。当Claude Code尝试调用某个工具时,before Hook作为PEP被触发,它将请求上下文(工具名称、参数、当前工作目录、项目信息等)传递给PDP进行权限决策,PDP根据预先配置的策略规则返回"允许"或"拒绝"的决策结果,PEP根据决策结果决定是否允许操作执行。

层次管控对象Hook触发点防护目标
第一层文件访问权限before:tool/Read/Write/Edit防止对敏感文件的未授权读写
第二层目录范围限制before:tool/Read/Write/Edit限制操作仅限于项目目录内
第三层命令执行权限before:tool/Bash根据角色分级管控命令执行
第四层Git分支保护before:tool/git_push保护重要分支不被直接推送

下面将逐一详细介绍每一层Hook的具体实现方案和配置方法。

二、文件访问权限控制Hook(before:tool/Read/Write/Edit)

文件访问权限控制Hook是最基础也最常用的访问控制Hook。它在Claude Code执行文件读取(Read)、写入(Write)或编辑(Edit)操作之前触发,根据预定义的权限规则判断该操作是否被允许。通过这个Hook,可以有效防止Claude Code意外修改或泄漏敏感文件。

2.1 权限规则配置

文件访问权限通过一个JSON配置文件来定义,该文件通常放在项目根目录的 .claude/ 文件夹下。配置的核心是定义不同文件或目录的访问级别:

// .claude/file-access-rules.json - 文件访问权限规则配置 { "version": "1.0", "default": "deny", "rules": [ // ========== 可读目录(Read only) ========== { "path": "${PROJECT_DIR}/src/**/*", "access": "read", "description": "源代码目录可读" }, { "path": "${PROJECT_DIR}/docs/**/*", "access": "read", "description": "文档目录可读" }, { "path": "${PROJECT_DIR}/tests/**/*", "access": "read", "description": "测试目录可读" }, // ========== 可读写目录(Read & Write) ========== { "path": "${PROJECT_DIR}/src/components/**/*", "access": "write", "description": "组件目录可读写" }, { "path": "${PROJECT_DIR}/tmp/**/*", "access": "write", "description": "临时目录可读写" }, // ========== 敏感文件 - 仅可读 ========== { "path": "${PROJECT_DIR}/.env*", "access": "read", "description": "环境变量文件仅可读不可修改" }, { "path": "${HOME}/.ssh/**/*", "access": "read", "description": "SSH密钥仅可读" }, { "path": "${PROJECT_DIR}/config/secrets/**/*", "access": "deny", "description": "机密配置目录禁止访问" }, // ========== 安全配置 - 禁止修改 ========== { "path": "${PROJECT_DIR}/.claude/**/*", "access": "read", "description": "Claude配置仅可读" }, { "path": "${PROJECT_DIR}/.github/**/*", "access": "read", "description": "GitHub配置仅可读" }, { "path": "${PROJECT_DIR}/Makefile", "access": "read", "description": "Makefile仅可读" } ] }

2.2 Hook脚本实现

文件访问控制Hook脚本读取上述JSON配置,并根据工具调用参数中的文件路径进行匹配判断:

#!/bin/bash # .claude/hooks/before-tool-file-access.sh # 文件访问控制Hook - 在文件读写前进行权限检查 set -euo pipefail # 读取环境变量(由Claude Code设置) TOOL_NAME="${CLAUDE_TOOL_NAME:-}" TOOL_ARGS="${CLAUDE_TOOL_ARGS:-}" PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}" RULES_FILE="${PROJECT_DIR}/.claude/file-access-rules.json" # 只在文件操作工具上触发 if [[ "$TOOL_NAME" != "Read" && "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then exit 0 fi # 解析工具参数中的文件路径 FILE_PATH=$(echo "$TOOL_ARGS" | python3 -c " import json, sys args = json.loads(sys.stdin.read()) print(args.get('file_path', args.get('path', ''))) " 2>/dev/null || echo "") if [[ -z "$FILE_PATH" ]]; then echo "ERROR: 无法解析文件路径,操作被拒绝" exit 1 fi # 确定请求的操作类型 case "$TOOL_NAME" in "Read") REQUESTED_ACCESS="read" ;; "Write"|"Edit") REQUESTED_ACCESS="write" ;; esac # 调用策略决策函数 python3 -c " import json, fnmatch, os, sys def load_rules(rules_file): with open(rules_file) as f: return json.load(f) def check_access(file_path, requested_access, rules): # 默认拒绝 granted = False for rule in rules['rules']: pattern = rule['path'] # 替换变量 pattern = pattern.replace('\${PROJECT_DIR}', '${PROJECT_DIR}') pattern = pattern.replace('\${HOME}', os.environ.get('HOME', '')) # glob模式匹配 if fnmatch.fnmatch(file_path, pattern): rule_access = rule['access'] if rule_access == 'deny': # 显式拒绝,优先级最高 print(f'DENY: {file_path} - {rule.get(\"description\", \"被禁止访问\")}') sys.exit(1) if rule_access == 'read' and requested_access in ('read',): granted = True elif rule_access == 'write' and requested_access in ('read', 'write'): granted = True if not granted: print(f'DENY: {file_path} - 未匹配到任何允许规则(默认拒绝)') sys.exit(1) print(f'ALLOW: {file_path} - {requested_access}') check_access('${FILE_PATH}', '${REQUESTED_ACCESS}', load_rules('${RULES_FILE}')) " || exit 1

2.3 敏感文件保护策略

在实际项目中,有几类敏感文件需要特别关注:

文件类型风险等级建议策略说明
.env / .env.*仅可读包含API密钥、数据库密码等敏感凭证
.ssh/*极高仅可读或禁止SSH私钥泄漏会导致服务器被入侵
.claude/*仅可读Claude配置被修改可能导致安全策略失效
config/*secret*极高禁止访问机密配置不应被AI直接读取
credentials.json极高禁止访问Google Cloud等云服务凭证文件
*.key / *.pem极高禁止访问TLS/SSL密钥文件
Makefile / CI配置仅可读构建和部署流水线配置不应被随意修改
重要提醒:敏感文件保护是纵深防御策略的一部分。虽然Hook可以阻止Claude Code修改或访问这些文件,但最佳实践仍然是不要在Prompt中显式要求AI操作敏感文件。技术手段与管理手段应当双管齐下。

2.4 自定义文件访问规则

除了上述通用规则,项目还可以根据自身需求自定义文件访问规则。例如:

通过将这些规则写入配置文件,团队可以在代码审查中统一管理权限策略,确保所有开发者使用一致的访问控制规则。

三、目录范围限制Hook

目录范围限制Hook是文件访问控制的进一步强化,它将Claude Code的"活动范围"限定在项目目录内,防止AI意外访问项目目录之外的系统敏感区域。即使文件访问权限规则配置有疏漏,目录范围限制也能作为最后一道防线阻止越权操作。

3.1 目录白名单配置

// .claude/directory-allowlist.json { "version": "1.0", "description": "Claude Code允许操作的白名单目录列表", "allowed_directories": [ "${PROJECT_DIR}", "${PROJECT_DIR}/src", "${PROJECT_DIR}/tests", "${PROJECT_DIR}/docs", "${PROJECT_DIR}/scripts", "${PROJECT_DIR}/config", "${PROJECT_DIR}/tmp" ], "exclusions": [ "${PROJECT_DIR}/node_modules", "${PROJECT_DIR}/.git" ] }

3.2 Hook脚本实现

#!/bin/bash # .claude/hooks/before-tool-directory-scope.sh # 目录范围限制Hook - 禁止访问项目目录外的文件系统 set -euo pipefail TOOL_NAME="${CLAUDE_TOOL_NAME:-}" TOOL_ARGS="${CLAUDE_TOOL_ARGS:-}" PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}" ALLOWLIST_FILE="${PROJECT_DIR}/.claude/directory-allowlist.json" # 只对文件和Bash操作进行检查 if [[ "$TOOL_NAME" != "Read" && "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" && "$TOOL_NAME" != "Bash" ]]; then exit 0 fi # 从工具参数中提取文件路径或命令中的路径引用 REFERENCED_PATH=$(echo "$TOOL_ARGS" | python3 -c " import json, sys, re args = json.loads(sys.stdin.read()) # 对于文件操作,检查file_path或path参数 for key in ['file_path', 'path', 'filename']: if key in args: print(args[key]) sys.exit(0) # 对于Bash命令,尝试提取路径引用 if 'command' in args: cmd = args['command'] paths = re.findall(r'[\"'](/[\w/.\-]+)[\"']', cmd) if paths: for p in paths: print(p) sys.exit(0) print('') " 2>/dev/null) if [[ -z "$REFERENCED_PATH" ]]; then exit 0 fi # 规范化路径(移除 .. 引用,解析符号链接) REAL_PATH=$(python3 -c " import os try: print(os.path.realpath('${REFERENCED_PATH}')) except: print('') ") if [[ -z "$REAL_PATH" ]]; then exit 0 fi # 检查路径是否在允许的白名单目录内 python3 -c " import json, os with open('${ALLOWLIST_FILE}') as f: config = json.load(f) # 获取项目目录的绝对路径 project_dir = os.path.realpath('${PROJECT_DIR}') real_path = '${REAL_PATH}' # 检查白名单 for allowed in config['allowed_directories']: allowed_path = os.path.realpath(allowed.replace('\${PROJECT_DIR}', project_dir)) if real_path.startswith(allowed_path): # 检查是否在排除列表中 for excl in config.get('exclusions', []): excl_path = os.path.realpath(excl.replace('\${PROJECT_DIR}', project_dir)) if real_path.startswith(excl_path): print(f'DENY: {real_path} - 路径在白名单排除列表中') sys.exit(1) print(f'ALLOW: {real_path}') sys.exit(0) print(f'DENY: {real_path} - 不在允许的目录范围内') sys.exit(1) " || exit 1

3.3 禁止访问的系统目录列表

以下系统关键目录在任何情况下都不应被允许访问,这些目录通常被硬编码在Hook中作为"黑名单":

最佳实践:目录范围限制应该使用"白名单"方式,即只允许明确列出的目录,拒绝所有其他目录。这样可以最大程度地防止遗漏。同时,应当对路径中的 .. 进行规范化处理,防止路径穿越攻击。

3.4 越权访问的错误提示

当越权访问被拦截时,应当给出明确、友好的错误提示,帮助用户理解为什么操作被拒绝:

ERROR: 操作被访问控制Hook拒绝 原因: 尝试访问的路径不在允许的目录范围内 路径: /etc/passwd 允许范围: /home/user/my-project/ 解决方案: 1. 检查命令中的路径是否正确 2. 如需扩展允许的目录范围,请联系项目管理员修改 .claude/directory-allowlist.json 配置文件 3. 确认当前工作目录在项目目录内 提示: 访问控制策略由项目安全配置定义, 修改请提交PR至 .claude/ 目录下的配置文件。

四、命令执行权限分级Hook(before:tool/Bash)

命令执行权限分级Hook是对Bash工具调用的精细化管控。在实际团队协作场景中,不同的成员角色对命令执行的需求不同。通过分级权限管理,可以在保证工作效率的同时,有效降低安全风险。

4.1 权限分级模型

系统定义了三个权限等级,每个等级对应不同的命令执行范围:

权限等级角色可执行命令范围典型场景
Level 3管理员(Admin)所有命令系统配置、软件安装、生产部署
Level 2开发者(Developer)常规开发命令代码构建、测试运行、git操作
Level 1只读者(Reader)只读命令代码审查、文档阅读、日志查看

4.2 命令白名单配置

// .claude/command-permissions.json { "version": "1.0", "roles": { "admin": { "mode": "allow_all", "description": "管理员:所有命令均可执行" }, "developer": { "mode": "allowlist", "description": "开发者:仅允许执行开发相关命令", "allowed_commands": [ // 构建与包管理 "npm", "yarn", "pnpm", "pip", "pip3", "mvn", "gradle", "cargo", "go build", "make", // Git操作(非破坏性) "git add", "git commit", "git push", "git pull", "git fetch", "git merge", "git checkout", "git branch", "git rebase", "git status", "git log", "git diff", "git stash", "git reset", "git restore", // 开发工具 "node", "python", "python3", "gcc", "clang", "rustc", "deno", "bun", // 文件操作 "cat", "head", "tail", "less", "more", "ls", "find", "grep", "rg", "ack", "wc", "sort", "uniq", "cut", "tee", "cp", "mv", "rm", "mkdir", "touch", "chmod", "chown", // 测试 "jest", "mocha", "pytest", "go test", "cargo test", "npm test", // Docker(只读操作) "docker ps", "docker logs", "docker images", "docker inspect" ], "blocked_patterns": [ "^rm -rf /", "^dd if=", "^mkfs\\.", "^:(){ :|:& };:", "^> /dev/sda" ] }, "reader": { "mode": "allowlist", "description": "只读者:仅允许执行只读命令", "allowed_commands": [ "cat", "head", "tail", "less", "more", "ls", "find", "grep", "rg", "ack", "wc", "sort", "uniq", "cut", "git log", "git diff", "git status", "git show", "git blame", "pwd", "which", "type", "echo", "printf", "date", "cal" ] } } }

4.3 Hook脚本实现

#!/bin/bash # .claude/hooks/before-tool-bash-permission.sh # 命令执行权限分级Hook set -euo pipefail TOOL_NAME="${CLAUDE_TOOL_NAME:-}" TOOL_ARGS="${CLAUDE_TOOL_ARGS:-}" PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}" CONFIG_FILE="${PROJECT_DIR}/.claude/command-permissions.json" # 只在Bash工具上触发 if [[ "$TOOL_NAME" != "Bash" ]]; then exit 0 fi # 解析命令 COMMAND=$(echo "$TOOL_ARGS" | python3 -c " import json, sys args = json.loads(sys.stdin.read()) print(args.get('command', '')) " 2>/dev/null) if [[ -z "$COMMAND" ]]; then exit 0 fi # 确定当前用户的角色(可以通过环境变量、Git配置等获取) USER_ROLE="${CLAUDE_USER_ROLE:-developer}" # 权限决策 python3 -c " import json, re, os, sys with open('${CONFIG_FILE}') as f: config = json.load(f) role = '${USER_ROLE}' command = '${COMMAND}'.strip() if role not in config['roles']: print(f'ERROR: 未知的角色类型: {role}') sys.exit(1) role_config = config['roles'][role] # 管理员模式:允许所有 if role_config['mode'] == 'allow_all': print(f'ALLOW [{role}]: {command}') sys.exit(0) # 白名单模式 if role_config['mode'] == 'allowlist': cmd_base = command.split()[0] if command.split() else '' # 检查黑名单模式 for pattern in role_config.get('blocked_patterns', []): if re.search(pattern, command): print(f'DENY [{role}]: {command} - 匹配到禁止模式: {pattern}') sys.exit(1) # 检查白名单 for allowed in role_config['allowed_commands']: if command.startswith(allowed): print(f'ALLOW [{role}]: {command}') sys.exit(0) # 未匹配到白名单 print(f'DENY [{role}]: {command} - 命令不在 {role} 角色的白名单中') print(f'提示: 如需执行此命令,请联系管理员提升权限') sys.exit(1) " || exit 1

4.4 不同项目和分支的权限应用

更进阶的权限模型可以根据项目和分支动态切换权限等级。例如:

// .claude/environment-permissions.json { "version": "1.0", "context_mapping": [ { "project": "my-web-app", "branch_pattern": "main", "role": "reader", "description": "生产主分支只允许只读操作" }, { "project": "my-web-app", "branch_pattern": "release/*", "role": "developer", "description": "发布分支允许开发者权限" }, { "project": "my-web-app", "branch_pattern": "feature/*", "role": "developer", "description": "特性分支允许开发者权限" }, { "project": "my-web-app", "branch_pattern": "hotfix/*", "role": "admin", "description": "热修复分支需要管理员权限" }, { "project": "infrastructure", "branch_pattern": "*", "role": "admin", "description": "基础设施项目始终使用管理员权限" } ] }

通过这种上下文感知的权限模型,可以根据当前的工作场景自动切换权限等级,既保证了生产环境的安全性,又不会过度限制开发分支的操作灵活性。

五、Git分支保护Hook(before:tool/git_push)

Git分支保护Hook是专门针对推送操作的安全控制机制。它可以在Git推送操作(git push)被执行前进行检查,禁止向受保护分支直接推送代码,强制要求通过Pull Request流程进行代码审查和合并,从而保证代码质量和项目安全。

5.1 分支保护规则配置

// .claude/git-branch-protection.json { "version": "1.0", "protected_branches": [ { "pattern": "main", "mode": "block_push", "message": "main分支禁止直接推送!请通过Pull Request合并代码。", "allow_users": ["release-manager"], "description": "主分支完全保护" }, { "pattern": "master", "mode": "block_push", "message": "master分支禁止直接推送!请通过Pull Request合并代码。", "description": "主分支完全保护" }, { "pattern": "release/*", "mode": "require_approval", "approvers": ["tech-lead", "qa-manager"], "message": "release分支需要tech-lead或qa-manager审批后方可推送", "description": "发布分支需要审批" }, { "pattern": "hotfix/*", "mode": "require_approval", "approvers": ["admin"], "message": "hotfix分支需要管理员审批", "description": "热修复分支需要管理员权限" }, { "pattern": "staging", "mode": "block_force_push", "message": "staging分支禁止强制推送", "description": "预发布分支禁止force push" } ], "audit_log": true, "audit_log_path": "${PROJECT_DIR}/.claude/audit/push-attempts.log" }

5.2 Hook脚本实现

#!/bin/bash # .claude/hooks/before-tool-git-push.sh # Git分支保护Hook - 在git push前检查分支保护规则 set -euo pipefail TOOL_NAME="${CLAUDE_TOOL_NAME:-}" TOOL_ARGS="${CLAUDE_TOOL_ARGS:-}" PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}" CONFIG_FILE="${PROJECT_DIR}/.claude/git-branch-protection.json" AUDIT_LOG="${PROJECT_DIR}/.claude/audit/push-attempts.log" # 只在git push操作时触发 if [[ "$TOOL_NAME" != "Bash" ]]; then exit 0 fi # 检查命令中是否包含 git push COMMAND=$(echo "$TOOL_ARGS" | python3 -c " import json, sys args = json.loads(sys.stdin.read()) print(args.get('command', '')) " 2>/dev/null) if [[ ! "$COMMAND" =~ "git push" ]]; then exit 0 fi # 获取当前分支名称 CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") if [[ -z "$CURRENT_BRANCH" ]]; then exit 0 fi # 检查是否为强制推送 IS_FORCE_PUSH=false if [[ "$COMMAND" =~ "git push.*--force" || "$COMMAND" =~ "git push.*-f" ]]; then IS_FORCE_PUSH=true fi # 获取当前用户 CURRENT_USER="${CLAUDE_USER:-$(git config user.name 2>/dev/null || echo 'unknown')}" # 分支保护决策 python3 -c " import json, fnmatch, os, sys, datetime with open('${CONFIG_FILE}') as f: config = json.load(f) branch = '${CURRENT_BRANCH}' user = '${CURRENT_USER}' is_force = ${IS_FORCE_PUSH} timestamp = datetime.datetime.now().isoformat() for rule in config['protected_branches']: if fnmatch.fnmatch(branch, rule['pattern']): # 记录审计日志 if config.get('audit_log', False): log_entry = f'[{timestamp}] PUSH_ATTEMPT user={user} branch={branch} mode={rule[\"mode\"]} command=${COMMAND}' log_path = '${AUDIT_LOG}' os.makedirs(os.path.dirname(log_path), exist_ok=True) with open(log_path, 'a') as f: f.write(log_entry + chr(10)) # 检查模式 if rule['mode'] == 'block_push': # 检查是否有豁免用户 if user in rule.get('allow_users', []): print(f'WARN: 分支 {branch} 受保护,但用户 {user} 在白名单中,允许推送') sys.exit(0) print(f'BLOCKED: 分支 {branch} 受保护!') print(f' 原因: {rule.get(\"message\", \"禁止直接推送\")}') print(f' 建议: 创建Pull Request,通过代码审查后合并') sys.exit(1) elif rule['mode'] == 'require_approval': print(f'BLOCKED: 分支 {branch} 需要审批才能推送!') print(f' 原因: {rule.get(\"message\", \"需要审批\")}') print(f' 审批人: {\", \".join(rule.get(\"approvers\", []))}') print(f' 建议: 请先获得审批人批准,或创建Pull Request') sys.exit(1) elif rule['mode'] == 'block_force_push' and is_force: print(f'BLOCKED: 分支 {branch} 禁止强制推送!') print(f' 原因: {rule.get(\"message\", \"禁止force push\")}') print(f' 建议: 使用普通 git push 或通过PR合并') sys.exit(1) # 匹配到规则且未被拦截,允许 print(f'ALLOWED: 分支 {branch} 推送已通过检查') sys.exit(0) # 未匹配到任何保护规则,允许推送 print(f'ALLOWED: 分支 {branch} 无匹配的保护规则,允许推送') " || exit 1

5.3 分支保护违规告警

当分支保护被触发时,系统不仅会阻止推送操作,还会生成详细的告警信息:

╔══════════════════════════════════════════════════════════╗ ║ Git分支保护 - 操作已阻止 ║ ╠══════════════════════════════════════════════════════════╣ ║ 时间: 2026-05-08 10:30:15 ║ ║ 用户: developer-zhang ║ ║ 分支: main ║ ║ 操作: git push origin main ║ ║ 规则: block_push (main分支禁止直接推送) ║ ╠══════════════════════════════════════════════════════════╣ ║ 建议操作: ║ ║ 1. 创建新的特性分支: git checkout -b feature/xxx ║ ║ 2. 推送到特性分支: git push origin feature/xxx ║ ║ 3. 在GitHub上创建Pull Request ║ ║ 4. 通过代码审查后合并到main分支 ║ ╚══════════════════════════════════════════════════════════╝

5.4 审计日志

所有分支保护相关的操作都会被记录到审计日志中,便于事后追溯和安全审查:

# .claude/audit/push-attempts.log [2026-05-08T09:15:22] PUSH_ATTEMPT user=zhangsan branch=main mode=block_push status=DENIED [2026-05-08T09:20:45] PUSH_ATTEMPT user=lisi branch=release/v2.1 mode=require_approval status=DENIED [2026-05-08T09:35:10] PUSH_ATTEMPT user=wangwu branch=feature/login mode=no_rule status=ALLOWED [2026-05-08T10:00:00] PUSH_ATTEMPT user=admin branch=hotfix/critical-bug mode=require_approval status=DENIED [2026-05-08T10:05:30] PUSH_ATTEMPT user=admin branch=hotfix/critical-bug mode=require_approval status=ALLOWED

关键要点:分支保护Hook不是要完全禁止推送,而是要确保推送操作遵循团队的代码管理规范。合理的配置应该做到"安全写入"——让正确的代码以正确的方式到达正确的分支。