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

# Transport Security

> Configure CORS, bind address, DNS rebinding protection, and host validation for production deployments

FrontMCP provides transport-level security controls for CORS, network binding, DNS rebinding protection, and host header validation. The defaults are the safe choice — the server binds loopback and sends no CORS headers — so reaching it from another host or another origin is something you opt into. In production, FrontMCP logs a security audit at startup and offers a **strict mode** that enables the remaining protections at once.

<Warning>
  **Changed in v1.7.0.** `security.bindAddress` now defaults to `'loopback'` (was `'0.0.0.0'`), and omitting `cors` now sends no CORS headers (was `{ origin: true }`). A container or VM that must be reachable from outside now needs an explicit opt-in — see [Bind Address](#bind-address). See `BREAKING_CHANGES.v1.md` entries BC-033 and BC-034.
</Warning>

<Warning>
  **Changed in v1.7.2 (BC-035).** `security.dnsRebindingProtection` now defaults to **on**, with `allowedHosts` derived from what the server listens on. A proxied deployment should name its public hostname — see [DNS Rebinding Protection](#dns-rebinding-protection). Fixes GHSA-mc9g-v2cp-vfff.
</Warning>

## Quick Start

Enable strict security mode for production:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  http: {
    port: 3000,
    cors: {
      origin: ['https://app.example.com'],
      credentials: false,
    },
    security: {
      strict: true,
      dnsRebindingProtection: {
        enabled: true,
        allowedHosts: ['api.example.com', 'api.example.com:3000'],
        allowedOrigins: ['https://app.example.com'],
      },
    },
  },
})
```

## Security Options

| Field                                            | Type                              | Default      | Description                          |
| ------------------------------------------------ | --------------------------------- | ------------ | ------------------------------------ |
| `security.strict`                                | boolean                           | `false`      | Enable all security features at once |
| `security.bindAddress`                           | `'loopback'` \| `'all'` \| string | `'loopback'` | Network bind address                 |
| `security.dnsRebindingProtection.enabled`        | boolean                           | `true`       | Validate Host and Origin headers     |
| `security.dnsRebindingProtection.allowedHosts`   | string\[]                         | *derived*    | Allowed Host header values           |
| `security.dnsRebindingProtection.allowedOrigins` | string\[]                         | ---          | Allowed Origin header values         |

## CORS Configuration

By default FrontMCP sends **no CORS headers at all**. A cross-origin request still reaches the server and is served normally — the browser simply refuses to let the calling page read the response. Non-browser clients (the MCP CLI, an agent runtime, curl) are unaffected: CORS is a browser rule, **not server-side access control**. If you need to keep callers out, use authentication.

If a browser on another origin needs access, say so explicitly:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  http: {
    cors: {
      origin: ['https://app.example.com', 'https://admin.example.com'],
      credentials: false,
      maxAge: 600,
    },
  },
})
```

`cors: false` is the same runtime state as omitting the option — no headers — and exists only to say so explicitly:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  http: {
    cors: false,
  },
})
```

To restore the pre-v1.7.0 permissive behaviour, ask for it by name:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  http: {
    cors: { origin: true },
  },
})
```

<Warning>
  `origin: true` reflects the request origin header, effectively allowing **any** website to make cross-origin requests — including any page a developer happens to have open while a local server is running. It should never be used in production.
</Warning>

## Bind Address

Controls which network interface the server listens on. **The default is `127.0.0.1`** — a server that says nothing about security is local-only.

| Value        | Binds To    | Use Case                                         |
| ------------ | ----------- | ------------------------------------------------ |
| `'loopback'` | `127.0.0.1` | Local-only access (development, reverse proxy)   |
| `'all'`      | `0.0.0.0`   | All interfaces (distributed pods, direct access) |
| IP string    | Specific IP | Custom network binding                           |

The effective address is resolved in this order, first match wins:

1. `http.security.bindAddress` in the server config
2. The `FRONTMCP_BIND_ADDRESS` environment variable (`all`, `loopback`, or a literal address)
3. Strict mode — `0.0.0.0` for a distributed deployment, `127.0.0.1` otherwise
4. Distributed deployment mode — `0.0.0.0`
5. The default — `127.0.0.1`

### Containers and VMs

