Getting Started (更新: 2026/6/3)

Claude Code 权限审计清单:5分钟检查 allow/deny 和危险命令

每天早上用5分钟审计 Claude Code 的 allow/deny、危险命令、环境变量和日志策略。

Claude Code 权限审计清单:5分钟检查 allow/deny 和危险命令

权限审计是在确认 Claude Code 今天可以做什么

最危险的设置不一定是“全部允许”。更常见的问题更隐蔽:昨天你允许了 Bash(git push *) 或范围很宽的 WebFetch,今天打开生产仓库时忘了这条规则仍然有效。

这篇文章提供一个每天早上5分钟的 Claude Code 权限审计流程。allow 表示无需再次确认即可执行,ask 表示每次都要确认,deny 表示阻止。对新手来说,可以把它理解为给 agent 划出的工作台边界。

截至2026年6月3日,官方文档说明 /permissions 可以显示工具权限以及权限来自哪个 settings 文件。规则按 deny -> ask -> allow 的顺序评估,deny 优先。把本文内容变成团队政策前,请核对官方的 Claude Code PermissionsSettingsEnvironment variables

建议把这一步放在 前30分钟检查清单 之后、使用 CLAUDE.md 入门模板 做团队配置之前。目标不是增加流程,而是把工作缩小到可以被认真 review 的范围。

5分钟审计流程

先看今天任务的风险,而不是先看 JSON。文档修改、依赖更新、表单修改和部署不应该使用同一组权限边界。

flowchart TD
  A[Start session] --> B[Check git status]
  B --> C[Open /permissions]
  C --> D[Review allow / ask / deny]
  D --> E[Inspect env and logs policy]
  E --> F[Run local audit script]
  F --> G[Write handoff note]
检查位置要确认什么危险信号
git status --short开始前是否已有改动有 diff 但没人知道是谁改的
/permissions每条规则来自哪个文件BashWebFetchEdit 整体在 allow
.claude/settings.json团队共享权限部署、计费或支付链接被自动允许
.claude/settings.local.json个人临时权限昨天探索用的规则还在
env会话历史和子进程策略需要审计日志,但设置了 CLAUDE_CODE_SKIP_PROMPT_HISTORY=1

有一个官方细节很重要:在 deny 中写裸的 Bash 会把 Bash 工具从 Claude 的上下文中移除;而 Bash(rm *) 这样的范围规则会保留工具,只阻止匹配的调用。CLAUDE.md 里的“不要执行”是指导,不是技术边界。真正的边界要靠 permission rules、permission mode、hook 和 sandbox。

仓库级 settings 最小示例

共享设置最好先阻止危险操作,再把需要判断的操作放进 ask。下面可以作为 .claude/settings.json 的起点。

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(git status *)",
      "Bash(git diff *)",
      "Read(./src/**)",
      "Read(./docs/**)"
    ],
    "ask": [
      "Bash(npm install *)",
      "Bash(pnpm add *)",
      "Bash(git push *)",
      "Bash(npm run deploy *)",
      "Edit(./.github/**)"
    ],
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(rm -rf *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "WebFetch(domain:pastebin.com)"
    ],
    "defaultMode": "default",
    "disableBypassPermissionsMode": "disable"
  },
  "env": {
    "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1"
  }
}

这里的 harness 可以理解为 agent 的脚手架:Claude Code、settings、hooks、sandbox 和日志策略放在一起。不是更盲目地信任模型,而是给它更窄、更清楚的工作空间。

Bash(npm run test *) 使用通配符。当前文档也识别放在末尾的 :*,例如 Bash(ls:*),但只在模式末尾有效。团队配置里使用 Bash(git push *) 这种空格形式更容易 review。

可复制的仓库审计清单

审计不应该变成很长的安全文档。把下面这段贴到 PR、issue 或交接说明即可。

claude_code_permission_audit:
  date: "2026-06-03"
  repository:
    name: "your-repo"
    branch: "feature/your-task"
    dirty_before_start: "yes/no"
  allowed_today:
    - "Read project files"
    - "Edit MDX and test files"
    - "Run npm run lint"
    - "Run npm run test -- --runInBand"
  ask_before:
    - "Install or update packages"
    - "Change auth, billing, analytics, or deploy config"
    - "Push commits or create releases"
  never_allow:
    - "Print .env, tokens, cookies, or private keys"
    - "Run curl/wget for arbitrary URLs"
    - "Delete git history or force-push"
  proof_required:
    - "git diff reviewed"
    - "test or build command captured"
    - "rollback note written"
  owner_handoff:
    reviewer: "name"
    open_questions:
      - "Which production URL should be checked?"

