Do not use this file to submit new security issues. For responsible disclosure (what to send, scope, timelines), see SECURITY.md in the repository root.
The sections below are implementation and architecture notes for developers and auditors.
- All channel credentials (bot tokens, signing secrets) encrypted at rest with AES-256-GCM
- Encryption key: 32-byte hex from
ENCRYPTION_KEYenv var - Implementation lives in the active controller-side secret and crypto helpers
- Credentials decrypted only when needed (config generation, signature verification)
- Credentials must never appear in logs, error messages, or API responses
- All incoming Slack events verified via HMAC-SHA256
- Signing secret retrieved from encrypted
channel_credentials - 5-minute timestamp window enforced
- Timing-safe comparison (
crypto.timingSafeEqual) - Implementation follows the active controller/runtime event handling path
- better-auth with email/password registration
- HTTP-only session cookies
authMiddlewarevalidates session for all/v1/*routes- Configured in the active controller auth stack
Internal endpoints (/api/internal/*) use a two-tier token system:
| Token | Env var | Purpose | Who holds it |
|---|---|---|---|
| Internal token | INTERNAL_API_TOKEN |
Privileged operations (config, secrets mgmt, skill sync) | Controller-managed local runtime only |
| Skill token | SKILL_API_TOKEN |
Skill-facing operations (fetch scoped secrets, record artifacts) | OpenClaw child process (via env) |
Middleware:
requireInternalToken(c)β accepts onlyINTERNAL_API_TOKENrequireSkillToken(c)β acceptsSKILL_API_TOKENorINTERNAL_API_TOKEN(superset)- Both use
crypto.timingSafeEqualfor constant-time comparison - Implementation follows the active controller internal-auth middleware
Endpoint mapping:
| Endpoint | Auth |
|---|---|
GET /config, GET /config/latest, GET /config/versions/:v |
requireInternalToken |
POST /register, POST /heartbeat |
requireInternalToken |
PUT /secrets |
requireInternalToken |
POST /sessions, PATCH /sessions/:id, POST /sessions/sync-discord |
requireInternalToken |
GET /secrets/:skillName |
requireSkillToken |
POST /artifacts, PATCH /artifacts/:id |
requireSkillToken |
POST /composio/execute, POST /composio/disconnect |
requireSkillToken |
The gateway strips privileged env vars before spawning the OpenClaw child process:
INTERNAL_API_TOKENβ not inherited by OpenClawENCRYPTION_KEYβ not inherited by OpenClawSKILL_API_TOKENβ inherited, used by skills to fetch scoped secrets
nexu-context.json (written by gateway sidecar) contains only apiUrl, poolId, and agents β no tokens or secrets on disk.
- Secrets stored in
pool_secretstable, encrypted with AES-256-GCM - Each secret has a
scopefield:pool(available to all skills) orskill:<name>(specific skill only) - Skills fetch their secrets at runtime via
GET /api/internal/secrets/:skillNameusingSKILL_API_TOKEN - API returns only secrets where
scope = 'pool'ORscope = 'skill:<skillName>'
- Production: AWS Secrets Manager β External Secrets Operator β K8s Secrets
- Local dev:
.envfile (never committed) - Required:
DATABASE_URL,ENCRYPTION_KEY,INTERNAL_API_TOKEN,SKILL_API_TOKEN,SLACK_CLIENT_ID,SLACK_CLIENT_SECRET,SLACK_SIGNING_SECRET,BETTER_AUTH_SECRET - Optional:
LITELLM_BASE_URL,LITELLM_API_KEY
- Slack OAuth state tokens stored in
oauth_statestable with expiry - State verified on callback to prevent CSRF
- Tokens marked as used after consumption (single-use)
- Third-party OAuth flows (Gmail, Google Calendar, etc.) managed via Composio SDK
user_integrationstable tracks per-user OAuth connection stateintegration_credentialsstores encrypted credential material (AES-256-GCM)- OAuth state parameter stored in
user_integrations.oauth_statefor CSRF prevention - Connection URLs generated server-side (
composio-routes.ts) β never crafted client-side composio-exec.jsruns in OpenClaw child process withSKILL_API_TOKENonly (noINTERNAL_API_TOKEN)- Auth endpoint:
POST /api/internal/composio/executerequiresrequireSkillToken - Disconnect endpoint:
POST /api/internal/composio/disconnectrequiresrequireSkillToken
- No credentials in log output or error messages
- New API endpoints behind
authMiddleware,requireInternalToken, orrequireSkillToken - Encrypted storage for any new secret material
- Slack signature verification for any new webhook endpoint
- No
ENCRYPTION_KEYor tokens in committed code