AOS manages AI agents that can autonomously call tools, query databases, interact with external APIs, and process sensitive data. The threat model considers:
| Threat | Mitigation |
|---|---|
| Rogue agent accessing unauthorized resources | Policy engine blocks via DENY rules |
| Agent budget exhaustion / cost bomb | AgentBudget hard limit blocks before policy evaluation |
| Compromised API key exfiltration | API keys encrypted at rest with Fernet symmetric encryption |
| Unauthorized swarm dispatch | JWT authentication required on all bridge endpoints |
| Audit log tampering | PolicyAuditLog is append-only, never modified by system |
| Agent impersonation | Unique identity_key per agent, JWT sessions with revocation |
| Sensitive data leakage via tools | Policy conditions can match on context.contains_pii fields (requires caller to set) |
| Unbounded LLM loops | max_iterations field on AgentCapability; LangGraph iteration limits |
Standard Django authentication + JWT via djangorestframework-simplejwt.
POST /api/token/ → access token (5 min TTL) + refresh token (1 day TTL)
POST /api/token/refresh/ → new access token
Agents authenticate with their identity_key via the AgentAuthentication class in agent_gateway/authentication.py.
The AgentAuthentication backend supports:
- JWT tokens — Standard Bearer tokens in the
Authorizationheader - Identity keys — Direct
identity_keyvalues passed asX-Agent-Identityheader
Sessions are tracked in AgentSession with IP address, user agent, and expiry. Sessions can be revoked individually.
All LLM provider API keys are encrypted at rest using Fernet (AES-128 CBC + HMAC-SHA256).
# Stored in LLMConfig.api_key
# Encryption happens automatically on save():
def save(self, *args, **kwargs):
if self.api_key and not self.api_key.startswith('gAAAA'):
self.api_key = SecurityManager.encrypt(self.api_key)
super().save(*args, **kwargs)The encryption key is derived from settings.SECRET_KEY. Changing SECRET_KEY in production will make all existing encrypted keys unreadable.
In production, store the SECRET_KEY in a secrets manager (AWS Secrets Manager, HashiCorp Vault) and inject it via environment variable. Do not commit it to source control.
Agents are assigned one or more Role objects. Each role has a permissions JSON list.
{
"name": "finance-analyst",
"permissions": ["billing:read", "knowledge:query", "reports:read"]
}The policy engine uses roles when evaluating which policies apply to an agent:
- Policies assigned to a role apply to all agents with that role
- Policies assigned to a specific agent only apply to that agent
- Global policies (no agent or role assignment) apply to everyone
agent-swarm/core/command_executor.py classifies shell commands before executing them:
| Level | Examples | Behavior |
|---|---|---|
SAFE |
ls, cat, git status, npm list |
Auto-approved, no logging |
MODERATE |
npm install, git commit, pip install |
Logged, auto-approved |
DANGEROUS |
rm, git push, deployment commands |
Requires explicit approval |
BLOCKED |
rm -rf /, shutdown, format |
Never executed |
This prevents accidental or malicious destructive operations during autonomous agent runs.
All five bridge endpoints require a valid JWT Bearer token. The token is obtained via POST /api/token/ and written to agent-swarm/.env by start.sh.
JWT access tokens expire (default: 5 minutes via SIMPLE_JWT.ACCESS_TOKEN_LIFETIME). start.sh issues a fresh token on every startup. For long-running swarm sessions, implement token refresh using the refresh token.
By default, if AOS is unreachable the swarm fails open (proceeds without governance). This prevents AOS outages from blocking all agent work.
Change to fail-closed for production:
# agent-swarm/core/aos_client.py
def policy_check(...):
if not ENABLED:
return {"decision": "deny", "reason": "AOS not configured"}
...
except Exception:
return {"decision": "deny", "reason": "AOS unreachable — fail-closed"}The default SECRET_KEY in backend/backend/settings.py is insecure and public:
SECRET_KEY = 'django-insecure-vbo=9r$&_9d_&4da=j%mq2w%rv0!ldfw*-ym+3+0ah6=yso@&5'You must change this before any non-local deployment. Generate a secure key:
python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"The default admin password is admin1234. Change it immediately after setup:
python manage.py changepassword adminThe default database is SQLite (backend/db.sqlite3). SQLite is not suitable for production:
- No concurrent write support
- No network access (can't scale horizontally)
- File permissions are the only access control
Switch to PostgreSQL via DATABASE_URL before going to production.
settings.py has DEBUG=True by default. Debug mode:
- Exposes full stack traces in HTTP responses
- Disables security headers
- Should never be enabled in production
The root .env file contains ANTHROPIC_API_KEY=your_key_here. Replace the placeholder with your actual key. Never commit a real API key to version control — add .env to .gitignore.
start.sh writes a JWT token to agent-swarm/.env. This file should not be committed to source control. Ensure .env is in .gitignore:
agent-swarm/.env
backend/.env
.env
There is no global rate limiting on the AOS API. In production, add rate limiting at the nginx/Caddy level or use djangorestframework throttling.
Development runs on plain HTTP. In production, all traffic must be TLS-encrypted. Use a reverse proxy (nginx, Caddy) to terminate TLS.
- Rotate
SECRET_KEY - Set
DEBUG=False - Set
ALLOWED_HOSTSto your domain only - Use PostgreSQL with strong credentials
- Enable HTTPS (TLS 1.2+) via reverse proxy
- Change admin password
- Store all secrets in a secrets manager
- Add
.envfiles to.gitignore - Set
SIMPLE_JWT.ACCESS_TOKEN_LIFETIMEto a short interval (e.g., 5 minutes) - Change swarm bridge to fail-closed
- Add nginx rate limiting
- Enable Django's
SECURE_*headers (SECURE_SSL_REDIRECT,SECURE_HSTS_SECONDS, etc.) - Run
python manage.py check --deployand fix all warnings - Implement periodic
AgentBudgetreset (monthly cron) - Review and tighten default policies for production environment
- Set
AOS_ENV=prodin swarm.env