Network security
Loopback binding
By default, Captain binds to127.0.0.1:63372 — only reachable from your local machine. This is the safest configuration for single-user deployments.
Even on loopback, MeepaGateway validates the Origin header on all WebSocket upgrade requests. This prevents cross-site WebSocket hijacking (CSWSH), where a malicious website could connect to your local gateway through your browser.
Allowed WebSocket origins on loopback:
| Origin host | Allowed |
|---|---|
localhost | Yes |
127.0.0.1 | Yes |
[::1] | Yes |
No Origin header (CLI/native) | Yes |
| Any other origin | Blocked (403) |
Non-loopback binding
When Captain binds to any address other than127.0.0.1 or ::1 (e.g. 0.0.0.0), the auth middleware enforces full authentication on every request — a valid session cookie or API key is required.
The bind address is set via captain.bind in config.yaml or the --bind CLI flag. Common non-loopback setups:
| Setup | Bind address | Auth enforced | TLS |
|---|---|---|---|
| Local dev (default) | 127.0.0.1 | No (loopback bypass) | Not needed |
| Reverse proxy (Caddy/Nginx) | 127.0.0.1 | No (proxy handles access) | Proxy terminates TLS |
| Tailscale | 127.0.0.1 | No (Tailscale handles access) | Tailscale handles TLS |
| Direct LAN/public | 0.0.0.0 | Yes | Use a reverse proxy for TLS |
Webhook signature verification
Discord and Slack webhooks are cryptographically verified:- Discord — Ed25519 signature verification using
x-signature-ed25519andx-signature-timestampheaders against the app’s public key. Configurepublic_keyin your Discord connector config. - Slack — HMAC-SHA256 verification using
x-slack-signatureandx-slack-request-timestampheaders against the signing secret. Includes 5-minute replay protection. Configuresigning_secretin your Slack connector config. - Telegram — Secret token verification (already existed).
404 response (no information leakage about agent existence).
Webhook rate limiting
Hook endpoints are rate-limited to 60 requests per minute per IP. Exceeding the limit returns429 Too Many Requests with a Retry-After: 60 header.
SSRF protection
User-supplied URLs in skill installation are validated before fetching:- Only
https://URLs accepted (no plaintext HTTP — prevents MITM tampering of skill content) - DNS resolution performed before connection; private/reserved IP ranges blocked (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, etc.)
- Redirects followed manually with re-validation at each hop (DNS rebinding defense)
Content Security Policy
Captain dashboard enforces CSP:script-src 'self' 'sha256-...' (no unsafe-inline). Style-src allows inline for Tailwind CSS.
Authentication
Captain supports three authentication methods:- Password — Argon2-hashed, set during first-time setup
- API keys —
cptn_prefixed tokens for programmatic access
Session cookies
Thecaptain_session cookie is set with:
HttpOnly— not accessible to JavaScriptSameSite=Lax— prevents CSRF from cross-origin navigationSecure— only sent over HTTPS (automatically omitted when bound to loopback, since HTTP localhost is safe and loopback traffic never leaves the machine)
CORS
Captain enforces same-origin CORS policy. Cross-origin HTTP requests to the Captain API are blocked. Only requests from the dashboard itself (same origin) are permitted.Agent isolation
Agents execute shell commands as part of their tool use. MeepaGateway provides three isolation levels to control what agents can access. During first-time setup, you’ll be asked to choose one:| Level | Protection | Use case |
|---|---|---|
| Container (recommended) | Full Docker isolation | Production, untrusted agents, multi-tenant |
| Kernel sandbox | Landlock (Linux) or Seatbelt (macOS) | Lightweight isolation when Docker isn’t available |
| Unrestricted | No restrictions | Trusted local development only |
Isolation settings protection
Changing isolation settings (container mode, sandbox mode, file access restrictions) via the API now returns a warning that changes take effect only after the agent is restarted. This prevents weakening isolation on a running agent.Path validation
- Credential filenames are sanitized and canonicalized; null bytes and directory traversal sequences are rejected
- Skill names only allow alphanumeric characters, hyphens, underscores, and single dots
- Container mount paths are canonicalized before Docker bind mount to close TOCTOU race windows
Secrets management
Agent secrets (API keys, tokens) are encrypted at rest using a gateway-level encryption key. They are only decrypted in memory when passed to the agent runtime.- Gateway secrets: stored in
secrets.gatewayconfig section - Per-agent secrets: stored in
secrets.agents.{id}config section - Encryption key: generated during setup, stored in config
Best practices
- Keep
bind: 127.0.0.1unless you need remote access - Use a strong, unique password
- Create dedicated API keys for CI/CD and revoke them when no longer needed
- Use container isolation for agents that run untrusted code
- Keep MeepaGateway updated — run
meepagateway updateregularly
