From a571a48f0420b68889f1c068797c0008ab84474c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CSebastian?= <64795732+slegarraga@users.noreply.github.com> Date: Fri, 22 May 2026 10:09:49 -0400 Subject: [PATCH] fix(oas): allow bearer scopes on operations in OpenAPI 3.1 OAS 3.1 permits scope lists on http bearer security requirements without defining those scopes on the security scheme. Skip scope validation for bearer schemes when the document is OpenAPI 3.1+. Fixes #2643 --- .../oas3-operation-security-defined.test.ts | 29 +++++++++++++++++++ .../src/oas/functions/oasSecurityDefined.ts | 16 ++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/rulesets/src/oas/__tests__/oas3-operation-security-defined.test.ts b/packages/rulesets/src/oas/__tests__/oas3-operation-security-defined.test.ts index 2046871f7..aa49f3acd 100644 --- a/packages/rulesets/src/oas/__tests__/oas3-operation-security-defined.test.ts +++ b/packages/rulesets/src/oas/__tests__/oas3-operation-security-defined.test.ts @@ -210,4 +210,33 @@ testRule('oas3-operation-security-defined', [ }, ], }, + + { + name: 'oas3.1: bearer http scopes on operation without scheme-level scopes', + document: { + openapi: '3.1.0', + info: { title: 'test', version: '1.0.0' }, + paths: { + '/users': { + get: { + security: [ + { + bearerAuth: ['read:users', 'public'], + }, + ], + }, + }, + }, + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'jwt', + }, + }, + }, + }, + errors: [], + }, ]); diff --git a/packages/rulesets/src/oas/functions/oasSecurityDefined.ts b/packages/rulesets/src/oas/functions/oasSecurityDefined.ts index 1043a9bc2..e4174c275 100644 --- a/packages/rulesets/src/oas/functions/oasSecurityDefined.ts +++ b/packages/rulesets/src/oas/functions/oasSecurityDefined.ts @@ -33,6 +33,8 @@ export default createRulesetFunction, Options>( if (!isPlainObject(document.data)) return; + const openapiVersion = typeof document.data.openapi === 'string' ? document.data.openapi : ''; + const allDefs = oasVersion === 2 ? document.data.securityDefinitions @@ -58,7 +60,7 @@ export default createRulesetFunction, Options>( const scope = input[schemeName]; for (let i = 0; i < scope.length; i++) { const scopeName = scope[i]; - if (!isScopeDefined(oasVersion, scopeName, allDefs[schemeName])) { + if (!isScopeDefined(oasVersion, scopeName, allDefs[schemeName], openapiVersion)) { results ??= []; results.push({ message: `"${scopeName}" must be listed among scopes.`, @@ -72,9 +74,19 @@ export default createRulesetFunction, Options>( }, ); -function isScopeDefined(oasVersion: 2 | 3, scopeName: string, securityScheme: unknown): boolean { +function isScopeDefined(oasVersion: 2 | 3, scopeName: string, securityScheme: unknown, openapiVersion = ''): boolean { if (!isPlainObject(securityScheme)) return false; + // OpenAPI 3.1 allows scope lists on http bearer requirements without scheme-level scope definitions + if ( + oasVersion === 3 && + openapiVersion.startsWith('3.1') && + securityScheme.type === 'http' && + securityScheme.scheme === 'bearer' + ) { + return true; + } + if (oasVersion === 2) { return isPlainObject(securityScheme.scopes) && scopeName in securityScheme.scopes; }