Security
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 on the repository's Security tab, or by email, as FrontMCP's security policy 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. Fixed vulnerabilities are published as GitHub security advisories, and each release's changes are in the release notes.
The defaults
What a server does before you configure anything:
| By default | Change it with | |
|---|---|---|
| Who may connect | Everyone: the public auth mode | auth |
| Where the Node server listens | 127.0.0.1 | http.security.bindAddress |
| 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, CORS and host checks |
| CORS | None on MCP responses | http.cors |
| Request bodies | JSON over 4 MB refused with 413 | 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 |
| 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 |
| Tool arguments | Checked against inputSchema before execute() runs; arguments it doesn't declare are dropped | Validating input |
| Outgoing requests | this.fetch() sends no caller token, and doesn't follow redirects while it carries credentials | What this.fetch() adds |
| Rate limits | None server-wide; a tool's own rateLimit, concurrency and timeout apply | Rate limits |
| The old HTTP+SSE transport | On: GET /sse opens a session for anyone who asks | transport.protocol |
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, 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), approval on a tool without the Approval plugin (approval), and an app with its own auth on the shared endpoint of a public or static server (Auth for one app). Unknown or misspelled auth options, though, are dropped without an error (Caveats).
Before production
Auth in production 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. Security headers and transport covers HSTS, a CSP, body limits and what the Node server exposes without credentials.
The OWASP MCP Top 10
The OWASP 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.
MCP01 Token mismanagement and secret exposure
FrontMCP:
- In production, a server that needs a secret, like
JWT_SECRETinlocalandremotemode, won't start without it, and won't make one up (Production mode, Keys and signing). - The caller's token stays on the server:
this.fetch()sends it only to origins inforwardCallerTokenTo, and the OpenAPI adapter only withpassthroughCallerToken(this.fetch(), 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). - Static keys are compared in constant time and never reach your code (Static keys). Tokens FrontMCP issues work only on the server that issued them (Tokens FrontMCP issues), and
/oauth/tokenanswers aren't cached (Endpoints). - Debug logs of request headers redact the ones that carry credentials (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, Endpoints). Your code can read the caller's token, and what it logs isn't redacted.
MCP02 Privilege escalation via scope creep
FrontMCP:
authoritiesrules refuse a tool, resource, prompt, skill or agent before it runs, and leave it out of the caller's lists (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, CodeCall). - In
localmode, a token's scopes are limited toallowedScopes(Options). The Approval plugin can refuse a tool until an approval is recorded for the caller (approval).
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). Hiding a tool doesn't refuse calls to it (Hiding versus refusing), and a job with no permissions can be run by anyone (@Job).
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), and two apps' entries with the same name are both kept, each prefixed with its app's id (Name clashes).
- Tools from another MCP server are served under a namespace, and
filterpicks which ones (What clients see). Skilled OpenAPI applies a bundle only with a valid signature (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, Running someone else's code). Adding an app can rename entries clients already use.
MCP04 Software supply chain attacks and dependency tampering
FrontMCP:
- It depends on
@modelcontextprotocol/sdk1.32.1, which fixes a high-severity advisory (Security headers and transport). - Skilled OpenAPI applies only signed bundles, and its
npmsource doesn't load whileverifyProvenanceis on (Sources). The OpenAPI adapter doesn't fetch external$refs (loadOptions). frontmcp registry publishproves you own a listing's namespace, and each version can be published once (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). Install from your lockfile in production (Running the bundle).
MCP05 Command injection and execution
FrontMCP:
- Arguments are checked against
inputSchemabeforeexecute()runs, and undeclared ones are dropped (Validating input, Caveats). - Scripts a model writes for CodeCall are checked before they run:
eval,Function,import(),require,process,globalThisandfetchare refused (Security). Skilled OpenAPI's scripts are interpreted step by step, with nofetchand noprocess. register_job, which runs a script a client sends, is off unless you turn it on (Built-in tools). The OpenAPI adapter encodes path parameters and refuses header values with line breaks (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).
MCP06 Prompt injection via contextual payloads
FrontMCP:
- What a model is talked into sending still goes through argument validation and
authoritieson every call. - In the OpenAPI adapter, a credential from the server always wins over one the model sends (How credentials are chosen).
- CodeCall stops call sequences that look like exfiltration, such as listing data and sending it out (Limits). The Approval plugin can hold a tool until a person approves it (How a call is checked), and
this.elicit()can ask the user before a tool acts (this.elicit()).
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).
MCP07 Insufficient authentication and authorization
FrontMCP:
- The
static,transparent,localandremotemodes check every request to the MCP endpoint,tools/listandserver/discoverincluded (Auth modes). - An identity provider's token is checked for its algorithm (
HS256andnonerefused), issuer, expiry and subject (What's checked), with keys fetched over HTTPS from public addresses (Where the keys come from). - As its own OAuth 2.1 server, FrontMCP lets only registered clients in, accepts only
S256PKCE, turns off dynamic registration in production, and ties each sign-in to the browser that started it (Local auth, The sign-in cookie). Its sign-in pages carry a strict CSP and a CSRF token (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, Rate limits). local mode's built-in sign-in page checks nothing; it's for development (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). The Node server logs its network settings at startup (The startup audit).
- With
@frontmcp/observability: OpenTelemetry spans for each request, call and outgoing fetch, with the caller and the authorities result (Spans); request logs (Request logs); and/metrics(Metrics). - A hook can record every authorization decision (Auditing decisions). CodeCall logs its audit events (Audit events), and Skilled OpenAPI can keep a signed, chained audit log (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.1with host checks (http.security), and in production it logs a warning when it listens on every interface without a list of hosts (The startup audit). - To make a server known,
@FrontMcp({ registry })serves a proof of its domain andfrontmcp registry publishlists it in the 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), and production hides stacks and internal messages (What production hides). - Each request has its own context. The Cache plugin keeps an entry per caller (Who gets which entry), a caller can't reach another's remembered values (Scopes), and a job run is readable only by the user who started it (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).
Left to you: in production, an authorities refusal still names the roles that would have let the caller in (What production hides). Remember's global scope and a cache with keyByIdentity: false are shared by every caller.