A container publishes a port and expects the process inside to listen on every interface, but a Dockerfile cannot reach into the server's TypeScript config. Set the environment variable instead — no rebuild required:

```dockerfile theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
ENV FRONTMCP_BIND_ADDRESS=all
EXPOSE 3000
CMD ["node", "dist/main.js"]
```

```yaml theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
services:
  app:
    environment:
      - FRONTMCP_BIND_ADDRESS=all
```

Projects scaffolded by `frontmcp create` with the Docker target already set this.

<Note>
  Distributed builds (`frontmcp build --target distributed`, which sets `FRONTMCP_DEPLOYMENT_MODE=distributed`) bind all interfaces automatically — a distributed deployment must be reachable by its peers. Serverless targets (Vercel, Lambda, Cloudflare) never bind a port at all, so this setting does not apply to them.
</Note>

### Strict Mode Behavior

When `security.strict: true`:

* **Standalone mode**: binds to `127.0.0.1` (loopback only)
* **Distributed mode**: binds to `0.0.0.0` (pods need external access)

### Explicit Override

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
security: {
  bindAddress: 'loopback', // Always 127.0.0.1, regardless of deployment mode
}
```

<Tip>
  When running behind a reverse proxy (NGINX, Traefik, Envoy), bind to loopback and let the proxy handle external traffic.
</Tip>

## DNS Rebinding Protection

Validates the HTTP `Host`, `X-Forwarded-Host` and `Origin` headers against an allowlist. A request naming a host this server does not answer to gets `403 Forbidden` — before routing and before the body is read, so the MCP endpoint, the OAuth routes, the SSE transport and any custom route are all covered.

**On by default since v1.7.2.** You only configure it when the derived default cannot know your hostname.

### What the default allows

With no `allowedHosts` configured, the list is derived from what the process actually listens on:

* `localhost`, `127.0.0.1`, `[::1]` — each with and without the bound port
* the specific NIC address, when the server binds one

Matching is case-insensitive, and `example.com` / `example.com:80` / `example.com:443` compare equal.

### Deployments behind a proxy

A server bound to a routable address (`0.0.0.0`, `::`, a specific NIC) is reached under a hostname the process cannot know. FrontMCP does **not** enforce a derived list there — it logs a warning and leaves host checking off, so upgrading a patch version does not take a proxied deployment offline. Name your public host to turn it on:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
security: {
  dnsRebindingProtection: {
    allowedHosts: ['api.example.com', 'api.example.com:8443'],
    allowedOrigins: ['https://app.example.com'],
  },
}
```

Or via the environment, which pairs with `FRONTMCP_BIND_ADDRESS` in a container:

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
FRONTMCP_ALLOWED_HOSTS=api.example.com,api.example.com:8443
```

To turn it off entirely:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
security: { dnsRebindingProtection: { enabled: false } }
```

### Semantics

* **`allowedHosts`**: matched against `Host`, and against every hop of `X-Forwarded-Host` when present. A poisoned forwarded host is rejected even when `Host` itself is valid, because issuer and OAuth-discovery URLs can be derived from it.
* **`allowedOrigins`**: matched against `Origin`, scheme included.
* A request with **no** `Origin` is allowed through: non-browser clients never send one, and a rebound page always does.

<Note>
  A DNS rebinding attack points an attacker-controlled domain at `127.0.0.1`, so the victim's browser treats a request to a local server as same-origin. Binding to loopback does not help — loopback is the destination — and neither does CORS, because the browser genuinely considers the request same-origin. Validating `Host` is the server-side defence. See GHSA-mc9g-v2cp-vfff.
</Note>

## Request Body Limits

