Library · Power-user techniques

Permissions and security

Builder45 minUpdated: October 2026
45 of 105 in the library

Module: 9. Advanced features | Time: ~25 min theory + 20 min practice

The gist

An agent (an autonomous worker) holding the keys to every door is a disaster waiting to happen. Security in AI development isn't bureaucracy, it's architecture: well-designed boundaries protect you, your client and your reputation.

Terms in this lesson: permission (the right to perform an action), API (application programming interface), agent (an autonomous worker), MCP (Model Context Protocol), bash (the terminal's command language), git (a version control system), hook (a script that reacts to an event), sandbox (an isolated test environment), token (a unit of text for AI), settings.json (the project settings file), workflow (a work process).

Key concepts

  • .env is the only place for secrets
  • Claude Code's permission modes and when to use each one
  • Storing secrets in production (the live environment)
  • Limiting subagent access to protect against accidental damage

Theory

.env: where secrets live

🎨 Picture this: .env is the safe in the back room. Your code is the store window. You don't keep cash in the window. Secrets go in the safe, behind a steel door.

The .env file is an isolated store for sensitive data: API keys, tokens, passwords. It lives in the root of your project and never goes into the repository (where your code is stored).

Example .env structure:

Code
OPENAI_API_KEY=sk-proj-abc123...
ANTHROPIC_API_KEY=sk-ant-...
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
SLACK_BOT_TOKEN=xoxb-...
STRIPE_SECRET_KEY=sk_live_...

In your code, you access these values through environment variables:

python
import os
api_key = os.environ.get("OPENAI_API_KEY")

or in Node.js:

javascript
const apiKey = process.env.OPENAI_API_KEY;

Why you should NEVER hardcode keys in your code:

GitHub and other platforms actively scan public repositories for patterns like sk-ant-, sk-proj-, AIza, Bearer . If a key ends up in a commit, it's considered compromised. Attackers use automated scanners and can start spending your money minutes after you push.

Even if the repository is private, leaks happen: someone accidentally changes the visibility, adds a collaborator, makes a fork. There's one rule: a secret that has ever been in Git history is considered public.

.gitignore: the first line of defense

🎨 Picture this: .gitignore is the "don't pack" list when you move. The boxes go on the truck (the public repository), but the safe with your documents doesn't. Forget it, and the safe rides along with everything else, and everyone at the new place sees what's inside.

Before your first git commit, always add .env to .gitignore:

Code
# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key
node_modules/

To check, run git status before committing. The .env file should not appear in the list of changes. If it does, stop.


Claude Code permission modes

🎨 Picture this: Permission modes are like levels of trust in a contractor. Plan mode: they only look around and draw a blueprint. Manual: they clear every nail with you. Accept edits: they make the changes themselves, but come ask about anything else. Auto: they work on their own, with a separate inspector keeping an eye on them. Bypass: keys to the whole building, basement included.

Claude Code gives you a flexible system for controlling what the agent can do. Which mode to pick depends on how much you trust the task. You switch modes with Shift+Tab right in the session, or with the --permission-mode flag at launch. As of October 2026 there are six modes; the list changes, so check the documentation and the What's current page.

Plan mode (planning only) The agent analyzes the task and builds a plan, but doesn't take any actions. Ideal for getting to know a new codebase or before a complex operation: look at the plan first, then approve it.

Code
claude --permission-mode plan "Add an authentication system"

Manual (called default in the config): ask before acting Only reading happens without asking. File edits, commands and network requests need your confirmation. Use it when you're working with someone else's code, with production configuration, or when the task is unusual. Slower, but safer.

Accept edits (acceptEdits): automatic edits The agent edits files and runs simple file commands on its own (create a folder, move, copy) and asks about everything else. Good for well-defined tasks in your own project, where you understand the consequences.

Auto (auto): a reviewing model Instead of you, a separate classifier model reviews every action: it blocks anything that goes beyond your request or looks like a command from untrusted text. Starting with Claude Code version 2.1.283, this is the default starting mode in the terminal and VS Code (if the mode isn't available on your plan or model, the session starts in Manual). Remember: auto reduces the number of questions, but it doesn't guarantee safety. For sensitive operations, check things yourself.

Don't ask (dontAsk) Only reading and pre-approved tools are allowed; everything else is declined without asking. Good for CI and scripts, where there's no person around.

Bypass permissions (bypassPermissions, full trust) The agent can do anything, including running arbitrary commands (--dangerously-skip-permissions turns it on). Never use it in a production environment or on your main computer. Only in isolated containers and virtual machines where losing data doesn't matter. Deny rules (deny) still work even here.


Limiting subagent access

🎨 Picture this: A subagent with limited access is like an intern who gets a reader's badge but no signing authority. They study the documents and find problems, but they can't change anything. Safe by design.

Subagents are helper agents the main agent brings in for subtasks (see the Subagents lesson). You can control what each subagent can do: in the subagent's file, the tools field allows only the listed tools, and disallowedTools forbids specific ones.

Read-only The subagent can read files but not change them. Good for analyst agents: "look at the code and find problems," "read the logs and summarize them."

MCPs only (external tools) The subagent works only through external integrations (Notion, Slack, Google Docs) and doesn't touch local files. Useful for agents that send notifications or update external systems.

Bash only The subagent can run commands but can't edit files directly. For deployment, builds and testing.

A limited agent won't break what it isn't supposed to touch. This is the principle of least privilege, a cornerstone of information security.


Storing secrets in production

For local development, you use .env. In production (the live environment where your service runs), you need dedicated secret stores:

Cloudflare Workers Secrets

bash
wrangler secret put ANTHROPIC_API_KEY
# Enter the value: it gets encrypted and stored in Cloudflare

Once saved, the secret can't be viewed. Only the Worker can use it.

Vercel Environment Variables Through the Vercel dashboard or the CLI:

bash
vercel env add ANTHROPIC_API_KEY production

Split into development, preview and production: different keys for different environments.

The principle: different environments, different keys. Your test key should never be the same as your production key.


Rate limiting and cost control

🎨 Picture this: Rate limiting (a cap on how often requests can be made) is like an automatic shutoff valve on your water line. Without it, a faucet can run all night and you'd never know. With a limit: spend $20 in a day and the valve closes automatically until you open it yourself.

API requests cost money. Without rate limiting, one bug in your code can drain your budget overnight.

Ways to protect yourself:

  • Agent-level limit: at most N requests per minute/hour
  • Hard cap: if more than $X is spent in a day, the agent stops and reports it
  • Monitoring: a daily report of tokens and money spent
  • Test with cheap models: develop on Haiku, and deploy on whatever model the quality requires (current prices and versions: What's current)

Practice

Assignment: Set up a secure environment for a practice project

  1. Create a new folder my-agent-project
  2. Initialize Git: git init
  3. Create a .gitignore with the lines .env and node_modules/
  4. Create a .env with test (fake) variables:
    Code
    ANTHROPIC_API_KEY=sk-ant-test-placeholder
    APP_SECRET=my-super-secret-value
  5. Create a main.py that reads the key from the environment (doesn't hardcode it)
  6. Run git add . and git status, and make sure .env is NOT on the list
  7. Make your first commit: git commit -m "Initial setup with secure .env"
  8. Check that git show HEAD doesn't contain any secret values

Bonus: Try launching Claude Code with different permission modes (claude --permission-mode plan, claude --permission-mode default) and get a feel for how the agent behaves differently. Also add a rule that blocks reading secrets: in .claude/settings.json, under permissions → deny, add Read(./.env).


Claude Code permission levels: quick reference

Level What it can do What it CAN'T do When to use it
Yes (once) One specific action Repeat it without asking An unfamiliar operation, the first time
Yes, and don't ask again Actions of this type from then on without asking: for file edits until the end of the session, for Bash commands and domains permanently for this repository Go beyond what you approved Routine work in a trusted project (git commit, npm test)
Plan Analyze and plan Change files, run code A first look at a new codebase
Auto Work without asking, under a reviewing model's watch Guarantee safety Long tasks in a project you trust
Bypass Absolutely everything — Only an isolated container or VM, NEVER in production

Common mistakes

1. An API key ended up in a git commit (a saved change) Even if you delete the key in the next commit, it stays in the history forever. The fix: git-secrets + a pre-commit hook (a script that runs before each commit).

bash
# Install git-secrets and add the check
brew install git-secrets
git secrets --install
git secrets --register-aws  # for AWS keys

2. No .gitignore before the first commit Create .gitignore BEFORE git init or right after. If .env has already been committed, removing it from the history is harder than preventing it.

3. One key for all environments Your test and production keys MUST be different. A bug in dev code shouldn't spend your production budget.

4. Forgetting .env.example Create a .env.example with empty values, so other developers (or you, six months from now) know which variables are needed:

Code
OPENAI_API_KEY=
DATABASE_URL=
SLACK_BOT_TOKEN=

5. Bypass permissions on a production project Full trust (--dangerously-skip-permissions) = no protection at all. One wrong rm -rf and the project is gone.


Tools and resources

  • Claude Code Permissions: official documentation, code.claude.com/docs/en/permissions
  • Claude Code Security: the security model, code.claude.com/docs/en/security
  • python-dotenv / dotenv (Node.js): loads .env into environment variables
  • git-secrets: a CLI tool that scans commits for secrets
  • Cloudflare Workers Secrets: production secret store for Workers
  • Vercel Environment Variables: production secret store for Vercel deployments
  • GitHub Secret Scanning: automatic detection of leaked keys (works automatically for public repositories; for private ones it depends on your plan and settings)

Key takeaways

A secret that ends up in Git history is considered public, even if the repository is private. A limited agent beats a broken production system: least privilege is the foundation of secure architecture. .gitignore goes in before the first commit, not after. After is too late.


  • API keys and .env: how to get and manage API keys for different services
  • Hooks and Hooks LIVE: automatic security checks with pre-commit and pre-tool-use hooks (you can block an accidental commit of a secret)

What's next

→ Context management: advanced techniques

The mark stays in this browser only and is never sent anywhere. My progress