Securely Deploying AI Agents
View Source📚 Official Documentation: This guide is based on the official Claude Agent SDK documentation. Examples are adapted for Elixir.
⚠️ TODO: THESE DOCS ARE INCOMPLETE
Harden your ClaudeCode deployment for production use.
Sandbox Configuration
Isolate bash command execution with the sandbox option:
{:ok, session} = ClaudeCode.start_link(
sandbox: %{
"environment" => "docker",
"container" => "my-sandbox"
}
)The sandbox configuration is merged into the CLI's --settings flag as {"sandbox": {...}}.
Permission Modes for Production
Choose the least-privileged permission mode for your use case:
| Mode | Use Case |
|---|---|
:plan | Read-only analysis, code review |
:dont_ask | Automated pipelines where prompts can't be answered |
:accept_edits | Supervised environments where file edits are expected |
:default | Interactive use with a human in the loop |
# Code review agent - read-only
{:ok, reviewer} = ClaudeCode.start_link(
permission_mode: :plan,
allowed_tools: ["Read", "Glob", "Grep"]
)
# CI/CD agent - rejects anything needing permission
{:ok, ci_agent} = ClaudeCode.start_link(
permission_mode: :dont_ask,
allowed_tools: ["Read", "Bash(mix:*)"]
)Least-Privilege Tool Access
Restrict tools to only what's needed:
# Analysis-only agent
{:ok, session} = ClaudeCode.start_link(
allowed_tools: ["Read", "Glob", "Grep"],
add_dir: ["/app/src"] # Only access source directory
)
# Build agent - limited bash commands
{:ok, session} = ClaudeCode.start_link(
allowed_tools: ["Read", "Edit", "Bash(mix:*)", "Bash(git:*)"],
disallowed_tools: ["Bash(rm:*)", "Bash(curl:*)"]
)API Key Management
Never hardcode API keys. Use environment variables:
# config/runtime.exs
config :claude_code,
api_key: System.fetch_env!("ANTHROPIC_API_KEY")Or pass via the env option for per-session keys:
{:ok, session} = ClaudeCode.start_link(
api_key: System.fetch_env!("ANTHROPIC_API_KEY")
)The SDK passes the API key via the ANTHROPIC_API_KEY environment variable to the CLI subprocess. It is never included in command-line arguments.
Directory Access Control
Limit which directories Claude can access:
{:ok, session} = ClaudeCode.start_link(
cwd: "/app/workspace",
add_dir: ["/app/shared/config"]
# Claude can access /app/workspace (cwd) and /app/shared/config
)Cost Controls
Prevent runaway costs with budget limits:
{:ok, session} = ClaudeCode.start_link(
max_turns: 10,
max_budget_usd: 1.00
)If either limit is hit, the session returns an error result (subtype: :error_max_turns or :error_max_budget_usd).
Audit Trail
Enable hooks for a complete audit trail:
{:ok, session} = ClaudeCode.start_link(
hooks: %{
PostToolUse: [%{hooks: [
fn %{tool_name: name, tool_input: input}, _id ->
Logger.info("Tool #{name} executed", input: input)
:ok
end
]}]
}
)Disable Session Persistence
For ephemeral workloads, prevent session data from being saved to disk:
{:ok, session} = ClaudeCode.start_link(
no_session_persistence: true
)Production Checklist
- [ ] Use a restrictive
permission_mode(:plan,:dont_ask, or:accept_edits) - [ ] Set
allowed_toolsto only what's needed - [ ] Set
max_turnsandmax_budget_usdlimits - [ ] Store API keys in environment variables
- [ ] Enable
PostToolUsehooks for auditing - [ ] Use
cwdandadd_dirto limit file access - [ ] Consider
sandboxfor bash isolation - [ ] Use
no_session_persistence: truefor sensitive workloads - [ ] Use per-user or per-request sessions for multi-user workloads (see Hosting)
Next Steps
- Permissions - Detailed permission configuration
- Hosting - OTP supervision and deployment
- Cost Tracking - Usage monitoring