Getting Started (업데이트: 2026. 6. 3.)

Claude Code 권한 감사 체크리스트: allow/deny와 위험 명령을 5분 안에 점검

Claude Code의 allow/deny, 위험 명령, 환경 변수, 로그 정책을 매일 아침 5분 안에 점검하는 체크리스트.

Claude Code 권한 감사 체크리스트: allow/deny와 위험 명령을 5분 안에 점검

권한 감사는 오늘 Claude Code가 할 수 있는 일을 정하는 절차입니다

가장 위험한 설정이 항상 “전부 허용”인 것은 아닙니다. 실제로는 더 조용한 문제가 자주 생깁니다. 어제 Bash(git push *)나 넓은 WebFetch를 허용했고, 오늘 프로덕션 저장소를 열면서 그 권한이 남아 있다는 사실을 잊는 경우입니다.

이 글은 매일 아침 5분 안에 할 수 있는 Claude Code 권한 감사 절차입니다. allow는 추가 확인 없이 실행해도 된다는 뜻이고, ask는 매번 확인한다는 뜻이며, deny는 차단한다는 뜻입니다. 처음 쓰는 사람에게는 에이전트에게 내어주는 작업 공간의 경계라고 설명하면 이해하기 쉽습니다.

2026년 6월 3일 기준 공식 문서에 따르면 /permissions에서 도구 권한과 해당 권한이 온 설정 파일을 확인할 수 있습니다. 규칙은 deny -> ask -> allow 순서로 평가되고 deny가 우선합니다. 정책으로 쓰기 전에는 Claude Code Permissions, Settings, Environment variables를 확인하세요.

이 절차는 처음 30분 체크리스트 이후, 팀 설정을 CLAUDE.md 스타터 템플릿로 정리하기 전에 두면 좋습니다. 목적은 절차를 늘리는 것이 아니라, 리뷰 가능한 크기로 작업을 줄이는 것입니다.

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각 규칙의 출처Bash, WebFetch, Edit 전체가 allow에 있음
.claude/settings.json팀 공유 권한배포, 결제, 상품 링크가 자동 허용됨
.claude/settings.local.json개인 임시 권한어제 탐색용 규칙이 남아 있음
env세션 기록과 하위 프로세스감사 로그가 필요한데 CLAUDE_CODE_SKIP_PROMPT_HISTORY=1

중요한 세부 사항이 있습니다. denyBash만 쓰면 Bash 도구 자체가 Claude의 컨텍스트에서 사라집니다. 반면 Bash(rm *)처럼 범위를 좁히면 도구는 남고 해당 호출만 차단됩니다. CLAUDE.md에 “실행하지 말라”고 쓰는 것은 안내일 뿐이고, 실제 경계는 permissions, 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는 에이전트의 발판입니다. Claude Code, settings, hooks, sandbox, 로그 정책을 묶어 에이전트가 안전하게 움직일 구조를 만드는 것입니다. 모델을 더 믿는 것이 아니라 작업 공간을 더 좁고 명확하게 만드는 접근입니다.

Bash(npm run test *)는 와일드카드를 사용합니다. 현재 문서는 Bash(ls:*)처럼 끝에 붙는 :*도 인식한다고 설명하지만, 패턴 끝에서만 의미가 있습니다. 팀 설정에서는 Bash(git push *)처럼 공백을 둔 형태가 읽기 쉽습니다.

복사해서 쓰는 저장소 감사 체크리스트

감사를 긴 보안 문서로 만들 필요는 없습니다. 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

이 스크립트는 완전한 보안 엔진이 아닙니다. 리뷰의 입구를 만드는 도구입니다. 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 pushask에 남겨둡니다.

함정은 CTA입니다. Gumroad 링크, 문의 폼, 무료 PDF URL은 매출과 리드에 영향을 줍니다. 따라서 공개 URL 확인이 완료 조건에 들어가야 합니다.

2. 의존성 업데이트

의존성 작업은 package.json, lockfile, build 동작, 보안 상태까지 바꿀 수 있습니다. 설치 명령은 ask 뒤에 두고, 이유와 테스트 명령, 롤백 메모를 요구합니다.

좋은 프롬프트는 다음과 같습니다. “업데이트 후보를 세 개로 줄이고 breaking change 위험, 검증 명령, 롤백을 표로 정리해 주세요.”

3. 인증, 결제, 분석 태그

로그인, Stripe, Gumroad, 광고 태그, 이메일 대상, webhook은 사람 승인 뒤에 둡니다. 테스트 통과만으로 충분하지 않습니다. 어떤 데이터가 전송되는지, 실패 시 중복 결제가 가능한지, 로그에 개인정보가 남는지 확인해야 합니다.

CLAUDE_CODE_SKIP_PROMPT_HISTORY=1은 프롬프트 기록과 세션 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.

자주 생기는 함정

첫째, 자동 허용을 너무 넓히는 것입니다. Bash, PowerShell, Edit, WebFetch 전체를 allow에 두면 편하지만 경계가 거의 사라집니다.

둘째, Bash 패턴만으로 URL을 정확히 제한하려는 것입니다. Bash(curl https://github.com *)는 옵션, redirect, 변수, 공백 차이로 쉽게 흔들립니다. curlwget은 막고 WebFetch(domain:example.com)이나 hook을 쓰는 편이 명확합니다.

셋째, wrapper 명령입니다. 문서는 timeout, time 같은 일부 wrapper 처리를 설명하지만 npx, docker exec, devbox run, mise exec 같은 개발 runner는 주의해야 합니다.

넷째, 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와 권한 UI가 합니다. 다만 첫 수정 전에 경계를 눈에 보이게 만든다는 점에서 효과가 있습니다.

매출 경로 보호하기

무료 PDF, Gumroad 상품, 컨설팅으로 이어지는 사이트에서는 권한 감사가 보안뿐 아니라 매출도 보호합니다. 기본 흐름을 익히는 단계라면 무료 치트시트부터 시작하세요. CLAUDE.md, 권한, hooks, MCP를 팀 기준으로 정리하려면 Setup Guide가 빠릅니다. 실제 저장소 도입을 함께 설계하려면 교육 및 컨설팅을 참고하세요.

실제로 적용해 보니 가장 효과가 컸던 루틴은 Claude Code에게 수정을 요청하기 전에 git status, /permissions, .claude/settings.local.json, 감사 스크립트를 확인하는 것이었습니다. Gumroad 링크와 배포 설정을 ask에 남겨 두면 세션은 조금 느려지지만, 매출에 영향을 주는 변경마다 승인 이유가 남기 때문에 리뷰가 더 빨라졌습니다.

#claude-code #permissions #security #setup #workflow #claude-md
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.