Skip to content

Commit 997f718

Browse files
committed
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
1 parent 682295a commit 997f718

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

packages/rulesets/src/oas/__tests__/oas3-operation-security-defined.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,33 @@ testRule('oas3-operation-security-defined', [
210210
},
211211
],
212212
},
213+
214+
{
215+
name: 'oas3.1: bearer http scopes on operation without scheme-level scopes',
216+
document: {
217+
openapi: '3.1.0',
218+
info: { title: 'test', version: '1.0.0' },
219+
paths: {
220+
'/users': {
221+
get: {
222+
security: [
223+
{
224+
bearerAuth: ['read:users', 'public'],
225+
},
226+
],
227+
},
228+
},
229+
},
230+
components: {
231+
securitySchemes: {
232+
bearerAuth: {
233+
type: 'http',
234+
scheme: 'bearer',
235+
bearerFormat: 'jwt',
236+
},
237+
},
238+
},
239+
},
240+
errors: [],
241+
},
213242
]);

packages/rulesets/src/oas/functions/oasSecurityDefined.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export default createRulesetFunction<Record<string, string[]>, Options>(
3333

3434
if (!isPlainObject(document.data)) return;
3535

36+
const openapiVersion =
37+
typeof document.data.openapi === 'string' ? document.data.openapi : '';
38+
3639
const allDefs =
3740
oasVersion === 2
3841
? document.data.securityDefinitions
@@ -58,7 +61,7 @@ export default createRulesetFunction<Record<string, string[]>, Options>(
5861
const scope = input[schemeName];
5962
for (let i = 0; i < scope.length; i++) {
6063
const scopeName = scope[i];
61-
if (!isScopeDefined(oasVersion, scopeName, allDefs[schemeName])) {
64+
if (!isScopeDefined(oasVersion, scopeName, allDefs[schemeName], openapiVersion)) {
6265
results ??= [];
6366
results.push({
6467
message: `"${scopeName}" must be listed among scopes.`,
@@ -72,9 +75,24 @@ export default createRulesetFunction<Record<string, string[]>, Options>(
7275
},
7376
);
7477

75-
function isScopeDefined(oasVersion: 2 | 3, scopeName: string, securityScheme: unknown): boolean {
78+
function isScopeDefined(
79+
oasVersion: 2 | 3,
80+
scopeName: string,
81+
securityScheme: unknown,
82+
openapiVersion = '',
83+
): boolean {
7684
if (!isPlainObject(securityScheme)) return false;
7785

86+
// OpenAPI 3.1 allows scope lists on http bearer requirements without scheme-level scope definitions
87+
if (
88+
oasVersion === 3 &&
89+
openapiVersion.startsWith('3.1') &&
90+
securityScheme.type === 'http' &&
91+
securityScheme.scheme === 'bearer'
92+
) {
93+
return true;
94+
}
95+
7896
if (oasVersion === 2) {
7997
return isPlainObject(securityScheme.scopes) && scopeName in securityScheme.scopes;
8098
}

0 commit comments

Comments
 (0)