Locks down an AI agent by configuring platform-level tool restrictions (deniedTools) and Earl network egress rules. Use after Earl is working and templates are created, to make Earl's security guarantee enforceable rather than advisory.
Installs into .claude/skills of the current project.
Are you the author of Secure Agent?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/diegosouzapw-secure-agent)
---
name: secure-agent
description: Locks down an AI agent by configuring platform-level tool restrictions (deniedTools) and Earl network egress rules. Use after Earl is working and templates are created, to make Earl's security guarantee enforceable rather than advisory.
---
# Secure Agent
After Earl is set up and working, this skill restricts the agent's ability to bypass Earl
and make raw API/CLI calls directly. Without this step, CLAUDE.md is just a suggestion.
## What This Does
1. **`deniedTools`**: Blocks specific bash commands (curl, gh, stripe-cli, etc.) at the
platform level for Claude Code. Agents cannot run these commands at all.
2. **Egress rules**: Restricts which URLs Earl templates can contact, preventing Earl itself
from being used as an open proxy.
## Important Limitation
`deniedTools` pattern matching can be bypassed via alternative tools (`python3 -c "import
urllib..."`, `node -e "fetch(...)"`, etc.). This blocks accidental or habitual CLI use — the
common case. For stronger containment, pair with OS-level firewall rules or a network proxy.
## Platform Support
| Platform | Mechanism | Hard restriction? |
| ---------------------- | -------------------------------------------- | ------------------------------------------------------- |
| **Claude Code** | `deniedTools` in `.claude/settings.json` | Yes — platform enforced |
| **Cursor** | `.cursor/mcp.json` or Cursor settings UI | Partial — check Cursor docs for per-tool restrictions |
| **Windsurf** | `.windsurf/mcp.json` or Windsurf settings UI | Partial — check Windsurf docs for per-tool restrictions |
| **Claude Desktop** | No bash access | N/A |
| **Non-MCP CLI agents** | CLAUDE.md instructions only | No — advisory only |
This skill primarily targets Claude Code. Instructions for other platforms are best-effort.
---
## Step 1: Check Coverage
Only deny tools for services that have Earl templates. Denying a tool for a service with no
Earl template would leave the agent unable to interact with that service at all.
```bash
earl templates list --json
```
Note which providers are covered. Map each to the CLI tools to deny:
| Earl template covers | Deny these tools |
| --------------------- | ---------------------------------------------------------------- |
| `github` | `Bash(gh *)`, `Bash(hub *)` |
| `stripe` | `Bash(stripe *)` |
| `slack` | `Bash(slack *)` |
| `openai` | `Bash(openai *)` |
| `vercel` | `Bash(vercel *)` |
| Any HTTP API template | `Bash(curl *)`, `Bash(wget *)`, `Bash(http *)`, `Bash(httpie *)` |
| Any SQL template | `Bash(psql *)`, `Bash(mysql *)`, `Bash(sqlite3 *)` |
| Any gRPC template | `Bash(grpcurl *)` |
**Do NOT deny `Bash` entirely.** Earl's bash protocol and legitimate shell operations still
need it.
**Note on `Bash(curl *)` and `Bash(wget *)`:** Denying these also blocks all non-API curl
uses — downloading binaries, health probes (`curl http://localhost:8080/health`), fetching
install scripts, etc. If the agent legitimately needs curl for non-API tasks, add a narrow
`allowedTools` override for those specific patterns, or use `earl call` instead for all
HTTP operations.
**Note on `Bash(gh *)`:** Denying `gh` also blocks all gh CLI uses that are not API calls:
`gh pr create`, `gh release upload`, `gh repo clone`, branch management, etc. If the agent
needs gh for repository operations that don't have Earl templates, add narrow exceptions or
create templates for those commands before denying `Bash(gh *)`.
---
## Step 2: Generate and Present Denylist
Based on the covered providers, generate the `deniedTools` array. Show it to the user before
applying anything:
> "Based on your Earl templates, I'd add these restrictions to `.claude/settings.json`:
>
> ```json
> {
> "deniedTools": [
> "Bash(curl *)",
> "Bash(wget *)",
> "Bash(gh *)",
> "Bash(stripe *)"
> ]
> }
> ```
>
> This blocks the listed tools for this agent in this project. Other projects are unaffected.
> Shall I apply this?"
Do not apply until the user explicitly approves.
---
## Step 3: Apply Denylist
For Claude Code: read `.claude/settings.json`, merge the `deniedTools` array (do not overwrite
other keys), write it back.
If `deniedTools` already exists, merge arrays — do not duplicate entries.
---
## Step 4: Verify
**For Claude Code:** Attempt to run a denied command:
```bash
curl https://example.com
```
Claude Code will refuse to run this command. The "tool denied" or "not allowed" error message
from Claude Code **is the success signal** — it means the denylist is active. You cannot
distinguish success from failure by looking at the exit code; look at whether Claude Code
blocked it before the shell ran it.
If Claude Code runs `curl` without blocking it, the `deniedTools` pattern syntax is wrong.
Check the format against current Claude Code documentation (search "deniedTools settings" in
the Claude Code docs or at https://docs.anthropic.com/en/docs/claude-code) — the exact pattern
syntax may vary by version. Then re-apply with the corrected format.
**For other platforms:** Ask the user to attempt a denied command manually in their agent
session and confirm it is blocked.
Test that Earl still works:
```bash
earl templates list
```
Expected: succeeds and lists available templates (Earl is not in the denylist).
---
## Step 5: Configure Egress Rules (Strongly Recommended)
Without egress rules, Earl is an open proxy — any HTTP template with a parameterized URL can
reach any public endpoint. Add `[[network.allow]]` rules to
`~/.config/earl/config.toml` (macOS/Linux) or `%APPDATA%\earl\config.toml` (Windows)
to restrict which hosts Earl templates can contact.
**Note:** Egress rules are global — they apply to all projects using this Earl install.
Earl does not currently support per-project config files.
For each provider template, add a rule:
```toml
[[network.allow]]
hosts = ["api.github.com"]
[[network.allow]]
hosts = ["api.stripe.com"]
[[network.allow]]
hosts = ["api.slack.com", "slack.com"]
```
**Security note on environments:** If any template uses `allow_environment_protocol_switching
= true` in its annotations, an environment override can silently switch protocols (e.g. from
HTTP to bash). Review templates with this annotation carefully — a staging environment that
switches to bash bypasses the HTTP egress rules above. Prefer `vars.*` for environment
differences (e.g. different base URLs) over full protocol switching where possible.
After editing, verify:
```bash
earl doctor
```
Earl doctor checks that the config is valid. Any `[[network.allow]]` parse errors will be
reported.
---
## What This Does Not Cover
- **Bash templates**: The `bash` protocol runs user-defined scripts in Earl's sandbox. The
denylist does not restrict what Earl's own bash protocol can do. Ensure bash templates
explicitly set `sandbox.network = false` unless network access is required.
- **Agents without per-tool restriction**: For non-Claude-Code agents, the CLAUDE.md instruction
is the only constraint. This is advisory, not enforced.
- **Alternative interpreters**: Python, Node, Ruby, and other interpreters are not blocked by
a curl-specific denylist. Pair with OS-level firewall rules for stronger containment.
---
## Next Steps
- Earl is now the enforced channel for all covered API calls
- To add egress rules for a new provider: add `[[network.allow]]` blocks to
`~/.config/earl/config.toml`
- If the agent is blocked from a call it needs: add an Earl template for that service, or
remove the specific deny rule for that tool
- If something breaks: invoke `troubleshoot-earl`