The problem
A development team writes the following prompt instruction in their project CLAUDE.md: "never print the contents of .env files or include API keys in your responses." It seems like a reasonable safeguard. But it is a defense that lives entirely within prompt text — and a prompt instruction is not an access control boundary. If the model inspects a .env file during a legitimate debugging task (such as verifying why an environment variable is not loading), the secret has already entered the context window token stream. From that moment on, preventing secret leakage depends entirely on probabilistic adherence — zero deterministic security guarantees have been established.
This lesson explores the fundamental distinction between systems that are truly secure and systems that merely look secure: real security lives outside the prompt, enforced via deterministic access controls, least-privilege scoping, and permission boundaries that do not depend on the model "behaving well."
Secrets are never protected by prompt instructions
The architectural rule is simple to state, yet routinely violated under delivery pressure: a secret (API keys, OAuth tokens, passwords, database connection strings) must never be accessible to any runtime process that could emit it in an output payload unless strictly required. The true defenses are structural:
- Secrets reside in environment variables or dedicated secret management vaults, never hardcoded in source code or versioned configurations.
- Filesystem inspection tools are explicitly restricted from traversing secret paths (
.env, private SSH keys, TLS certificates) when tasks do not require them. - When an agent must authenticate an outbound API call, secrets are injected by the execution harness outside the model's reasoning loop — the model decides that a specific API call should occur, the harness resolves and injects the credential headers, and the model never views the raw secret value in its token context.
A directive in CLAUDE.md asking the model not to leak credentials reduces accidental disclosure in standard cases. It is not, and must never be treated as, an architectural security control.
Least privilege across tools, MCP, and Claude Code
The principle of least privilege — granting each system component strictly the minimum permissions necessary to perform its task — applies across three critical layers:
- Claude Code tools: A custom command or Skill that only parses files should never have
Bashenabled. A utility that formats text should not possessWritepermissions outside its designated target directory. - MCP servers: An MCP server providing read-only access to a database should never expose write or mutation tools unless mutating state is an explicit, audited requirement.
- Claude Code permissions: Project
settings.jsonconfigurations should explicitly deny high-risk shell commands (such asrm -rforgit push --force) regardless of what prompt instructions state — enforcing in configuration what cannot rely on probabilistic compliance.
The most prevalent architectural antipattern is granting overly broad permissions "so development doesn't get blocked later." Granting unneeded permissions inflates the blast radius of any model reasoning failure without providing present utility.
Built-in tools carry distinct risk profiles
Claude Code's built-in tools exhibit fundamentally different risk boundaries. Choosing between them is not about finding any tool that "works" — multiple tools can accomplish a given task — but selecting the tool with the smallest possible blast radius:
Read: Safe for non-destructive inspection prior to modifying target files.Edit: Requires exact string matching against target blocks; full-file replacements (Write) should be reserved only when structured replacement is demonstrably safer than targeted diffs.Grep: Searches content within files with low operational risk.Glob: Identifies filesystem paths matching patterns without reading file contents into context.Bash: Crosses a broad execution boundary with immense operational power, requiring strict parameter validation, permission controls, and sandbox isolation.
Selecting Bash for a search task that Grep or Read solves natively is not just inefficient — it unnecessarily expands the system attack surface.
Put it into practice
In this lesson's lab, you will implement a permission boundary auditor: given an allowlist of tools for a command or subagent, the script flags least-privilege violations (such as Bash enabled on a read-only task) and simulates out-of-band secret injection where credentials authenticate external APIs without ever surfacing in the model's context stream.