> ## Documentation Index
> Fetch the complete documentation index at: https://meepa.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> Security model, hardening, and best practices for MeepaGateway deployments.

## 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 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 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:

| 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 |

**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 keys** — `cptn_` 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:

| 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                    |

See [Isolation](/gateway/container) 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](/gateway/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](mailto:bea@bogpad.io)**.
