Skip to main content

Network security

Loopback binding

By default, Captain binds to 127.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 hostAllowed
localhostYes
127.0.0.1Yes
[::1]Yes
No Origin header (CLI/native)Yes
Any other originBlocked (403)

Non-loopback binding

When Captain binds to any address other than 127.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:
SetupBind addressAuth enforcedTLS
Local dev (default)127.0.0.1No (loopback bypass)Not needed
Reverse proxy (Caddy/Nginx)127.0.0.1No (proxy handles access)Proxy terminates TLS
Tailscale127.0.0.1No (Tailscale handles access)Tailscale handles TLS
Direct LAN/public0.0.0.0YesUse a reverse proxy for TLS
Always use TLS when Captain is reachable over a network. Use a reverse proxy (Caddy, Nginx) or a tunnel service (Tailscale, Cloudflare) for TLS termination.

Webhook signature verification

Discord and Slack webhooks are cryptographically verified:
  • Discord — Ed25519 signature verification using x-signature-ed25519 and x-signature-timestamp headers against the app’s public key. Configure public_key in your Discord connector config.
  • Slack — HMAC-SHA256 verification using x-slack-signature and x-slack-request-timestamp headers against the signing secret. Includes 5-minute replay protection. Configure signing_secret in your Slack connector config.
  • Telegram — Secret token verification (already existed).
Unverified webhook deliveries are rejected with a uniform 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 returns 429 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 keyscptn_ prefixed tokens for programmatic access
On loopback, authentication is bypassed for convenience (except WebSocket origin checks). On all other bind addresses, every API request requires a valid session cookie or API key.

Session cookies

The captain_session cookie is set with:
  • HttpOnly — not accessible to JavaScript
  • SameSite=Lax — prevents CSRF from cross-origin navigation
  • Secure — 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:
LevelProtectionUse case
Container (recommended)Full Docker isolationProduction, untrusted agents, multi-tenant
Kernel sandboxLandlock (Linux) or Seatbelt (macOS)Lightweight isolation when Docker isn’t available
UnrestrictedNo restrictionsTrusted local development only
See Isolation for detailed configuration.

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.gateway config section
  • Per-agent secrets: stored in secrets.agents.{id} config section
  • Encryption key: generated during setup, stored in config
See Credentials for the credential flow.

Best practices

  • Keep bind: 127.0.0.1 unless 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 update regularly

Reporting vulnerabilities

If you find a security issue, please email bea@bogpad.io.