From 73a815350fc9d1d408eb2596340a3f94d16576b8 Mon Sep 17 00:00:00 2001 From: Isaac Mills Date: Sat, 9 May 2026 11:54:51 -0600 Subject: [PATCH 1/5] Adjust names of security schemes to match prefixed name --- packages/json-schema-ref-parser/src/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/json-schema-ref-parser/src/index.ts b/packages/json-schema-ref-parser/src/index.ts index 1acf036591..111594b18f 100644 --- a/packages/json-schema-ref-parser/src/index.ts +++ b/packages/json-schema-ref-parser/src/index.ts @@ -461,6 +461,14 @@ export class $RefParser { } } else if (k === 'tags' && Array.isArray(v) && v.every((x) => typeof x === 'string')) { out[k] = v.map((t) => tagMap.get(t) || t); + } else if (k === 'security' && Array.isArray(v)) { + out[k] = v.map((s) => { + const securityScheme: Record = {}; + for (const [key, value] of Object.entries(s)) { + securityScheme[`${opIdPrefix}_${key}`] = value; + } + return securityScheme; + }); } else if (k === 'operationId' && typeof v === 'string') { out[k] = unique(usedOpIds, `${opIdPrefix}_${v}`); } else { From 91ab2b0fb55fa788ae0dfcdeb1229b21fb2e3323 Mon Sep 17 00:00:00 2001 From: Isaac Mills Date: Mon, 11 May 2026 13:00:57 -0600 Subject: [PATCH 2/5] Add regression test for security schema merging bug --- .../src/__tests__/bundle.test.ts | 85 +++++++++++++++++++ packages/json-schema-ref-parser/src/index.ts | 16 ++-- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts b/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts index 00b7ae0023..6b58106f4d 100644 --- a/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts +++ b/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts @@ -527,6 +527,91 @@ describe('bundle', () => { expect(pathKeys).toHaveLength(1); }); + it('prefixes operation-level security scheme names with source prefix', async () => { + const refParser = new $RefParser(); + const spec1 = { + components: { + securitySchemes: { + bearerAuth: { + bearerFormat: 'JWT', + scheme: 'bearer', + type: 'http', + }, + }, + }, + info: { title: 'Spec 1', version: '1.0.0' }, + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'listUsers', + responses: { '200': { description: 'OK' } }, + security: [{ bearerAuth: [] }], + }, + }, + }, + }; + const spec2 = { + components: { + securitySchemes: { + bearerAuth: { + bearerFormat: 'JWT', + scheme: 'bearer', + type: 'http', + }, + oauth2: { + flows: { + authorizationCode: { + authorizationUrl: 'https://example.com/oauth/authorize', + scopes: { read: 'read access', write: 'write access' }, + tokenUrl: 'https://example.com/oauth/token', + }, + }, + type: 'oauth2', + }, + }, + }, + info: { title: 'Spec 2', version: '1.0.0' }, + openapi: '3.0.0', + paths: { + '/orders': { + get: { + operationId: 'listOrders', + responses: { '200': { description: 'OK' } }, + security: [{ oauth2: ['read'] }, { bearerAuth: [] }], + }, + }, + }, + }; + + const merged = (await refParser.bundleMany({ + pathOrUrlOrSchemas: [ + { ...spec1, $id: 'file:///spec1.json' }, + { ...spec2, $id: 'file:///spec2.json' }, + ], + })) as any; + + const spec1Prefix = 'spec1'; + const spec2Prefix = 'spec2'; + + console.dir(merged, { depth: null }); + + // Component security scheme names should be prefixed + expect(merged.components.securitySchemes[`${spec1Prefix}_bearerAuth`]).toBeDefined(); + expect(merged.components.securitySchemes[`${spec2Prefix}_bearerAuth`]).toBeDefined(); + expect(merged.components.securitySchemes[`${spec2Prefix}_oauth2`]).toBeDefined(); + + // Operation-level security references should also be prefixed + const usersSecurity = merged.paths['/users'].get.security; + expect(usersSecurity).toEqual([{ [`${spec1Prefix}_bearerAuth`]: [] }]); + + const ordersSecurity = merged.paths['/orders'].get.security; + expect(ordersSecurity).toEqual([ + { [`${spec2Prefix}_oauth2`]: ['read'] }, + { [`${spec2Prefix}_bearerAuth`]: [] }, + ]); + }); + it('adds prefix to path when HTTP methods conflict', async () => { const refParser = new $RefParser(); const spec1 = { diff --git a/packages/json-schema-ref-parser/src/index.ts b/packages/json-schema-ref-parser/src/index.ts index 111594b18f..44eecd33ea 100644 --- a/packages/json-schema-ref-parser/src/index.ts +++ b/packages/json-schema-ref-parser/src/index.ts @@ -42,15 +42,20 @@ export function getResolvedInput({ resolvedInput.path = url.fromFileSystemPath(resolvedInput.path); resolvedInput.type = 'file'; } else if (!resolvedInput.path && pathOrUrlOrSchema && typeof pathOrUrlOrSchema === 'object') { - if ('$id' in pathOrUrlOrSchema && pathOrUrlOrSchema.$id) { + if ( + ('openapi' in pathOrUrlOrSchema && pathOrUrlOrSchema.openapi) || + ('swagger' in pathOrUrlOrSchema && pathOrUrlOrSchema.swagger) + ) { + resolvedInput.schema = pathOrUrlOrSchema; + resolvedInput.type = 'json'; + if ('$id' in pathOrUrlOrSchema && pathOrUrlOrSchema.$id) + resolvedInput.path = pathOrUrlOrSchema.$id as string; + } else if ('$id' in pathOrUrlOrSchema && pathOrUrlOrSchema.$id) { // when schema id has defined an URL should use that hostname to request the references, // instead of using the current page URL const { hostname, protocol } = new URL(pathOrUrlOrSchema.$id as string); resolvedInput.path = `${protocol}//${hostname}:${protocol === 'https:' ? 443 : 80}`; resolvedInput.type = 'url'; - } else { - resolvedInput.schema = pathOrUrlOrSchema; - resolvedInput.type = 'json'; } } @@ -385,7 +390,8 @@ export class $RefParser { const baseName = (p: string) => { try { const withoutHash = p.split('#')[0]!; - const parts = withoutHash.split('/'); + const withoutTrailingSlash = withoutHash.replace(/\/+$/, ''); + const parts = withoutTrailingSlash.split('/'); const filename = parts[parts.length - 1] || 'schema'; const dot = filename.lastIndexOf('.'); const raw = dot > 0 ? filename.substring(0, dot) : filename; From 526eeba9d816108aa4be12242a064d83e62e45a2 Mon Sep 17 00:00:00 2001 From: Isaac Mills <57533634+StratusFearMe21@users.noreply.github.com> Date: Fri, 15 May 2026 01:30:10 -0600 Subject: [PATCH 3/5] Remove debug console.dir from regression test Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com> --- packages/json-schema-ref-parser/src/__tests__/bundle.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts b/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts index 6b58106f4d..07adda01ef 100644 --- a/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts +++ b/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts @@ -594,7 +594,7 @@ describe('bundle', () => { const spec1Prefix = 'spec1'; const spec2Prefix = 'spec2'; - console.dir(merged, { depth: null }); + // Component security scheme names should be prefixed expect(merged.components.securitySchemes[`${spec1Prefix}_bearerAuth`]).toBeDefined(); From e7cf98b8c88f4812b86ed22826419bd60e55122f Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 09:01:23 +0000 Subject: [PATCH 4/5] fix: restore inline JSON Schema object fallback in getResolvedInput Without the else branch, an inline object input that has neither openapi/swagger nor $id falls through with schema undefined and type 'url', which broke the package's general-purpose JSON Schema bundling contract. --- packages/json-schema-ref-parser/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/json-schema-ref-parser/src/index.ts b/packages/json-schema-ref-parser/src/index.ts index 44eecd33ea..a99ff5c411 100644 --- a/packages/json-schema-ref-parser/src/index.ts +++ b/packages/json-schema-ref-parser/src/index.ts @@ -56,6 +56,9 @@ export function getResolvedInput({ const { hostname, protocol } = new URL(pathOrUrlOrSchema.$id as string); resolvedInput.path = `${protocol}//${hostname}:${protocol === 'https:' ? 443 : 80}`; resolvedInput.type = 'url'; + } else { + resolvedInput.schema = pathOrUrlOrSchema; + resolvedInput.type = 'json'; } } From 7339164e64ce520fe4ff6f51a2216ee25ecc3c63 Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 09:02:14 +0000 Subject: [PATCH 5/5] chore: trim leftover blank lines from regression test --- packages/json-schema-ref-parser/src/__tests__/bundle.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts b/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts index 07adda01ef..0a151c06cb 100644 --- a/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts +++ b/packages/json-schema-ref-parser/src/__tests__/bundle.test.ts @@ -594,8 +594,6 @@ describe('bundle', () => { const spec1Prefix = 'spec1'; const spec2Prefix = 'spec2'; - - // Component security scheme names should be prefixed expect(merged.components.securitySchemes[`${spec1Prefix}_bearerAuth`]).toBeDefined(); expect(merged.components.securitySchemes[`${spec2Prefix}_bearerAuth`]).toBeDefined();