Skip to content

Commit a571a48

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 076f0d2 commit a571a48

2 files changed

Lines changed: 43 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: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export default createRulesetFunction<Record<string, string[]>, Options>(
3333

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

36+
const openapiVersion = typeof document.data.openapi === 'string' ? document.data.openapi : '';
37+
3638
const allDefs =
3739
oasVersion === 2
3840
? document.data.securityDefinitions
@@ -58,7 +60,7 @@ export default createRulesetFunction<Record<string, string[]>, Options>(
5860
const scope = input[schemeName];
5961
for (let i = 0; i < scope.length; i++) {
6062
const scopeName = scope[i];
61-
if (!isScopeDefined(oasVersion, scopeName, allDefs[schemeName])) {
63+
if (!isScopeDefined(oasVersion, scopeName, allDefs[schemeName], openapiVersion)) {
6264
results ??= [];
6365
results.push({
6466
message: `"${scopeName}" must be listed among scopes.`,
@@ -72,9 +74,19 @@ export default createRulesetFunction<Record<string, string[]>, Options>(
7274
},
7375
);
7476

75-
function isScopeDefined(oasVersion: 2 | 3, scopeName: string, securityScheme: unknown): boolean {
77+
function isScopeDefined(oasVersion: 2 | 3, scopeName: string, securityScheme: unknown, openapiVersion = ''): boolean {
7678
if (!isPlainObject(securityScheme)) return false;
7779

80+
// OpenAPI 3.1 allows scope lists on http bearer requirements without scheme-level scope definitions
81+
if (
82+
oasVersion === 3 &&
83+
openapiVersion.startsWith('3.1') &&
84+
securityScheme.type === 'http' &&
85+
securityScheme.scheme === 'bearer'
86+
) {
87+
return true;
88+
}
89+
7890
if (oasVersion === 2) {
7991
return isPlainObject(securityScheme.scopes) && scopeName in securityScheme.scopes;
8092
}

0 commit comments

Comments
 (0)