Security Best Practices
Practical security guidelines for building and deploying Agent.md agents safely.
Core Principles
1. Least Privilege
Only grant the minimum permissions needed for an agent to function.
Bad:
Good:
Real-world example:
---
name: log-analyzer
paths:
- /var/log/app/*.log
- /workspace/config/patterns.yml
- /output/reports/
# Can only access these specific paths
---
2. Never Store Secrets in Frontmatter
API keys, passwords, and tokens belong in environment variables, never in .md files.
NEVER do this:
---
name: slack-bot
model:
provider: openai
api_key: sk-proj-abc123... # WRONG!
settings:
slack_webhook: https://hooks.slack.com/services/T00/B00/xxx # WRONG!
---
Always do this:
---
name: slack-bot
model:
provider: openai
# API key loaded from OPENAI_API_KEY env var
---
Send a notification to ${SLACK_WEBHOOK} with today's summary.
.env file:
At runtime, ${SLACK_WEBHOOK} in the prompt is replaced with the actual URL from .env.
3. Use Environment Variables
Store all sensitive data in .env files and reference them in frontmatter.
Directory structure:
/Users/zfab/repos/agentmd/
├── .env # Local dev (gitignored)
├── .env.production # Production secrets (gitignored)
├── .env.example # Template (committed)
└── workspace/
└── my-agent.md
.env.example (commit this):
# LLM Provider
OPENAI_API_KEY=sk-proj-your-key-here
ANTHROPIC_API_KEY=sk-ant-your-key-here
GOOGLE_API_KEY=your-key-here
# External Services
SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
DATABASE_URL=postgresql://user:pass@localhost:5432/db
# Agent.md Settings
WORKSPACE_DIR=/workspace
OUTPUT_DIR=/output
MAX_ITERATIONS=10
Access in agent prompts:
Use ${VAR_NAME} syntax in the prompt body to inject secrets at runtime:
---
name: api-poller
model:
provider: google
name: gemini-2.5-flash
---
Poll the API at ${API_BASE_URL}/status with header "Authorization: Bearer ${API_TOKEN}".
Report any issues.
The ${VAR} values are resolved from .env before the prompt is sent to the LLM. Variables without the $ prefix (e.g., {date}) are not substituted — use those as placeholders for the LLM to fill in.
4. Set Timeouts and Limits
Prevent runaway costs and infinite loops.
---
name: research-agent
model:
provider: openai
name: gpt-4
temperature: 0.7
max_tokens: 2000 # Limit output length
settings:
max_iterations: 5 # Prevent infinite loops
timeout: 300 # 5 minute timeout
recursion_limit: 10
---
Real-world example for expensive operations:
---
name: web-scraper
model:
provider: anthropic
name: claude-opus-4-6
max_tokens: 4000 # Opus is expensive
settings:
max_iterations: 3 # Limit tool calls
timeout: 180 # 3 minute hard stop
triggers:
- type: schedule
cron: "0 9 * * *" # Once daily, not every minute
---
5. File Watcher Debouncing
Prevent watch triggers from firing repeatedly during file edits.
Without debouncing (fires multiple times):
triggers:
- type: watch
paths: ["/workspace/data/input.json"]
# Will fire on every keystroke during editing!
With debouncing:
triggers:
- type: watch
paths: ["/workspace/data/input.json"]
debounce: 2.0 # Wait 2 seconds after last change
Real-world example:
---
name: code-reviewer
triggers:
- type: watch
paths:
- "/workspace/src/**/*.py"
- "/workspace/src/**/*.js"
debounce: 3.0 # Wait for user to finish typing
ignore_patterns:
- "**/__pycache__/**"
- "**/node_modules/**"
- "**/.git/**"
---
6. Pre-Validation
Validate inputs before expensive LLM calls.
Example agent with validation:
---
name: email-processor
paths:
- /workspace/data/emails/*.json
- /output/processed/
---
Before processing emails:
1. Validate JSON structure
2. Check required fields: from, to, subject, body
3. Skip if already processed (check /output/processed/)
4. Only process emails from the last 24 hours
This prevents wasting tokens on invalid data.
Validation in prompt:
You are an email processor. Follow these steps:
1. **Validate input** - Check that the email JSON contains:
- `from` (email address)
- `to` (email address)
- `subject` (non-empty string)
- `body` (non-empty string)
- `timestamp` (ISO 8601 format)
2. **Skip if invalid** - If validation fails, write an error to `/output/errors/` and stop. Do not proceed.
3. **Process valid emails only** - Continue with your task.
7. Token Monitoring
Track and limit token usage to prevent cost overruns.
Check token usage:
# View token usage for all executions
agentmd logs research-agent --verbose
# Output shows:
# Execution: abc123
# Input tokens: 1,234
# Output tokens: 567
# Total tokens: 1,801
# Estimated cost: $0.05
Set alerts in production:
---
name: production-agent
model:
provider: openai
name: gpt-4
max_tokens: 1000 # Hard limit per response
settings:
max_iterations: 5 # Max 5 tool calls = max ~5000 tokens
---
Calculate maximum possible cost:
Max tokens per iteration: 1000 output + ~500 input = 1500
Max iterations: 5
Total max tokens: 7,500
GPT-4 pricing (example):
- Input: $0.03 / 1K tokens
- Output: $0.06 / 1K tokens
Max cost per execution:
- Input: 2,500 * $0.03 / 1000 = $0.075
- Output: 5,000 * $0.06 / 1000 = $0.30
- Total: ~$0.38 per execution
Path Security
Workspace Isolation
Keep agents isolated to their designated directories.
Secure setup:
---
name: report-generator
paths:
- /workspace/data/reports/
- /workspace/config/templates/
- /output/reports/
# Cannot access /etc/, /home/, other sensitive paths
---
Multiple agents with isolation:
# Agent 1: Customer data processor
---
name: customer-processor
paths:
- /workspace/data/customers/
- /output/customers/
---
# Agent 2: Analytics reporter
---
name: analytics-reporter
paths:
- /output/customers/aggregated.json # Only accesses aggregated data
- /output/reports/
# Cannot access raw customer data from /workspace/data/customers/
---
Sensitive Path Patterns
Never grant access to these paths:
# DANGEROUS - Never do this:
paths:
- "/etc/**" # System config
- "/home/**" # User directories
- "~/.ssh/**" # SSH keys
- "~/.aws/**" # AWS credentials
- "**/.env" # Environment files
- "**/secrets.yml" # Secret files
Schedule Security
Avoid Expensive Frequent Schedules
# EXPENSIVE - $$$
triggers:
- type: schedule
cron: "* * * * *" # Every minute with GPT-4!
# REASONABLE
triggers:
- type: schedule
cron: "0 */6 * * *" # Every 6 hours
Calculate schedule costs:
Schedule: Every hour (24x per day)
Cost per execution: $0.10
Daily cost: 24 * $0.10 = $2.40
Monthly cost: $2.40 * 30 = $72.00
vs.
Schedule: Every 6 hours (4x per day)
Cost per execution: $0.10
Daily cost: 4 * $0.10 = $0.40
Monthly cost: $0.40 * 30 = $12.00
Rate Limiting External APIs
Respect API rate limits in scheduled agents.
---
name: api-poller
triggers:
- type: schedule
cron: "*/15 * * * *" # Every 15 minutes
---
The API has a rate limit of 100 requests/hour.
- Each run makes ~4 requests
- Schedule: 4 runs/hour * 4 requests = 16 requests/hour
- Well within 100 req/hour limit ✓
Tool Security
HTTP Request Tool
Restrict allowed domains and methods.
---
name: news-fetcher
tools:
- name: http_request
config:
allowed_domains:
- "api.newsapi.org"
- "rss.nytimes.com"
allowed_methods: ["GET"] # No POST/PUT/DELETE
timeout: 10
max_redirects: 2
---
File Write Tool
Prevent overwriting important files.
---
name: log-processor
paths:
- /output/processed/
# Explicitly NOT allowed to access:
# - /workspace/ (source data)
# - /etc/ (system files)
# - ~ (home directory)
---
Never overwrite files in /workspace/. Only write to /output/.
MCP Security
Restrict MCP Tool Access
Only enable needed MCP servers and tools.
---
name: filesystem-agent
mcp:
servers:
filesystem:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace/data"]
# Filesystem server is sandboxed to /workspace/data only
allowed_tools:
- "read_file"
- "list_directory"
# NOT allowed: write_file, delete_file, move_file
---
Checklist
Before deploying an agent to production:
- No secrets in frontmatter
- All secrets in
.envfile -
.envfile is in.gitignore -
pathsfollows least privilege -
max_tokensset appropriately -
max_iterationsset to prevent loops -
timeoutset to prevent hangs - Schedule frequency is cost-effective
- Watch triggers have
debounceconfigured - HTTP tools have
allowed_domainsset - Token usage monitored and alerted
- Pre-validation in prompt to skip invalid inputs
- Tested with invalid/malicious inputs
Security Incident Response
If an agent is compromised or leaking data:
-
Stop execution immediately:
-
Rotate secrets:
-
Audit logs:
-
Review and fix:
- Add missing path restrictions
- Add missing input validation
-
Reduce permissions
-
Test and redeploy: