# Security

> How to report a vulnerability in FrontMCP, the defaults a new server starts with, what to set before production, and what FrontMCP does about each risk in the OWASP MCP Top 10, and what it leaves to you.

Source: https://frontmcp.dev/reference/security

[Covers FrontMCP 1.10.7](https://frontmcp.dev/versions#how-pages-are-checked) as of October 20, 2026

A new FrontMCP server is reachable only from its own machine: it listens on `127.0.0.1`, answers only loopback host names, sends no CORS headers and refuses JSON bodies over 4 MB. It isn't authenticated: with no `auth`, everyone who can reach it may call it. This page says how to report a vulnerability, lists the defaults and what to change before production, and goes through the OWASP MCP Top 10: what FrontMCP does about each risk, and what it leaves to you. Every line links to the page that documents it, checked against FrontMCP 1.10.7.

---

## Reporting a vulnerability

Don't open a public issue. Report it privately on GitHub, with [Report a vulnerability](https://github.com/agentfront/frontmcp/security/advisories/new) on the repository's Security tab, or by email, as FrontMCP's [security policy](https://github.com/agentfront/frontmcp/blob/main/SECURITY.md) says. The policy aims to acknowledge a report within 48 hours and to update you within 7 days.

Only the latest minor release gets security fixes, and every `@frontmcp/*` package and the `frontmcp` CLI are released together, at the same version: upgrade with [Versions](https://frontmcp.dev/versions#upgrading). Fixed vulnerabilities are published as [GitHub security advisories](https://github.com/agentfront/frontmcp/security/advisories), and each release's changes are in the [release notes](https://frontmcp.dev/releases).

## The defaults

What a server does before you configure anything:

| | By default | Change it with |
| --- | --- | --- |
| Who may connect | Everyone: the `public` auth mode | [`auth`](https://frontmcp.dev/reference/auth/modes#auth) |
| Where the Node server listens | `127.0.0.1` | [`http.security.bindAddress`](https://frontmcp.dev/reference/deployment/security#httpsecurity) |
| Host names it answers | On the Node server, only loopback names while it listens on loopback; on another address, and under `createFetchHandler()`, any name until you list them | [`allowedHosts`](https://frontmcp.dev/reference/deployment/security#answering-only-your-host-names), [CORS and host checks](https://frontmcp.dev/reference/sdk/create-fetch-handler#cors-and-host-checks) |
| CORS | None on MCP responses | [`http.cors`](https://frontmcp.dev/reference/auth/production#hosts-origins-and-cors) |
| Request bodies | JSON over 4 MB refused with `413` | [Body size limits](https://frontmcp.dev/reference/deployment/security#body-size-limits) |
| Response headers | `X-Content-Type-Options: nosniff` and `X-Frame-Options: DENY`, no `X-Powered-By`; no HSTS or CSP | [`http.securityHeaders`](https://frontmcp.dev/reference/deployment/security#turning-on-hsts-and-a-csp) |
| Sessions of clients before MCP 2026-07-28 | The id is encrypted with AES-256-GCM, holds no credential, and works only with the token or key that opened it; credentials are checked on every request | [Older clients](https://frontmcp.dev/reference/auth/tokens#older-clients) |
| Tool arguments | Checked against `inputSchema` before `execute()` runs; arguments it doesn't declare are dropped | [Validating input](https://frontmcp.dev/reference/sdk/tool#validating-input) |
| Outgoing requests | `this.fetch()` sends no caller token, and doesn't follow redirects while it carries credentials | [What `this.fetch()` adds](https://frontmcp.dev/reference/sdk/fetch#what-it-adds) |
| Rate limits | None server-wide; a tool's own `rateLimit`, `concurrency` and `timeout` apply | [Rate limits](https://frontmcp.dev/reference/auth/production#rate-limits) |
| The old HTTP+SSE transport | On: `GET /sse` opens a session for anyone who asks | [`transport.protocol`](https://frontmcp.dev/reference/deployment/security#what-the-node-server-exposes) |
| With `NODE_ENV=production` | Internal errors reach callers as an error id, with no stack; secrets FrontMCP would otherwise make up are refused; the Node server logs its network settings at startup | [Production mode](https://frontmcp.dev/reference/auth/production#production-mode), [the startup audit](https://frontmcp.dev/reference/deployment/security#the-startup-audit) |

FrontMCP also refuses to start on several mistakes: an `authorities` rule that checks nothing or names a profile that doesn't exist ([Rules](https://frontmcp.dev/reference/auth/authorities#rules)), `approval` on a tool without the Approval plugin ([`approval`](https://frontmcp.dev/reference/plugins/approval#the-approval-tool-option)), and an app with its own `auth` on the shared endpoint of a `public` or `static` server ([Auth for one app](https://frontmcp.dev/reference/auth/modes#auth-for-one-app)). Unknown or misspelled `auth` options, though, are dropped without an error ([Caveats](https://frontmcp.dev/reference/auth/modes#caveats)).

## Before production

[Auth in production](https://frontmcp.dev/reference/auth/production#the-checklist) is the checklist: `NODE_ENV=production` and the secrets it requires, HTTPS and your public URL, your host names and CORS origins, a strict check of the token's audience, rate limits, what production still tells callers, and the [gaps to work around](https://frontmcp.dev/reference/auth/production#gaps-to-work-around). [Security headers and transport](https://frontmcp.dev/reference/deployment/security) covers HSTS, a CSP, body limits and what the Node server exposes without credentials.

## The OWASP MCP Top 10

The [OWASP MCP Top 10](https://owasp.org/www-project-mcp-top-10/) lists the main risks of MCP servers; its 2025 edition is in beta. Here is what FrontMCP does about each, by default or once you turn it on, and what it leaves to you. One plugin has a table of its own: [Skilled OpenAPI](https://frontmcp.dev/reference/plugins/skilled-openapi#what-the-checks-protect-against).

### MCP01 Token mismanagement and secret exposure

**FrontMCP:**

- In production, a server that needs a secret, like `JWT_SECRET` in `local` and `remote` mode, won't start without it, and won't make one up ([Production mode](https://frontmcp.dev/reference/auth/production#production-mode), [Keys and signing](https://frontmcp.dev/reference/auth/local#keys-and-signing)).
- The caller's token stays on the server: `this.fetch()` sends it only to origins in `forwardCallerTokenTo`, and the OpenAPI adapter only with `passthroughCallerToken` ([`this.fetch()`](https://frontmcp.dev/reference/sdk/fetch#server-options), [How credentials are chosen](https://frontmcp.dev/reference/adapters/openapi#how-credentials-are-chosen)). These operation requests don't follow redirects while carrying credentials. Separately, the OpenAPI adapter refuses spec-URL redirects by default; if you enable them, spec-download headers are dropped once a redirect leaves the original origin ([Loading the spec from a URL](https://frontmcp.dev/reference/adapters/openapi#loading-the-spec-from-a-url)).
- Static keys are compared in constant time and never reach your code ([Static keys](https://frontmcp.dev/reference/auth/tokens#static-keys)). Tokens FrontMCP issues work only on the server that issued them ([Tokens FrontMCP issues](https://frontmcp.dev/reference/auth/tokens#tokens-frontmcp-issues)), and `/oauth/token` answers aren't cached ([Endpoints](https://frontmcp.dev/reference/auth/local#endpoints)).
- Debug logs of request headers redact the ones that carry credentials ([Seeing request headers](https://frontmcp.dev/reference/server/logging#seeing-request-headers)).

**Left to you:** tokens FrontMCP issues are signed with one shared HS256 secret, with no list of old keys and no revocation endpoint ([Secrets](https://frontmcp.dev/reference/auth/production#secrets), [Endpoints](https://frontmcp.dev/reference/auth/local#endpoints)). Your code can read the caller's token, and what it logs isn't redacted.

### MCP02 Privilege escalation via scope creep

**FrontMCP:**

- `authorities` rules refuse a tool, resource, prompt, skill or agent before it runs, and leave it out of the caller's lists ([Authorities on an entry](https://frontmcp.dev/reference/auth/authorities#authorities-on-an-entry)). A rule that checks nothing stops the server at startup.
- A tool that calls another with `this.callTool()`, and a CodeCall script, are checked as the original caller ([Caveats](https://frontmcp.dev/reference/auth/authorities#caveats), [CodeCall](https://frontmcp.dev/reference/plugins/codecall#security)).
- In `local` mode, a token's scopes are limited to `allowedScopes` ([Options](https://frontmcp.dev/reference/auth/local#options)). The Approval plugin can refuse a tool until an approval is recorded for the caller ([`approval`](https://frontmcp.dev/reference/plugins/approval#the-approval-tool-option)).

**Left to you:** in `local` and `remote` mode, a user gets every allowed scope their client asks for, so base access on who the user is ([Scopes](https://frontmcp.dev/reference/auth/local#scopes)). Hiding a tool doesn't refuse calls to it ([Hiding versus refusing](https://frontmcp.dev/reference/auth/authorities#hiding-versus-refusing)), and a job with no `permissions` can be run by anyone ([`@Job`](https://frontmcp.dev/reference/sdk/job#options)).

### MCP03 Tool poisoning

**FrontMCP:**

- Your tools' names and descriptions come from your code. A tool registered at runtime can't take an existing tool's name ([Errors](https://frontmcp.dev/reference/sdk/register-tool#errors)), and two apps' entries with the same name are both kept, each prefixed with its app's id ([Name clashes](https://frontmcp.dev/reference/server/apps#name-clashes)).
- Tools from another MCP server are served under a namespace, and `filter` picks which ones ([What clients see](https://frontmcp.dev/reference/server/remote#what-clients-see)). Skilled OpenAPI applies a bundle only with a valid signature ([Signing](https://frontmcp.dev/reference/plugins/skilled-openapi#signing)).

**Left to you:** tools from another MCP server, an OpenAPI spec or an npm package keep their descriptions, and a remote tool's arguments aren't checked against its `inputSchema` ([What clients see](https://frontmcp.dev/reference/server/remote#what-clients-see), [Running someone else's code](https://frontmcp.dev/reference/server/esm#running-someone-elses-code)). Adding an app can rename entries clients already use.

### MCP04 Software supply chain attacks and dependency tampering

**FrontMCP:**

- It depends on `@modelcontextprotocol/sdk` 1.32.1, which fixes a high-severity advisory ([Security headers and transport](https://frontmcp.dev/reference/deployment/security#reference)).
- Skilled OpenAPI applies only signed bundles, and its `npm` source doesn't load while `verifyProvenance` is on ([Sources](https://frontmcp.dev/reference/plugins/skilled-openapi#sources)). The OpenAPI adapter doesn't fetch external `$ref`s ([`loadOptions`](https://frontmcp.dev/reference/adapters/openapi#loadoptions)).
- `frontmcp registry publish` proves you own a listing's namespace, and each version can be published once ([Publishing a listing](https://frontmcp.dev/reference/deployment/mcp-registry#publishing-a-listing)).

**Left to you:** `App.esm()` runs an npm package's code from esm.sh without an integrity check, so pin exact versions ([Running someone else's code](https://frontmcp.dev/reference/server/esm#running-someone-elses-code)). Install from your lockfile in production ([Running the bundle](https://frontmcp.dev/reference/deployment/node#running-the-bundle)).

### MCP05 Command injection and execution

**FrontMCP:**

- Arguments are checked against `inputSchema` before `execute()` runs, and undeclared ones are dropped ([Validating input](https://frontmcp.dev/reference/sdk/tool#validating-input), [Caveats](https://frontmcp.dev/reference/sdk/tool#caveats)).
- Scripts a model writes for CodeCall are checked before they run: `eval`, `Function`, `import()`, `require`, `process`, `globalThis` and `fetch` are refused ([Security](https://frontmcp.dev/reference/plugins/codecall#security)). Skilled OpenAPI's scripts are interpreted step by step, with no `fetch` and no `process`.
- `register_job`, which runs a script a client sends, is off unless you turn it on ([Built-in tools](https://frontmcp.dev/reference/sdk/job#built-in-tools)). The OpenAPI adapter encodes path parameters and refuses header values with line breaks ([What a call sends](https://frontmcp.dev/reference/adapters/openapi#what-a-call-sends)).

**Left to you:** what your own tools do with their input. CodeCall's sandbox is built on Node's `vm`, which Node doesn't document as a security boundary on its own ([Security](https://frontmcp.dev/reference/plugins/codecall#security)).

### MCP06 Prompt injection via contextual payloads

**FrontMCP:**

- What a model is talked into sending still goes through argument validation and `authorities` on every call.
- In the OpenAPI adapter, a credential from the server always wins over one the model sends ([How credentials are chosen](https://frontmcp.dev/reference/adapters/openapi#how-credentials-are-chosen)).
- CodeCall stops call sequences that look like exfiltration, such as listing data and sending it out ([Limits](https://frontmcp.dev/reference/plugins/codecall#limits)). The Approval plugin can hold a tool until a person approves it ([How a call is checked](https://frontmcp.dev/reference/plugins/approval#how-a-call-is-checked)), and `this.elicit()` can ask the user before a tool acts ([`this.elicit()`](https://frontmcp.dev/reference/sdk/elicit#caveats)).

**Left to you:** FrontMCP doesn't look at what tools, resources or APIs return. Treat that content as you'd treat user input ([What the checks protect against](https://frontmcp.dev/reference/plugins/skilled-openapi#what-the-checks-protect-against)).

### MCP07 Insufficient authentication and authorization

**FrontMCP:**

- The `static`, `transparent`, `local` and `remote` modes check every request to the MCP endpoint, `tools/list` and `server/discover` included ([Auth modes](https://frontmcp.dev/reference/auth/modes#reference)).
- An identity provider's token is checked for its algorithm (`HS256` and `none` refused), issuer, expiry and subject ([What's checked](https://frontmcp.dev/reference/auth/tokens#whats-checked)), with keys fetched over HTTPS from public addresses ([Where the keys come from](https://frontmcp.dev/reference/auth/tokens#where-the-keys-come-from)).
- As its own OAuth 2.1 server, FrontMCP lets only registered clients in, accepts only `S256` PKCE, turns off dynamic registration in production, and ties each sign-in to the browser that started it ([Local auth](https://frontmcp.dev/reference/auth/local#options), [The sign-in cookie](https://frontmcp.dev/reference/auth/local#the-sign-in-cookie)). Its sign-in pages carry a strict CSP and a CSRF token ([Security](https://frontmcp.dev/reference/auth/login-ui#security)).

**Left to you:** choosing a mode: without `auth`, the server is public. A token without an audience is accepted unless you set `requireAudience`, and nothing is rate-limited until you configure `throttle` ([Token checks](https://frontmcp.dev/reference/auth/production#token-checks), [Rate limits](https://frontmcp.dev/reference/auth/production#rate-limits)). `local` mode's built-in sign-in page checks nothing; it's for development ([Gaps to work around](https://frontmcp.dev/reference/auth/production#gaps-to-work-around)).

### MCP08 Lack of audit and telemetry

**FrontMCP:**

- In production, an internal error reaches the caller as an id, and the log has the message and stack next to the same id ([Production mode](https://frontmcp.dev/reference/auth/production#production-mode)). The Node server logs its network settings at startup ([The startup audit](https://frontmcp.dev/reference/deployment/security#the-startup-audit)).
- With `@frontmcp/observability`: OpenTelemetry spans for each request, call and outgoing fetch, with the caller and the authorities result ([Spans](https://frontmcp.dev/reference/server/observability#spans)); request logs ([Request logs](https://frontmcp.dev/reference/server/observability#request-logs)); and `/metrics` ([Metrics](https://frontmcp.dev/reference/server/observability#metrics)).
- A hook can record every authorization decision ([Auditing decisions](https://frontmcp.dev/reference/auth/authorities#auditing-decisions)). CodeCall logs its audit events ([Audit events](https://frontmcp.dev/reference/plugins/codecall#audit-events)), and Skilled OpenAPI can keep a signed, chained audit log ([Audit log](https://frontmcp.dev/reference/plugins/skilled-openapi#audit-log)).

**Left to you:** turning these on, and sending them somewhere: tracing needs a tracer provider outside development, and there's no signed audit log for the whole server.

### MCP09 Shadow MCP servers

**FrontMCP:**

- It can't find servers nobody registered, but it makes an unintended one harder to reach: the Node server listens on `127.0.0.1` with host checks ([`http.security`](https://frontmcp.dev/reference/deployment/security#httpsecurity)), and in production it logs a warning when it listens on every interface without a list of hosts ([The startup audit](https://frontmcp.dev/reference/deployment/security#the-startup-audit)).
- To make a server known, `@FrontMcp({ registry })` serves a proof of its domain and `frontmcp registry publish` lists it in the MCP Registry ([Publishing a listing](https://frontmcp.dev/reference/deployment/mcp-registry#publishing-a-listing)).

**Left to you:** knowing which servers your organization runs.

### MCP10 Context injection and over-sharing

**FrontMCP:**

- A result is trimmed to the tool's `outputSchema` ([Options](https://frontmcp.dev/reference/sdk/tool#options)), and production hides stacks and internal messages ([What production hides](https://frontmcp.dev/reference/auth/production#what-production-hides-and-what-it-doesnt)).
- Each request has its own context. The Cache plugin keeps an entry per caller ([Who gets which entry](https://frontmcp.dev/reference/plugins/cache#who-gets-which-entry)), a caller can't reach another's remembered values ([Scopes](https://frontmcp.dev/reference/plugins/remember#scopes)), and a job run is readable only by the user who started it ([Who can read a run](https://frontmcp.dev/reference/sdk/job#who-can-read-a-run)).
- The state of a question to the user is signed, bound to the caller, the tool and its arguments, and expires after 10 minutes ([What happens on the wire](https://frontmcp.dev/learn/asking-the-user#what-happens-on-the-wire)).

**Left to you:** in production, an `authorities` refusal still names the roles that would have let the caller in ([What production hides](https://frontmcp.dev/reference/auth/production#what-production-hides-and-what-it-doesnt)). Remember's `global` scope and a cache with `keyByIdentity: false` are shared by every caller.
