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 defaultChange it with
Who may connectEveryone: the public auth modeauth
Where the Node server listens127.0.0.1http.security.bindAddress
Host names it answersOn the Node server, only loopback names while it listens on loopback; on another address, and under createFetchHandler(), any name until you list themallowedHosts, CORS and host checks
CORSNone on MCP responseshttp.cors
Request bodiesJSON over 4 MB refused with 413Body size limits
Response headersX-Content-Type-Options: nosniff and X-Frame-Options: DENY, no X-Powered-By; no HSTS or CSPhttp.securityHeaders
Sessions of clients before MCP 2026-07-28The 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 requestOlder clients
Tool argumentsChecked against inputSchema before execute() runs; arguments it doesn't declare are droppedValidating input
Outgoing requeststhis.fetch() sends no caller token, and doesn't follow redirects while it carries credentialsWhat this.fetch() adds
Rate limitsNone server-wide; a tool's own rateLimit, concurrency and timeout applyRate limits
The old HTTP+SSE transportOn: GET /sse opens a session for anyone who askstransport.protocol
With NODE_ENV=productionInternal 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 startupProduction 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_SECRET in local and remote mode, 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 in forwardCallerTokenTo, and the OpenAPI adapter only with passthroughCallerToken (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/token answers 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:

  • 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). 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 local mode, a token's scopes are limited to allowedScopes (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 filter picks 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/sdk 1.32.1, which fixes a high-severity advisory (Security headers and transport).
  • Skilled OpenAPI applies only signed bundles, and its npm source doesn't load while verifyProvenance is on (Sources). The OpenAPI adapter doesn't fetch external $refs (loadOptions).
  • frontmcp registry publish proves 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 inputSchema before execute() 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, globalThis and fetch are refused (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). 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 authorities on 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, local and remote modes check every request to the MCP endpoint, tools/list and server/discover included (Auth modes).
  • An identity provider's token is checked for its algorithm (HS256 and none refused), 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 S256 PKCE, 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.1 with 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 and frontmcp registry publish lists 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.