最重要的是 allowed_today。它不是永久授权,而是今天完成任务并留下证据所需的最小权限集合。

用 Node 检测危险 permission pattern

手工检查很容易漏掉整体 Bash 自动允许,或者昨天留下的 Bash(npx *)。把下面保存为 scripts/audit-claude-permissions.mjs,在仓库根目录运行。

#!/usr/bin/env node
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

const repo = process.cwd();
const settingsFiles = [
  path.join(os.homedir(), ".claude", "settings.json"),
  path.join(repo, ".claude", "settings.json"),
  path.join(repo, ".claude", "settings.local.json"),
].filter((file) => fs.existsSync(file));

const riskyAllowRules = [
  { pattern: /^Bash$/i, severity: "high", reason: "all Bash commands are auto-allowed" },
  { pattern: /^PowerShell$/i, severity: "high", reason: "all PowerShell commands are auto-allowed" },
  { pattern: /^(Edit|Write)$/i, severity: "high", reason: "all file edits are auto-allowed" },
  { pattern: /^WebFetch$/i, severity: "medium", reason: "all web fetches are auto-allowed" },
  {
    pattern: /^Bash\((curl|wget|nc|ncat|ssh|scp|rsync)\b.*\)$/i,
    severity: "high",
    reason: "network or transfer command is auto-allowed",
  },
  {
    pattern: /^Bash\(.*\b(rm\s+-[^\)]*r|git\s+push|npm\s+install|pnpm\s+add|yarn\s+add|npx|docker\s+exec|devbox\s+run|mise\s+exec|terraform\s+apply|kubectl\s+apply)\b.*\)$/i,
    severity: "high",
    reason: "destructive or environment-changing command is auto-allowed",
  },
  {
    pattern: /^PowerShell\(.*\b(Remove-Item|Invoke-WebRequest|Invoke-RestMethod|Start-Process)\b.*\)$/i,
    severity: "high",
    reason: "risky PowerShell command is auto-allowed",
  },
];

const expectedDenyRules = [
  "Read(./.env)",
  "Read(./.env.*)",
  "Read(./secrets/**)",
  "Bash(curl *)",
  "Bash(wget *)",
];

const findings = [];

function add(file, severity, rule, reason) {
  findings.push({ file: path.relative(repo, file) || file, severity, rule, reason });
}

function readJson(file) {
  try {
    return JSON.parse(fs.readFileSync(file, "utf8"));
  } catch (error) {
    add(file, "high", "JSON", `cannot parse settings: ${error.message}`);
    return null;
  }
}

for (const file of settingsFiles) {
  const settings = readJson(file);
  if (!settings) continue;

  const permissions = settings.permissions ?? {};
  const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
  const ask = Array.isArray(permissions.ask) ? permissions.ask : [];
  const deny = Array.isArray(permissions.deny) ? permissions.deny : [];

  if (permissions.defaultMode === "bypassPermissions") {
    add(file, "high", "permissions.defaultMode", "session starts in bypassPermissions");
  }

  if (permissions.disableBypassPermissionsMode !== "disable") {
    add(file, "medium", "permissions.disableBypassPermissionsMode", "bypass mode is not disabled here");
  }

  if (settings.env?.CLAUDE_CODE_SKIP_PROMPT_HISTORY === "1") {
    add(file, "low", "CLAUDE_CODE_SKIP_PROMPT_HISTORY", "prompt history and transcripts are not written");
  }

  if (settings.env?.CLAUDE_CODE_SUBPROCESS_ENV_SCRUB !== "1") {
    add(file, "low", "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB", "subprocess credential scrubbing is not enabled here");
  }

  for (const rule of allow) {
    for (const risky of riskyAllowRules) {
      if (risky.pattern.test(rule)) add(file, risky.severity, rule, risky.reason);
    }
  }

  for (const required of expectedDenyRules) {
    if (!deny.includes(required)) add(file, "low", required, "consider adding this deny rule");
  }

  if (ask.length === 0) {
    add(file, "low", "permissions.ask", "no ask rules are defined");
  }

  for (const rule of [...allow, ...ask, ...deny]) {
    if (/:\*[^)]/.test(rule)) {
      add(file, "medium", rule, "the :* shorthand only behaves as a wildcard at the end of a pattern");
    }
  }
}

