# Making Claude Code production-ready — permission design, hooks, CLAUDE.md, and security gates

> An implementer's guide to configuring Claude Code so it survives production. Close secrets fail-closed with permission deny, auto-fire scans with hooks, and pin your conventions in CLAUDE.md. Then the division of labor: the vertical risks config can't close — authorization/IDOR and RLS — are shut with an aegis fix loop plus design.

- Published: 2026-07-07
- Author: 友田 陽大
- Tags: Claude Code, AI駆動開発, バイブコーディング, セキュリティ, Supabase, RLS
- URL: https://tomodahinata.com/en/blog/claude-code-production-operations-security-gates-guide
- Category: Vibe coding: shipping AI-generated code safely
- Pillar guide: https://tomodahinata.com/en/blog/vibe-coding-what-is-tools-risks-production-guide

## Key points

- What makes Claude Code survive production isn't prompt craft — it's 'configuration': locking down permission, hooks, and CLAUDE.md up front.
- In permission, write deny first. Close secrets (.env / secrets) and outbound network calls fail-closed, and run the agent with least privilege.
- CLAUDE.md conventions are 'advisory'; hooks are 'deterministic.' For controls you must enforce deterministically, auto-fire a scan with hooks, and use a Stop hook to hold the turn until verification passes.
- permission, hooks, and CLAUDE.md automate horizontal controls, but the vertical risks of authorization/IDOR and RLS design don't close with configuration — hand a machine-readable fix plan via `aegis fix --format json`, and close the vertical with design.
- Division of labor: Spec-driven (what to build) → Claude Code configuration (safety of the local loop) → CI gate (the last line of defense) → vertical risk goes to design review / audit.

---

Let me state the conclusion first. **The key to making Claude Code survive production is not the agent's "smarts," nor prompt craft. It's locking down the 'environment configuration' the agent runs in — permission, hooks, CLAUDE.md, and verification gates — up front.** With the same model and the same prompt, the design of your `settings.json` and `CLAUDE.md` decides whether the output is code that's fit to ship or code that's waiting for an accident. That I was able to build — solo × Claude Code — a B2B SaaS that won a METI Minister's Award, a payments platform with zero production double-charges, and an AI platform for a broadcaster, owes far more to this discipline of "locking down the configuration first" than to raw generation speed.

This article writes concretely, from an implementer's perspective, about **how to configure Claude Code itself so that speed of generation becomes speed of shipping.** Methodology (what to build) and mechanical pass/fail in CI (the last line of defense) are left to existing articles; here I focus on the layer just before those — **making the local agent loop itself safe.**

---

## 1. The fork in production operation is "configuration," not "prompts"

Discussions of AI coding tend to concentrate on prompts, but what matters in production is outside them. I design development in three layers. Their roles differ, so you must not mix them.

