Getting Started (Updated: 6/3/2026)

Claude Code Permission Audit Checklist: Review allow/deny and Risky Commands in 5 Minutes

Audit Claude Code allow/deny rules, risky commands, environment variables, and logs in five minutes each morning.

Claude Code Permission Audit Checklist: Review allow/deny and Risky Commands in 5 Minutes

Permission audits align what Claude Code can do today

The riskiest Claude Code setup is not always “let the agent do everything.” A more common real-world failure is quieter: yesterday you allowed Bash(git push *) or broad WebFetch, then today you open a production repository and forget that permission is still active.

This article gives you a five-minute morning audit for Claude Code permissions. allow means the tool can run without another prompt, ask means it must ask for confirmation, and deny means it is blocked. For a beginner, think of these rules as the boundary of the desk you give the agent.

As of June 3, 2026, Anthropic’s current docs say /permissions shows tool permissions and their source settings file. Rules are evaluated deny -> ask -> allow, with deny taking precedence. Verify the live details in the official Claude Code Permissions, Settings, and Environment variables docs before turning this into policy.

Use this after the first 30 minutes checklist and before deeper team setup with the CLAUDE.md starter template. The point is not bureaucracy. The point is to make useful work small enough to review.

The five-minute audit shape

Start with today’s risk, not with the JSON. Editing docs, updating dependencies, changing a form, and deploying production all deserve different boundaries.

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]

This sequence is enough for a daily check.

Place to checkWhat to verifyWarning sign
git status --shortWhether changes already existA diff exists and nobody knows who made it
/permissionsWhich file supplied each ruleBroad Bash, WebFetch, or Edit auto-allow
.claude/settings.jsonTeam-shared permissionsDeploy, billing, or payment links are in allow
.claude/settings.local.jsonPersonal temporary rulesYesterday’s exploration rule is still present
envSession history and subprocess behaviorAudit wants logs, but CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 is set

One documented nuance matters: a bare Bash deny removes the Bash tool from Claude’s context, while a scoped rule like Bash(rm *) leaves the tool available and blocks matching calls. A prompt or CLAUDE.md line saying “do not run this” is guidance, not enforcement. Use permission rules, permission modes, hooks, and sandboxing for actual boundaries.

A minimal repo-level settings example

Shared settings work better when risky actions are denied early and judgment-heavy actions stay behind ask. This is a practical .claude/settings.json starting point.

{
  "$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"
  }
}

Here, a harness simply means the agent’s working scaffold: Claude Code, settings, hooks, sandbox rules, and logging expectations together. The goal is not to trust the model harder. The goal is to give it a smaller, clearer workspace.

Bash(npm run test *) uses a wildcard. Current docs also recognize a trailing :* shorthand such as Bash(ls:*), but only at the end of a pattern. In team settings, the space form such as Bash(git push *) is easier to read and review.

Copy-paste repo audit checklist

Do not turn the audit into a long security document. Paste this into a PR, issue, or handoff note.

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?"

The most important line is allowed_today. It is not a permanent permission grant. It is the smallest permission set that lets today’s task finish with proof.

Detect risky permission patterns with Node

Manual review misses broad rules like Bash or yesterday’s Bash(npx *). Save this as scripts/audit-claude-permissions.mjs and run it from the repository root. It reads user, project, and local project settings.

#!/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;
}

Run it from PowerShell like this:

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

This script is not a complete security engine. It creates a review entrance. Even with Bash(curl *) denied, an allowed Bash command could still call a Node script that opens the network. For OS-level enforcement, pair permissions with sandboxing or containers.

Four concrete use cases

1. Articles and documentation

MDX, README files, translations, and screenshot replacements are usually lower risk. You can often allow Read(./src/**), Edit(./site/src/content/**), Bash(npm run lint), and Bash(npm run test *), while keeping Bash(npm install *) and Bash(git push *) behind ask.

The pitfall is the CTA. Gumroad links, contact forms, and free PDF URLs affect revenue and leads, so public URL checks should be part of the definition of done.

2. Dependency updates

Dependency work changes package.json, lockfiles, build behavior, and sometimes vulnerability posture. Keep package installs behind ask, then require Claude Code to write the reason, test command, and rollback note.

A good prompt is: “Limit this to three update candidates and show breaking-change risk, proof command, and rollback in a table.”

3. Auth, billing, and analytics

Login, Stripe, Gumroad, ad tags, email destinations, and webhooks should stay behind a human gate. Passing tests is not enough. Review which data is sent, whether failure can double-charge, and whether logs keep personal data.

CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 skips writing prompt history and session transcripts to disk. That can help for sensitive ephemeral sessions, but it hurts auditability when a team needs to understand why a choice was made.

4. Team handoff

When handing work to another person, leave the decision trail, not just the permissions. A useful handoff says what was allowed, what stayed blocked, what proof exists, and what still needs a human.

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.

Common pitfalls

First, broad auto-allow removes meaningful boundaries. Bash, PowerShell, Edit, or WebFetch in allow might feel efficient, but they leave too much to memory and review discipline.

Second, do not rely on Bash patterns for precise URL filtering. A rule like Bash(curl https://github.com *) is fragile because options, redirects, variables, and spacing can change the command. Deny curl and wget, then use WebFetch(domain:example.com) or a hook for explicit network policy.

Third, wrapper commands deserve suspicion. The docs describe built-in handling for wrappers such as timeout and time, but development runners such as npx, docker exec, devbox run, and mise exec are different. Bash(devbox run *) can become broader than intended.

Fourth, Read and Edit deny rules are not full operating-system isolation. They apply to Claude Code’s built-in tools and recognized file commands, but an arbitrary script may still read files internally. Use sandboxing or a container for stronger isolation.

Team prompt for the first turn

Start with an audit prompt, not an implementation prompt.

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.

The prompt does not enforce security by itself. Settings and the permission UI do that. Its value is making the boundary visible before edits begin.

Protect the revenue path

For a site that sends readers to a free PDF, Gumroad products, or implementation consulting, permission audits protect revenue as much as security. If you are still learning the basic workflow, start with the free cheat sheet. If you want CLAUDE.md, permissions, hooks, and MCP organized for a team, the Setup Guide is the faster route. For hands-on rollout planning, use the training and consulting page.

After trying this in practice, the highest-value routine was checking git status, /permissions, .claude/settings.local.json, and the audit script before asking Claude Code to edit. Keeping Gumroad links and deploy settings behind ask slowed the session slightly, but it made review faster because every revenue-affecting change had a visible approval reason.

#claude-code #permissions #security #setup #workflow #claude-md
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free cheatsheet, move to the setup guide or prompt pack when you hit a clear bottleneck, and use consultation only when you need workflow design help.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.