if (settingsFiles.length === 0) {
  console.log("No Claude Code settings files found in user or repo scope.");
} else if (findings.length === 0) {
  console.log("No risky Claude Code permission patterns found.");
} else {
  console.table(findings);
}

if (findings.some((finding) => finding.severity === "high")) {
  process.exitCode = 1;
}

PowerShell 中可以这样运行:

New-Item -ItemType Directory -Force -Path .\scripts | Out-Null
node .\scripts\audit-claude-permissions.mjs

这个脚本不是完整的安全引擎。它只是建立 review 入口。即使 Bash(curl *) 被阻止,一个被允许的 Bash 命令仍可能运行某个 Node 脚本并由脚本访问网络。需要更强边界时,请结合 sandbox 或 container。

四个具体用例

1. 文章和文档更新

MDX、README、翻译、截图替换通常风险较低。可以考虑允许 Read(./src/**)Edit(./site/src/content/**)Bash(npm run lint)Bash(npm run test *),同时把 npm installgit push 保留在 ask

陷阱是 CTA。Gumroad 链接、联系表单和免费 PDF URL 会影响收入和线索,所以公开 URL 检查应该进入完成条件。

2. 依赖更新

依赖更新会改变 package.json、lockfile、build 行为,有时还会改变安全状态。安装命令放在 ask 后面,并要求 Claude Code 写清原因、验证命令和 rollback 说明。

实用提示词是:“把更新候选限制为三个,用表格列出 breaking change 风险、验证命令和 rollback。”

3. 登录、计费和分析标签

登录、Stripe、Gumroad、广告标签、邮件目标、webhook 都应该需要人工批准。测试通过还不够。要看发送了哪些数据、失败时是否可能重复收费、日志里是否留下个人信息。

CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 会跳过把 prompt 历史和会话 transcript 写入磁盘。它适合敏感的临时会话,但会降低团队审计时追踪决策原因的能力。

4. 团队交接

交给别人时,不只留下权限,还要留下判断记录。好的交接说明会写清允许了什么、阻止了什么、已有证据是什么、还需要谁确认。

Handoff note:
- Allowed today: Edit content files, run lint, run unit tests.
- Asked before: package changes, deploy, payment links, analytics tags.
- Denied: .env reads, arbitrary curl/wget, recursive delete.
- Evidence: npm run lint passed, git diff reviewed.
- Remaining risk: production URL has not been checked after deploy.

常见坑

第一,自动允许太宽。BashPowerShellEditWebFetch 整体放进 allow 很方便,但边界会变得很弱。

第二,只用 Bash 模式过滤 URL。Bash(curl https://github.com *) 很脆弱,选项、重定向、变量和空格都可能变化。更清晰的做法是阻止 curlwget,必要时用 WebFetch(domain:example.com) 或 hook。

第三,忽略 wrapper 命令。文档说明 timeouttime 等 wrapper 有内置处理,但 npxdocker execdevbox runmise exec 这类开发运行器需要更谨慎。

第四,把 Read/Edit deny 当作操作系统级隔离。它适用于 Claude Code 内置工具和可识别的文件命令,但不一定阻止任意脚本在内部打开文件。

第一轮团队提示词

先请求审计,不要直接请求实现。

Before changing files, audit Claude Code permissions for this repository.
Return:
1. allow rules that are safe for today's task
2. ask rules that should stay behind human approval
3. deny rules that protect secrets, deploys, and destructive commands
4. environment variables that affect logs or subprocess secrets
5. the smallest task you can complete with proof and rollback notes
Do not edit files until the audit is summarized.

这个提示词本身不会强制安全,真正强制的是 settings 和 permissions UI。它的价值是让第一次修改前的边界可见。

保护收入路径

对一个把读者引向免费 PDF、Gumroad 产品或咨询服务的网站来说,权限审计不仅保护安全,也保护收入。还在熟悉基础流程时,可以先用 免费 cheatsheet。如果要把 CLAUDE.md、权限、hooks 和 MCP 作为团队流程整理好,Setup Guide 会更快。需要在真实仓库里规划落地,可以看 training 与咨询

实际试用后,最有价值的流程是在让 Claude Code 修改文件之前检查 git status/permissions.claude/settings.local.json 和审计脚本。把 Gumroad 链接和部署设置留在 ask 会让会话稍慢一点,但 review 更快,因为每个影响收入的改动都有可见的批准理由。

#claude-code #permissions #security #setup #workflow #claude-md
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。