Coding agents with unrestricted shell access are remote unattended engineers with root-adjacent habits. Default-deny filesystem and command allowlists turn “agent helpfulness” into something you can defend in a security review.
⚡ TL;DR: Allowlist write paths (
src/**,tests/**) and commands (pnpm test,tsc,git status). Denygit push --force,npm publish,curl|bash, cloud destructive APIs, and reads of.env. Elevation requires a human token. Implement as a policy check before every tool call — see Claude Code Hooks and LLM Coding Agents on AWS: Safe Tool Sandboxes.
Policy object evaluated on every tool call
export type ToolRequest =
| { type: "read"; path: string }
| { type: "write"; path: string }
| { type: "shell"; argv: string[] };
const WRITE_ALLOW = [/^src\//, /^tests\//, /^packages\/[^/]+\/src\//];
const READ_DENY = [/^\.env/, /^\.aws\//, /id_rsa$/, /credentials\.json$/];
const SHELL_ALLOW: { bin: string; args?: RegExp }[] = [
{ bin: "pnpm", args: /^(test|lint|typecheck|exec tsc)\b/ },
{ bin: "git", args: /^(status|diff|log|add|commit|checkout -b)\b/ },
{ bin: "rg", args: /.*/ },
];
const SHELL_DENY = [
/push\s+--force/,
/\bpublish\b/,
/curl\s+.*\|\s*(ba)?sh/,
/kubectl\s+delete/,
/aws\s+iam\s+/,
/rm\s+-rf\s+\//,
];
export function authorize(req: ToolRequest): { allow: boolean; reason: string } {
if (req.type === "read") {
if (READ_DENY.some((re) => re.test(req.path))) {
return { allow: false, reason: `read_denied:${req.path}` };
}
return { allow: true, reason: "ok" };
}
if (req.type === "write") {
if (!WRITE_ALLOW.some((re) => re.test(req.path))) {
return { allow: false, reason: `write_not_allowlisted:${req.path}` };
}
return { allow: true, reason: "ok" };
}
const line = req.argv.join(" ");
if (SHELL_DENY.some((re) => re.test(line))) {
return { allow: false, reason: `shell_denied:${line}` };
}
const bin = req.argv[0];
const rest = req.argv.slice(1).join(" ");
const ok = SHELL_ALLOW.some((r) => r.bin === bin && (r.args?.test(rest) ?? true));
return ok ? { allow: true, reason: "ok" } : { allow: false, reason: `shell_not_allowlisted:${line}` };
}
Elevation path for rare exceptions
async function runTool(req: ToolRequest, ctx: { elevationToken?: string }) {
const decision = authorize(req);
if (decision.allow) return execute(req);
if (ctx.elevationToken && await verifyElevation(ctx.elevationToken, req)) {
audit.elevated(req);
return execute(req);
}
throw new Error(`policy_denied:${decision.reason}`);
}
✅ Elevation is scoped to one argv fingerprint and expires in minutes.
❌ A sticky “YOLO mode” for the whole session.
Filesystem jail in sandboxes
When tools run remotely, combine allowlists with the sandbox patterns from LLM Coding Agents on AWS: ephemeral compute, no long-lived credentials, egress allowlists. Local IDE agents still need the same argv policy — hooks as in Claude Code Hooks.
Suggested default denials
| Category | Examples |
|---|---|
| Git | push --force, git filter-branch, rewrite of main |
| Packages | npm publish, twine upload |
| Cloud | aws iam, terraform apply, kubectl delete |
| Supply chain | curl \| bash, unpinned npx from HTTP |
| Secrets | read .env*, *.pem, ~/.aws |
Closing checklist
- [ ] Default-deny shell; explicit allowlist of bins + arg patterns
- [ ] Write allowlist limited to source/test trees
- [ ] Secret paths denied on read
- [ ] Elevation is single-use, fingerprint-scoped, audited
- [ ] CI policy unit tests for deny cases (force-push, publish, curl|sh)
- [ ] Remote sandboxes add egress + credential constraints
Related reading
- Claude Code Hooks: Gate Risky Shell Commands Before CI Runs
- LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda
- AI Code Review Bots: IAM, Secrets, and Least-Privilege Pipelines
- Bedrock Guardrails: Block Prompt Injection Inside Internal Dev Tools
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Lambda Outbound Proxies: Egress Allowlists That Block SSRF Paths - CheatCoders