Why Your AI Coding Assistant Is a Security Vulnerability (And How to Fix It)
Why Your AI Coding Assistant Is a Security Vulnerability (And How to Fix It)
Last week, I realized I'd been letting an AI assistant execute arbitrary commands on my machine for months. It had access to my AWS credentials, SSH keys, private GitHub repos, and anything else sitting in my home directory. The scary part? I never once thought about it because the tool was useful and nothing bad had happened yet.
That's precisely the kind of thinking that gets production databases wiped.
The Comfort of Convenience is a Security Trap
Here's the uncomfortable truth: most AI coding agents today operate with the same level of access you'd give to a senior infrastructure engineer on day one. We'd never hand a new contractor unrestricted SSH access to every server, but we hand an LLM the equivalent without blinking.
The problem isn't malice. It's negligence. These tools are so helpful that we stop thinking about what they're actually capable of doing. A single prompt injection in a cloned repository, a misaligned objective, or even a seemingly innocent command could exfiltrate credentials to an attacker-controlled endpoint. You'd never see it happen.
The good news? Tools like Claude Code actually do include robust security controls. Most of us just never bothered to turn them on.
Understanding the Default Risk
By default, many AI coding agents operate in "auto" mode. This means they execute commands without asking you unless they hit something explicitly blocked or flagged for review. Sounds efficient, right? Here's what that actually means in practice:
If your deny list is empty and your default mode is auto, your AI assistant can freely run:
curlto any endpointwgetto download filessshto connect to remote serversncto establish arbitrary network connections- Read every
.envfile in your project - Access
~/.aws/credentials,~/.ssh/keys,~/.gnupgdirectories - Push code directly to your repositories
All of this happens silently. No notification. No prompt. Just gone.
Building a Three-Layer Security Model
The reality is that you need granular control. Not everything your AI assistant does should require approval, but certain actions absolutely should. Here's how to think about it:
Layer 1: The Hard Deny
Start with an explicit deny list for anything credential-related:
{
"deny": [
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Read(~/.gnupg/**)",
"Read(~/.azure/**)",
"Read(~/.kube/**)",
"Read(~/.npmrc)",
"Read(~/.git-credentials)",
"Read(*.env)",
"Read(.env.*)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(nc *)",
"Bash(ssh *)"
]
}
Think of this as your firewall. Nothing on this list gets through, ever. Even if the AI agent somehow decides it needs to exfiltrate credentials, the tool itself blocks it at the system level.
Layer 2: The Review Checkpoint
Use an "ask" list for actions that are reversible but dangerous:
{
"ask": [
"Bash(git push *)",
"Bash(git commit *)",
"Bash(git merge *)",
"Bash(git reset *)",
"Bash(npm publish *)",
"Bash(docker push *)"
]
}
These are things the AI might legitimately need to do—pushing code, publishing packages, deploying containers—but they're also actions you want to review before they happen. A prompt appears before execution. You get the efficiency of the agent without the risk of irreversible mistakes.
Layer 3: The Fast Lane
Explicitly allow safe operations:
{
"allow": [
"Bash(npm run *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(ls *)",
"Read(src/**)",
"Read(tests/**)"
]
}
These commands don't require interaction. The agent can run them freely because they either don't modify state or the modifications are safe.
The Default Mode Decision
Here's where most people get it wrong. After you've set up your deny and ask lists, you still need to choose a default mode for everything else. Three options:
auto- Everything not explicitly blocked runs silently. Fastest, but requires confidence in your deny list being comprehensive.acceptEdits- The agent can read freely and suggest edits, but must ask before executing any bash commands outside your allow list. Balanced.ask- The agent asks before anything potentially risky. Slowest but safest for paranoid configurations.
Most teams are better served with acceptEdits as their default. It preserves productivity while forcing intentional decision-making on operational actions.
A Practical Configuration Example
Here's what a real, production-ready configuration looks like:
{
"permissions": {
"deny": [
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Read(~/.gnupg/**)",
"Read(~/.azure/**)",
"Read(~/.kube/**)",
"Read(~/.npmrc)",
"Read(~/.git-credentials)",
"Read(~/.config/gh/**)",
"Read(*.env)",
"Read(.env.*)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(nc *)",
"Bash(ssh *)"
],
"ask": [
"Bash(git push *)",
"Bash(git commit *)",
"Bash(npm publish *)",
"Bash(docker push *)"
],
"allow": [
"Bash(npm run *)",
"Bash(npm install *)",
"Bash(npm test *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)"
],
"defaultMode": "acceptEdits"
}
}
This configuration gives your AI assistant enough rope to be productive without enough to hang you. It can help with testing, linting, and understanding your codebase. But it can't steal credentials, push code without approval, or connect to arbitrary servers.
Two Mindsets for Different Scenarios
I personally use two shell aliases:
# Default: balanced security
alias cc="claude --permission-mode auto"
# Debug mode: trust the agent completely (use sparingly)
alias ccd="claude --permission-mode dangerously-skip-permissions"
I use cc for 95% of my work. The ccd alias exists for those debugging nightmares where I genuinely need everything to move fast, but I'm deliberately choosing to trust the agent in that moment. Every time I type ccd, I'm making a conscious risk decision.
The Industry-Wide Problem
This isn't unique to Claude Code. Every AI coding tool that executes shell commands has this same surface area. GitHub Copilot extensions, Devin, Cursor, ChatGPT with code execution—they all present this tradeoff between convenience and security.
The difference is that Claude Code actually gives you the granular controls to address it. Most tools expect you to either trust them completely or not use them at all. There's rarely a middle ground.
What This Means for Your Infrastructure
If you're deploying AI coding assistants in a team environment, this becomes even more critical. You can't rely on individual developers to get this right. Most won't think about it until something breaks.
Consider:
- Requiring deny lists in your team's shared configurations
- Blocking credential files at the filesystem level when possible
- Using temporary credentials or session tokens instead of long-lived keys
- Implementing network-level controls to prevent unexpected data exfiltration
- Educating your team on the distinction between productivity and risk tolerance
The Bottom Line
AI coding assistants are genuinely powerful and worth using. But they're not a free lunch. The security controls exist—you just have to use them.
Start today. Spend 15 minutes setting up your deny list for credentials. Add an ask list for deployment actions. Choose a sensible default mode. Then you can actually relax knowing that your AI assistant is productive and constrained.
The comfortable realization that "nothing bad has happened yet" should never be your security strategy.