FrontMCP's Express host applies a default request body limit of `'4mb'` to
both `express.json()` and `express.urlencoded()` — lifting body-parser's
silent 100KB default, which routinely rejected base64-encoded blobs (PDFs,
DOCXes, large HTML inputs) before they reached MCP tool handlers (issue
\#410). Override the limits via the `http` block on `@FrontMcp`:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  info: { name: 'my-server', version: '1.0.0' },
  http: {
    bodyLimit: '500kb',       // tighten for public-facing deployments
    urlencodedLimit: '100kb', // optional — falls back to bodyLimit when omitted
  },
})
class Server {}
```

| Option            | Type               | Default                   | Notes                                                                           |
| ----------------- | ------------------ | ------------------------- | ------------------------------------------------------------------------------- |
| `bodyLimit`       | `number \| string` | `'4mb'`                   | Accepts bytes (number) or body-parser strings (`'4mb'`, `'500kb'`, `'2gb'`, …). |
| `urlencodedLimit` | `number \| string` | falls back to `bodyLimit` | Independent override for `application/x-www-form-urlencoded` bodies.            |

When a request exceeds the configured limit, the adapter returns HTTP 413
with a structured JSON-RPC envelope:

```json theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": -32600,
    "message": "Payload Too Large",
    "data": { "limit": 102400, "length": 204800 }
  }
}
```

<Warning>
  **Security trade-off.** Body-parser buffers the full request body in
  memory before parsing, so raising `bodyLimit` scales per-request memory
  with concurrency. Deployments exposed to untrusted networks should set an
  explicit lower bound (e.g. `'500kb'` or `'1mb'`) sized for the largest
  legitimate payload. The 100KB → 4MB default change in this release is a
  liberalization — every request that succeeded before still succeeds, but
  the implicit DoS guard is gone unless you set the option yourself.
</Warning>

Custom `hostFactory` users build their own Express app and are
**not affected** by `bodyLimit`/`urlencodedLimit` — those options are
consumed only by the built-in `ExpressHostAdapter`. Custom-host
deployments must configure their own body limits.

## Security Audit Warnings

In production (`NODE_ENV=production`) or distributed mode, FrontMCP logs security warnings at startup:

```
[Security] CORS_ORIGIN_TRUE: CORS origin=true allows all origins to make cross-origin requests.
[Security] BIND_ALL_INTERFACES: Server bound to 0.0.0.0 — accessible from all network interfaces.
[Security] DNS_REBINDING_UNPROTECTED: DNS rebinding protection is disabled.
[Security] STRICT_MODE_HINT: To enable strict security defaults, set security.strict = true.
```

A server on the defaults logs only info-level findings — `CORS_DISABLED` and `BIND_RESTRICTED` — because the defaults are already the safe choice. The audit reports; it never changes behaviour. Use it to check what your configuration actually exposes before going to production.

## Production Checklist

* [ ] Set explicit `cors.origin` (not `true`) — only if a browser on another origin needs access
* [ ] Set `security.dnsRebindingProtection.allowedHosts` (or `FRONTMCP_ALLOWED_HOSTS`) to your public hostname — on a routable bind the derived default is not enforced
* [ ] Confirm the bind address: loopback behind a reverse proxy, `FRONTMCP_BIND_ADDRESS=all` in a container
* [ ] Configure TLS termination at the reverse proxy layer
* [ ] Set `NODE_ENV=production` for security audit warnings
* [ ] Review startup logs for `[Security]` warnings
* [ ] Tune `http.bodyLimit` to your largest legitimate payload

## Example: Full Production Config

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  info: { name: 'my-server', version: '1.0.0' },
  apps: [MyApp],
  http: {
    port: 3000,
    cors: {
      origin: ['https://app.example.com'],
      credentials: false,
      maxAge: 600,
    },
    security: {
      strict: true,
      bindAddress: 'loopback',
      dnsRebindingProtection: {
        enabled: true,
        allowedHosts: ['localhost:3000'],
        allowedOrigins: ['https://app.example.com'],
      },
    },
  },
  redis: { provider: 'redis', host: 'redis', port: 6379 },
  transport: {
    protocol: 'modern',
    persistence: {
      redis: { provider: 'redis', host: 'redis', port: 6379 },
    },
  },
})
```

## Related

<CardGroup cols={2}>
  <Card title="Security Headers & CSP" icon="shield-halved" href="/frontmcp/deployment/security-headers">
    Content Security Policy, HSTS, and X-Frame-Options
  </Card>

  <Card title="High Availability" icon="server" href="/frontmcp/deployment/high-availability">
    Distributed sessions, heartbeat, and session takeover
  </Card>

  <Card title="Redis Setup" icon="database" href="/frontmcp/deployment/redis-setup">
    Redis connection and session store configuration
  </Card>

  <Card title="Production Build" icon="rocket" href="/frontmcp/deployment/production-build">
    Build and deploy for production
  </Card>
</CardGroup>
