This document provides detailed technical knowledge about the authentication, database, sandbox orchestration, and conversation storage systems that enable self-healing across ECS Fargate task replacements.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β us-east-1 β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β AuthStack: Cognito User Pool, managed login branding β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β EdgeStack: Lambda@Edge, CloudFront, WAF, Route 53 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ (HTTP Origin)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Main Region β
β β
β βββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββ β
β β NetworkStack βββββΆβ SecurityStack βββββΆβ MonitoringStack β β
β β VPC, Endpointsβ β Fargate roles β β Logs, S3, Alarms β β
β βββββββββββββββββ β SGs, KMS β ββββββββββββββββββββ β
β β ββββββββββββββββββ β β
β β β β β
β βΌ βΌ β β
β ββββββββββββββββ βββββββββββββββββββββββββ β β
β β ClusterStack β β DatabaseStack β β β
β β ECS Cluster β β Aurora Serverless v2 β β β
β β Cloud Map β β RDS Proxy β β β
β ββββββββββββββββ βββββββββββββββββββββββββ β β
β β β β β
β ββββββββΆ ββββββββββββββββββββββββββββββββββββββββ β
β β β SandboxStack β β
β β β DynamoDB, Orchestrator, Sandbox Tasks β β
β β β Idle Monitor, Task State Lambda β β
β β ββββββββββββββββββββββββββββββββββββββββ β
β β β β
β βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ComputeStack β β
β β Fargate Services (App + OpenResty), ALB, EFS, Lambda TG β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
User Request
β
CloudFront (Edge)
β
Lambda@Edge (JWT Validation)
β (if valid)
Inject User Headers (X-Cognito-*)
β
Origin (ALB β Fargate)
β
OpenHands App (CognitoUserAuth)
| Setting | Value | Notes |
|---|---|---|
| Location | us-east-1 | Required for Lambda@Edge |
| Sign-up | Disabled | Admin creates users |
| Sign-in | Email-based | |
| Password Policy | 12+ chars | Uppercase, lowercase, numbers, symbols |
| MFA | Optional TOTP | |
| Access Token | 1 hour | API access |
| ID Token | 1 day | Identity (stored in cookie) |
| Refresh Token | 30 days | Session persistence |
Four authentication flows handled by lib/lambda-edge/auth-handler.js:
-
OAuth Callback (
/_callback): Receives auth code β exchanges for tokens β verifies ID token signature via JWKS β setsid_tokencookie (HttpOnly, Secure, SameSite=Lax) β redirects to destination -
Logout (
/_logout): Clearsid_tokencookie β redirects to Cognito logout URL -
Runtime Subdomain: Verify JWT β inject
X-Cognito-User-Idβ rewrite URI to/runtime/{convId}/{port}/...β allow/redirect -
Main Domain Request Validation: Extracts
id_tokenfrom cookie β verifies JWT signature against Cognito JWKS β validates issuer, expiration, audience β injects user headers β redirects to login if invalid
Critical for conversation persistence and multi-tenant isolation: Lambda@Edge injects verified user information into request headers, clearing any existing headers to prevent spoofing:
X-Cognito-User-Id: Cognito user ID (UUID frompayload.sub)X-Cognito-Email: User's email address
The OpenHands backend reads injected headers via CognitoUserAuth class configured in lib/compute-stack.ts:
USER_AUTH_CLASS=openhands.server.user_auth.cognito_user_auth.CognitoUserAuth- Header Spoofing Prevention: Lambda@Edge clears existing
x-cognito-*headers before setting verified values - JWKS Signature Verification: Full RS256 signature verification against Cognito's public keys
- Token Validation: Issuer, expiration, and audience validated
- HttpOnly Cookies: Token cookies cannot be accessed by JavaScript
- WAF Protection: AWS WAF rules protect against common attacks
| Setting | Value | Notes |
|---|---|---|
| Engine | PostgreSQL 15.8 | Aurora Serverless v2 |
| Min ACU | 0.5 | ~$43/month minimum |
| Max ACU | 4 | Auto-scales with usage |
| IAM Auth | Enabled (backup) | For direct cluster admin access |
| Encryption | Yes | At-rest encryption |
| Backup | 35 days | Automatic daily backups |
| Removal Policy | SNAPSHOT | Creates backup on deletion |
Primary: RDS Proxy with Password Auth
The application connects through RDS Proxy using password-based authentication via Secrets Manager:
- DatabaseStack creates a proxy user with credentials stored in Secrets Manager (
openhands/database/proxy-user) - RDS Proxy handles connection pooling and credential rotation
- Fargate App Service receives the DB password via ECS native secret injection
- No token refresh needed β RDS Proxy manages connection lifecycle
Backup: IAM Authentication
IAM auth is also enabled on the cluster for direct admin access:
- Used by the DB bootstrap Lambda (custom resource) for one-time setup
- Can be used for manual admin tasks via
aws rds generate-db-auth-token
The db-bootstrap Lambda function (custom resource) automatically runs on first deployment:
- Creates the proxy user with appropriate permissions
- Grants schema-level permissions for future tables
- No manual SQL setup required
App Service β Orchestrator Lambda (via Cloud Map DNS)
β
DynamoDB Registry β EventBridge (idle monitor, task state)
β
ECS Fargate Tasks (per-conversation sandbox)
β
EFS Access Points (per-conversation isolation)
| Action | Trigger | What Happens |
|---|---|---|
| Start | User creates conversation | Orchestrator: create EFS AP β register task def β RunTask β update DynamoDB |
| Resume | User resumes archived conversation | Same as Start (AP may already exist) |
| Stop | User stops conversation | Orchestrator: StopTask β cleanup AP |
| Idle Timeout | EventBridge (5-min schedule) | Idle Monitor Lambda: detect stale tasks β StopTask β delete AP |
| Task Crash | ECS task state change event | Task State Lambda: update DynamoDB β delete AP |
| Attribute | Type | Description |
|---|---|---|
conversation_id |
PK | Conversation UUID |
user_id |
GSI | For user-scoped queries |
status |
GSI | RUNNING, STOPPED, etc. |
task_arn |
String | ECS Fargate task ARN |
access_point_id |
String | EFS access point ID |
task_definition_arn |
String | Per-conversation task def |
last_activity_at |
Number | Unix timestamp for idle detection |
Each sandbox mounts an EFS access point rooted at /sandbox-workspace/<conversation_id>/:
- Container sees
/mnt/efs/= access point root (cannot traverse to parent/sibling directories) - Workspace files at
/mnt/efs/workspace/persist across task restarts - SDK conversation cache at
/mnt/efs/<CID_hex>/events/enables LLM context restoration
| Data Type | Storage | Written By | Persistence |
|---|---|---|---|
| Conversation Metadata | Aurora PostgreSQL | App server | Permanent |
| V1 Conversation Events | S3 (FILE_STORE=s3) |
App server via S3EventService |
Permanent (authority for UI) |
| User Settings / Secrets | S3 | App server | Permanent (KMS encrypted) |
| Workspace Files | EFS | Sandbox agent-server | Persistent (per-conversation AP) |
| SDK Conversation Cache | EFS | Sandbox SDK | Persistent (LLM context restoration) |
| Sandbox State | DynamoDB | Orchestrator | Permanent (task registry) |
# openhands/storage/locations.py
def get_conversation_dir(sid, user_id=None):
if user_id:
return f'users/{user_id}/conversations/{sid}/' # User-specific path
else:
return f'sessions/{sid}/' # Fallback (no user)Important: Without proper user_id from CognitoUserAuth, conversations would be stored in sessions/ and not associated with any user.
| Setting | Value | Purpose |
|---|---|---|
| Encryption | SSE-S3 | At-rest encryption |
| Versioning | Enabled | 30-day retention for old versions |
| Public Access | Blocked | All public access blocked |
| SSL | Enforced | HTTPS only |
| Removal Policy | RETAIN | Data preserved if stack deleted |
When a Fargate task is replaced (deployment, scaling, health check failure):
-
ECS Service launches new task:
- Fargate pulls container images from ECR
- Mounts EFS app workspace at
/data/openhands - Injects secrets via ECS native secret injection (DB password, sandbox secret key)
-
OpenHands App Startup:
- Connects to Aurora via RDS Proxy (password auth, no token refresh)
- Loads existing conversation metadata
- Connects to S3 for conversation events (via
S3EventService, wired by Patch 33)
-
User Access:
- Authenticates via Cognito (unchanged)
- Lambda@Edge injects user headers
- OpenHands retrieves user's conversations from Aurora
- User sees all previous conversations
-
Sandbox Resume:
- When user accesses an archived conversation, orchestrator creates new sandbox task
- EFS access point preserves workspace files from previous session
- SDK conversation cache enables LLM context restoration
# 1. Create a conversation in the application
# 2. Force Fargate app service redeployment
aws ecs update-service --cluster <cluster-name> \
--service openhands-app --force-new-deployment \
--region <region>
# 3. Wait for new task to stabilize (2-5 minutes)
aws ecs describe-services --cluster <cluster-name> \
--services openhands-app \
--query 'services[0].{running:runningCount,desired:desiredCount,pending:pendingCount}' \
--region <region>
# 4. Verify conversations persist
# - Log in to application
# - Previous conversations should be visible
# - Resume an archived conversation to verify workspace files persist