Zero Trust for AI Agents: How Alpha Agent Implements Defense-in-Depth
A deep dive into Alpha Agent's multi-layered security architecture: from KMS encryption and container isolation to zero inbound ports and network segmentation.
The trust problem with AI agents
AI agents operate differently from traditional SaaS applications. They hold API keys for external services, process sensitive user data, execute code, and interact with third-party systems on behalf of their users. A compromised agent does not just leak one database row — it can exfiltrate every integration credential, read private conversations, and act with the full authority of the user across every connected service.
This is why Alpha Agent’s security model starts from a single axiom: never trust, always verify. Every layer assumes the layers around it have already been compromised. Every component proves its identity before receiving access. Every secret is encrypted at rest and decrypted only at the moment of use.
This post walks through the five layers of Alpha Agent’s defense-in-depth architecture. If you are a security engineer evaluating managed AI agent platforms, or an architect designing multi-tenant systems of your own, this is the technical detail you need.
Layer 1: Network — no inbound ports, no attack surface
The most effective way to prevent network-based attacks is to eliminate the network attack surface entirely. Alpha Agent EC2 instances have zero inbound ports in their security groups. No SSH. No RDP. No management port of any kind. The scale of what happens when this boundary is missing is documented in our post on 135,000 exposed instances — every one of those machines had at least one port reachable from the public internet.
How management works without SSH
All administrative access goes through AWS Systems Manager Session Manager. SSM establishes an outbound connection from the instance to the SSM service endpoint, which means:
- There is no listening port for an attacker to discover through port scanning.
- There is no SSH key to rotate, leak, or brute-force.
- Every session is authenticated through IAM, logged to CloudTrail, and auditable.
Provisioning new containers, deploying updates, and running diagnostics all happen through SSM SendCommand, which the management Lambda invokes with IAM-scoped permissions.
Traffic routing
User traffic follows a single controlled path: the ALB terminates TLS and routes HTTPS based on host headers. On the instance, Nginx extracts the subdomain slug and maps it to the correct container port:
server {
listen 18790;
server_name ~^(?<slug>[^.]+)\.alphaagent\.app$;
location / {
if ($dashboard_port = 0) {
return 404;
}
proxy_pass http://127.0.0.1:$dashboard_port;
}
}
Ports are bound to 127.0.0.1 only — container services are never exposed to the host network interface. The slug-to-port mapping is maintained in per-user config files under /etc/nginx/slugs/, and Nginx enforces security headers on every response:
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
What this prevents: Port scanning, SSH brute force, direct service exploitation, man-in-the-middle attacks, and any network-level reconnaissance. An attacker scanning the instance’s IP address sees nothing.
Layer 2: Container isolation — blast radius containment
Even with a hardened network perimeter, we assume that application-level vulnerabilities will eventually be found — CVE-2026-25253 is a real example of an RCE in OpenClaw’s gateway that makes this assumption concrete. The container layer ensures that a compromise of one user’s agent cannot spread to another user or to the host.
One container per user, one network per container
Every user gets their own Docker container running on an isolated bridge network:
networks:
oc-{SLUG}-net:
driver: bridge
Containers cannot communicate with each other. There is no shared Docker network. Each container’s network namespace is completely separate. The only path out of a container is through its mapped port on 127.0.0.1, which Nginx controls.
Read-only rootfs and no-new-privileges
Containers run with two critical security flags:
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:size=256M
The read_only: true flag makes the entire root filesystem immutable. An attacker who gains code execution inside a container cannot modify system binaries, install persistence mechanisms, or tamper with the application. The only writable space is a 256MB tmpfs mount at /tmp, which lives in RAM and is wiped on restart.
The no-new-privileges flag prevents privilege escalation through setuid/setgid binaries. Even if an attacker discovers a local privilege escalation vulnerability, the container runtime blocks it at the kernel level.
Resource limits via cgroups
Each container is constrained to 1.0 CPU and 3072MB of memory, with reservations of 0.25 CPUs and 512MB:
deploy:
resources:
limits:
memory: 3072M
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.25'
This prevents denial-of-service conditions where one user’s runaway process starves others. The Linux kernel enforces these limits through cgroups — there is no application-level throttling to bypass.
Non-root execution
Containers run as user agent (UID 1001), not root. The Dockerfile creates this user, and the entrypoint drops privileges to it using gosu after initial directory setup:
RUN useradd -m -s /bin/bash -d /home/agent -u 1001 agent
The entrypoint script runs initial setup (directory permissions, bind mount preparation) as root, then launches all services as the agent user:
gosu agent openclaw gateway run &
gosu agent node server.js &
Combined with no-new-privileges, this means service processes inside the container can never gain root access, even if the container runtime has a vulnerability.
What this prevents: Container escape, lateral movement between users, privilege escalation, resource exhaustion attacks, persistent malware installation, and cross-tenant data access. For a deeper look at our container model, see the container isolation architecture post or our container isolation overview.
Layer 3: Data encryption — secrets that stay secret
AI agents need API keys, OAuth tokens, and service credentials to function. These secrets are the highest-value targets in any breach. Alpha Agent treats secret storage as a distinct security domain with its own encryption boundary.
KMS envelope encryption
User secrets are encrypted with AWS KMS before being stored in DynamoDB — for the full KMS encryption deep dive, including cost comparisons and IAM scoping details, see that dedicated post. The management Lambda performs encryption and decryption using a dedicated KMS key:
async function encryptSecret(plaintext) {
const { CiphertextBlob } = await kms.send(new EncryptCommand({
KeyId: KMS_KEY_ARN,
Plaintext: Buffer.from(plaintext, 'utf-8'),
}));
return Buffer.from(CiphertextBlob).toString('base64');
}
async function decryptSecret(ciphertext) {
const { Plaintext } = await kms.send(new DecryptCommand({
CiphertextBlob: Buffer.from(ciphertext, 'base64'),
}));
return Buffer.from(Plaintext).toString('utf-8');
}
Secrets are never stored in plaintext. They are never passed through environment variables at provisioning time. They are never included in EC2 user-data scripts. The encrypted ciphertext blob is stored in the DynamoDB encrypted_secrets field, and only the management Lambda — with its scoped IAM role — can decrypt it.
Why DynamoDB + KMS instead of Secrets Manager
AWS Secrets Manager charges $0.40 per secret per month. For a multi-tenant platform with dozens of secrets per user, this adds up to approximately $3.15 per user per month just for secret storage. By using DynamoDB with KMS encryption, we get the same cryptographic guarantees at a fraction of the cost, while maintaining a single data plane for user records.
The IAM policy scopes the Lambda’s KMS access to a single key:
- Effect: Allow
Action:
- kms:Encrypt
- kms:Decrypt
- kms:GenerateDataKey
Resource: !Ref KMSKeyArn
Even if the Lambda’s IAM role is compromised, the attacker can only use this specific KMS key. They cannot enumerate or access other KMS keys in the account.
Workspace storage isolation
User workspace data is stored on the host at /data/workspaces/{slug}/ and synced to S3 at s3://alphaagent-shared/workspaces/{slug}/. The sync daemon explicitly excludes sensitive files:
aws s3 sync "$dir" "s3://${S3_BUCKET}/workspaces/${slug}/" \
--exclude ".git/*" \
--exclude "node_modules/*" \
--exclude ".env" \
--exclude "openclaw.json" \
--exclude "*.key" \
--exclude "*.pem"
Private keys, environment files, and credentials are never synced to S3. The S3 bucket policy and IAM roles enforce per-user path isolation — a user’s Lambda or task role can only access their own workspace prefix.
What this prevents: Plaintext credential exposure in database dumps, secret leakage through environment variable enumeration, cross-tenant secret access, and credential theft through S3 bucket misconfigurations. See our encryption architecture for the full key management lifecycle.
Layer 4: Authentication and authorization — verify every request
Zero trust means every request is authenticated and authorized, regardless of which network it originates from. Alpha Agent has no concept of a “trusted internal network.”
Auth0 JWT validation on all routes
Every dashboard and API request is authenticated with Auth0-issued JWTs. The dashboard server validates tokens using Node.js crypto primitives — no third-party auth library with its own dependency chain and attack surface:
function verifySession(token, secret) {
const [payload, sig] = token.split('.');
if (!payload || !sig) return null;
const expected = createHmac('sha256', secret)
.update(payload).digest('base64url');
if (sig.length !== expected.length ||
!timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return null;
try {
return JSON.parse(Buffer.from(payload, 'base64url').toString());
} catch {
return null;
}
}
Note the use of timingSafeEqual for signature comparison. This prevents timing side-channel attacks where an attacker could determine the correct signature byte-by-byte by measuring response times.
Stripe webhook signature verification
Billing webhooks from Stripe are verified using HMAC-SHA256 signature validation before any processing occurs:
async function handleStripeWebhook(event) {
const secrets = await getSharedSecrets();
const sigHeader = event.headers?.['stripe-signature'] || '';
const rawBody = event.body || '';
if (!verifyStripeSignature(rawBody, sigHeader, secrets.STRIPE_WEBHOOK_SECRET)) {
return json(400, { error: 'Invalid signature' });
}
// ... process webhook only after verification
}
The signature verification also uses timingSafeEqual, ensuring consistent-time comparison regardless of how many bytes match.
Least-privilege IAM policies
The management Lambda’s IAM role follows the principle of least privilege. DynamoDB access is scoped to specific table ARNs and their indexes — not Resource: '*'. S3 access is scoped to specific path prefixes. EC2 operations require the ManagedBy: alphaagent tag as an IAM condition. KMS access is limited to a single key ARN. Every policy statement is resource-constrained.
What this prevents: Token forgery, replay attacks, timing side-channel attacks on authentication, webhook spoofing, unauthorized lateral access between AWS services, and privilege escalation through over-permissioned IAM roles.
Layer 5: Infrastructure hardening — defense at the metal
The final layer addresses attacks that target the infrastructure itself: SSRF against cloud metadata services, credential theft from instance profiles, and unauthorized resource access.
IMDS lockdown
The EC2 Instance Metadata Service (IMDS) is a common target for SSRF attacks. If an attacker can make the application send a request to 169.254.169.254, they can steal the instance’s IAM role credentials. Alpha Agent defends against this in two ways.
First, Docker containers are blocked from reaching IMDS entirely through an iptables rule applied during instance bootstrap. This is the strongest possible control — no container process can reach the metadata endpoint regardless of IMDS version:
iptables -I DOCKER-USER -d 169.254.169.254 -j DROP
Even if an attacker achieves code execution inside a container and somehow bypasses the read-only filesystem and no-new-privileges restrictions, they cannot reach the metadata service. The host-level firewall drops the traffic before it leaves the Docker network.
Second, the host itself uses IMDSv2 with session tokens for its own metadata requests, which requires a PUT request with a TTL header. This breaks most SSRF attacks at the host level because the attacker needs to make two separate requests with specific headers, not just a single GET.
Resource tagging and centralized logging
Every AWS resource carries a ManagedBy: alphaagent tag, enabling IAM condition-based access control and providing a clean audit trail. Container logs are shipped directly to CloudWatch Logs via the Docker awslogs driver — they never touch the local filesystem (which is read-only anyway). Logs flow from the Docker daemon to CloudWatch, where they can be monitored, alerted on, and retained according to compliance requirements.
What this prevents: SSRF-based credential theft from IMDS, unauthorized instance manipulation, log tampering, and untracked resource creation. For full details on our infrastructure hardening, see the infrastructure security page.
How the layers interact
Defense-in-depth is not a checklist. The value comes from how layers overlap and compensate for each other’s weaknesses:
| Attack scenario | Layer 1 (Network) | Layer 2 (Container) | Layer 3 (Data) | Layer 4 (Auth) | Layer 5 (Infra) |
|---|---|---|---|---|---|
| Port scan / service exploit | Blocked: no inbound ports | — | — | — | — |
| SSRF to steal instance credentials | — | Blocked: iptables DROP rule | — | — | Blocked: IMDSv2 only |
| Container escape to host | — | Blocked: read-only, no-new-privileges, non-root | — | — | — |
| Lateral movement between users | — | Blocked: isolated Docker networks | — | — | — |
| Database dump exposes secrets | — | — | Blocked: KMS encryption at rest | — | — |
| Forged authentication token | — | — | — | Blocked: HMAC + timingSafeEqual | — |
| Webhook spoofing | — | — | — | Blocked: Stripe signature verification | — |
| Over-privileged IAM role | — | — | — | Blocked: least-privilege policies | Blocked: tag-based conditions |
| Resource exhaustion / noisy neighbor | — | Blocked: cgroup CPU/memory limits | — | — | — |
No single layer is sufficient. An attacker who bypasses one control faces another, and another after that. This is the essence of zero trust applied to AI agent infrastructure.
What’s next for AI agent security?
Security is never finished. We are actively working on runtime anomaly detection within containers, automated secret rotation through KMS, and network policy enforcement through eBPF-based controls.
To go deeper, start with our security overview, then dive into container isolation, encryption architecture, and infrastructure hardening.
If you are building your own multi-tenant AI agent platform, the core principle remains the same: assume every layer will be breached, and design the next layer to contain the damage.