| Layer | What it decides | Main tools | Detail |
|---|---|---|---|
| **① Methodology** | What to build (own the spec) | explore → plan → implement → verify | [Spec-driven development's production workflow](/blog/spec-driven-development-claude-code-ai-agent-production-workflow) |
| **② Agent configuration** (this article) | Make the local loop safe | permission, hooks, CLAUDE.md | This article |
| **③ CI gate** | The last line of defense (mechanical pass/fail, remote) | types, tests, static analysis, security | [The quality gate for AI-driven development](/blog/ai-driven-development-quality-gates-ci-type-safety-test-security) |

① is the methodology story of "let AI implement, but don't let it decide the spec." ③ is the CI story of "judge whether AI's output is fit for production by pass/fail, not by subjective feel." ② — this article — is the **Claude Code configuration itself** that sits between those two. If this is loose, both the spec you set in ① and the CI in ③ will generate endless rework in the local loop before them. Conversely, lock this down and the moment Claude edits, a security scan runs, secrets are kept out of reach, and any implementation that strays from the conventions is stopped on the spot — you shave off only the accidents without slowing generation.

Below, I dig into ② in the order of **permission → hooks → CLAUDE.md → handling vertical risk**, with actual `settings.json`.

---

## 2. Permission design: write deny first (a fail-closed allowlist)

Claude Code's permissions are expressed in three arrays — `allow` / `ask` / `deny` — under `permissions` in `settings.json`. The first thing to do here is **write the closing `deny` before the convenient `allow`.** The security principle is fail-closed — when in doubt, fall to the safe side.

```json
{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(curl *)"
    ],
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "Bash(npx @aegiskit/cli scan)"
    ]
  }
}
```

What this `deny` protects are the two things most prone to accidents when an AI agent touches them: **secrets and outbound network calls.** In my actual setup, `~/.claude/settings.json` flatly refuses reads of `.env*`, `*.pem`, `*.key`, `.aws/**`, `.ssh/**`, and `credentials*`. This isn't mere caution. Supabase's `service_role` key runs with PostgreSQL's `BYPASSRLS` privilege and **ignores RLS entirely.** In other words, the production defense boundary moves off code logic and onto "where the key is kept and who can read it." That's why the `deny` that keeps the agent from reading secrets is the first line you write.

Permission scopes have a clear precedence (strongest first).

```text
Managed（組織管理） > コマンドライン引数 > Local（.claude/settings.local.json）
  > Project（.claude/settings.json） > User（~/.claude/settings.json）
```

Put team-shared rules in Project's `settings.json` (committed to git), and personal, temporary loosening in Local (gitignored). Keep this separation and you prevent the accident of "someone's local convenience loosening" leaking to everyone.

Further, use different permission modes per phase.

- **Exploration is plan mode** — "read only" the code and constraints; don't let it write files. It's the wall that keeps it from implementing on its own while it's meant to be understanding the spec.
- **Non-interactive batches are narrowed with `--allowedTools`** — as in `claude -p "..." --allowedTools "Edit,Bash(git commit *)"`, pin the usable tools to a minimum precisely when it runs unattended.
- **auto mode** — a classifier model blocks only permission deviations and unknown infra operations, without stopping routine work. Suited to work where you trust the direction but want to skip approving every step.

In short: **the permissions you need, only in the phase you need them, minimized.** This is exactly the same discipline as human IAM design.

---

## 3. hooks: make the scan a "firing," not a "prayer"

This is the Claude-Code-specific setting that pays off the most. **Conventions written in CLAUDE.md are inherently "advisory," but hooks are "deterministic" and are guaranteed to run** — the official docs state this difference explicitly. Write "always run a scan after editing" in CLAUDE.md and, in a busy session, it gets skipped. Make it a hook and it can't be skipped.

Claude Code's hooks auto-run commands written in `settings.json` on specific workflow events (`PreToolUse` / `PostToolUse` / `Stop` / `SessionStart`, etc.). For security operations, the two I lay down are:

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "npx @aegiskit/cli scan" }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "npm run verify" }
        ]
      }
    ]
  }
}
```

- **`PostToolUse` (matcher: `Edit|Write`)** — fires `npx @aegiskit/cli scan` **immediately after** Claude edits or creates a file. If the generated code has an injection path, a leaked secret, or a missing control, it comes back on the spot with a source→sink trace. Detection runs inside the loop, without waiting for a human to notice.
- **`Stop` (turn-end hook)** — before Claude decides it's "done" and closes the turn, it runs the verification command and **holds the turn until it passes.** The Stop hook can block termination until verification passes (though, honestly, Claude Code overrides the hook and ends the turn after 8 consecutive blocks — it's not an infinite gate), and here I place `npm run verify` (my own gate bundling lint + test + build + scan).

These two pay off because they **insert verification "inside" the local agent loop.** If the CI quality gate ([The quality gate for AI-driven development](/blog/ai-driven-development-quality-gates-ci-type-safety-test-security)) is the "far last line of defense" acting on everyone's push, hooks are the "near fort" that fires the moment Claude writes. The near fort cuts rework with fast feedback; the far fort finally stops what slips through. Their roles differ, so you lay down both, not one. Double it up in CI with the same detection via `aegis ci` (a wrapper that pushes SARIF to GitHub code scanning and lets only high-confidence detections fail the build), and even if you ignore it locally, it catches at the end for certain.

> **Design philosophy**: the controls you want to enforce should be "fired," not "prayed aloud." A rule written in CLAUDE.md that went unheeded is a sign to convert it into a hook. The official guidance also says: "delete what Claude does correctly without being told, and turn what you must enforce deterministically into a hook."

---

## 4. CLAUDE.md: pin conventions to a "file," not a "conversation"

If hooks are "what you enforce deterministically," CLAUDE.md is "advice you make it read every time." Because Claude Code **always loads CLAUDE.md at the start of the conversation,** pinning conventions here means they never get buried and lost in the chat. This is the Claude-Code-side implementation of the "put specs and conventions into files" philosophy discussed in [Spec-driven development](/blog/spec-driven-development-claude-code-ai-agent-production-workflow) (details left to that article).

Placement is hierarchical. This portfolio repository itself is a live example of the practice.

```text
~/.claude/CLAUDE.md        全セッション共通の普遍原則（コード品質の10本柱・秘密情報の扱い）
./CLAUDE.md                リポジトリの憲法（スタック・ルート規約・アンチパターン）
./app/api/CLAUDE.md        ルートハンドラのローカル規約（Zod検証・秘密情報の境界）
./lib/CLAUDE.md            データ層の規約（I/Oの隔離・フレームワーク非依存）
```

One "constitution" at the root, a "local convention" per directory, and Claude reads the relevant convention before touching those files. Even in a monorepo or a large repository, the conventions take effect without polluting the context. What you should write in CLAUDE.md, on the security side, is around here:

- Always validate external input at the boundary with Zod (pointing to the canonical pattern in `app/api/*`)
- Secrets only in env / secrets manager. No hardcoding
- Default Supabase RLS to owner scope (`USING (auth.uid() = user_id)`); use `USING (true)` **only when that table is genuinely meant to be public**
- Don't use `service_role` on any path reachable from the browser

But — **make CLAUDE.md long and it gets ignored.** As the official docs repeatedly warn, a bloated CLAUDE.md buries the crucial rules in noise. Ask of each line, "If I delete this, will Claude get it wrong?" and if the answer is No, delete it. And what you "must enforce deterministically" should not keep living in CLAUDE.md — **promote it to a hook.** The key is to correctly choose, per convention, the strength stage: CLAUDE.md (advice) → hooks (deterministic) → CI (mechanical pass/fail). For a machine-decidable rule like "no `any`," dropping it with `tsc` and Lint is more reliable than praying to it in CLAUDE.md.

---

## 5. The "vertical risk" configuration can't close: the aegis fix --format json loop

permission, hooks, and CLAUDE.md so far are powerful, but **there's an honest boundary to what they can close.** What they can automate and enforce is "horizontal controls" — the ones that apply uniformly across the app: headers/CSP, rate limiting, input validation, CSRF, secret hygiene. On the other hand, **vertical risks that depend on "who owns what"** don't close with configuration.

| | Horizontal controls | Vertical risks |
|---|---|---|
| Example | headers/CSP, rate limiting, input validation, secret hygiene | authorization/IDOR/BOLA, RLS design mistakes, cross-tenant leakage, business logic |
| Nature | uniform across the app | depends on app-specific ownership |
| With Claude Code config | can be automated/enforced by permission, hooks, CLAUDE.md | doesn't close with config (a design decision) |
| Aegis scan | detects the gap and proposes a safe fix | **detect and warn only** (the fix is your design) |

What makes vertical risk nasty is that **the attack is a "legitimate request with correct authentication and correct format."** A `GET /api/orders/123` pointing at someone else's ID gets caught by neither the WAF nor headers. This is the most frequent class, the one that has squatted at #1 (Broken Object Level Authorization) in the OWASP API Security Top 10 since 2019. The hole of "authenticates but doesn't authorize" — lets the login through, but doesn't check whether that row belongs to that person — is frequently baked into AI-generated Next.js × Supabase code.

How often? **In a primary study of 1,000 public Supabase apps, about 9.2% of apps with RLS had a policy that "authenticates but doesn't scope rows to the owner"** (a lower bound, since the sample is public repositories; owner-unscoped ≠ instantly vulnerable, needs confirmation). The method and all figures are in the [field study of RLS in Supabase apps](/blog/supabase-rls-security-field-study), and the vulnerable→fixed real code is in [the difference between "authenticated" and "authorized" in RLS](/blog/supabase-rls-authenticated-vs-authorized-owner-scope-guide).

So how do you fold this vertical risk into the Claude Code loop? **Don't close it with configuration — run the loop of "detect → machine-readable fix plan → paste into the agent → human judges the vertical."**

```text
1. scan     npx @aegiskit/cli scan
            └ 水平の欠落 + 縦のリスクの「疑い」を検出
   ↓
2. fix      npx @aegiskit/cli fix --format json
            └ 機械可読（プレーンJSON）の修正計画を出力
   ↓
3. 貼る     その JSON を Claude Code に渡す
            └ 水平の統制は安全な差分として適用させる
   ↓
4. 縦は人間 認可/IDOR・RLS設計は「設計判断」としてあなたに返る
            └ ここは自動修正しない（できない）
```

`aegis fix --format json` is the crux. Because the fix plan is plain JSON, you can paste it straight into Claude Code (or Cursor, or Codex) and have it apply the changes. Only "provably safe transformations" of horizontal controls get applied, while the vertical risks — whether `USING (true)` really intends to be public, whether the `service_role` path has an ownership check, whether a `SECURITY DEFINER` function's `search_path` is pinned — are not auto-fixed and return to the human as design decisions.

It's free OSS, so trying it now is fastest. With `npx @aegiskit/cli scan` (no install, no config) you can statically analyze your current project ([Aegis](/aegis)). But to be honest, **scan does not claim "complete protection."** A clean result means "you haven't stepped on the common traps," not "this code is safe." Automate the horizontal controls, keep vertical risk to detect-and-warn, and let closing it be your design — not fudging that boundary is what integrity looks like for a security tool. From the buyer's perspective, [AI-generated code vulnerability assessment](/blog/ai-generated-code-vulnerability-assessment-vibe-coding-security-guide), which frames the assessment approach, and [production hardening](/blog/vibe-coding-ai-generated-code-production-hardening-guide) will both help with the design too.

---

## 6. The three layers on one page: local loop → CI → audit

Finally, connect ②'s agent configuration onto one page with ① methodology and ③ CI. It's efficient to stack defenses "nearest first."

```text
[Claude Code ローカルループ]   ← 本記事（②）
  permission(deny先行) + hooks(scan自動発火/Stopゲート) + CLAUDE.md(規約固定)
        │  近い砦：Claudeが書いた瞬間に検出。手戻りを削る
        ▼
[CI ゲート]                     ← 品質ゲート（③）
  aegis ci(SARIF) + 型 + テスト + 静的解析 + 秘密情報スキャン
        │  遠い最後の砦：高信頼の検出だけがビルドを止める
        ▼
[縦のリスク]                    ← 設計判断（設定でもCIでも閉じない）
  認可/IDOR・RLS設計・テナント分離 → 設計レビュー / 監査で塞ぐ
```

Crush fast at the near fort (hooks), stop what slips through at the far fort (CI), and close the vertical risk that still remains with design. **The source of speed is not clever prompts but locking down this configuration up front.** Because the configuration is locked down, generation speed becomes shipping speed. That I could keep shipping production-quality products solo × generative AI is thanks to this discipline.

"I want to build fast with AI, but I don't want to sacrifice production security" — that dual goal is decided not by model choice but by the design of your `settings.json` and `CLAUDE.md`. I take on building the verification-first local loop and CI gate, and design-reviewing the vertical risks configuration can't close (authorization/IDOR, RLS design) — including introducing them into existing projects.

---

## Summary: the agent's configuration decides production quality

To make Claude Code survive production, the points to nail are:

1. **The fork is configuration, not prompts** — lock down permission, hooks, and CLAUDE.md first (layer ②).
2. **In permission, write deny first** — close secrets and outbound network calls fail-closed, and run with least privilege. The `service_role` boundary is where the key lives.
3. **Fire the scan with hooks** — don't pray with CLAUDE.md (advice); auto-detect on PostToolUse, and hold on Stop until verification passes.
4. **CLAUDE.md pins conventions, but don't let it bloat** — promote what you must enforce deterministically to a hook, and what's machine-decidable to CI.
5. **Vertical risk doesn't close with configuration** — hand a machine-readable plan to the agent via `aegis fix --format json`, and let a human close authorization/IDOR and RLS design with design.
