diff --git a/packages/ruleset/src/rules/parameter-casing-convention.js b/packages/ruleset/src/rules/parameter-casing-convention.js index f234b1c1..c7906844 100644 --- a/packages/ruleset/src/rules/parameter-casing-convention.js +++ b/packages/ruleset/src/rules/parameter-casing-convention.js @@ -45,7 +45,7 @@ module.exports = { // Spectral casing convention types aren't robust enough to handle // the complexity of headers, so we define our own kebab/pascal case regex. header: { - match: '/^[A-Z]+[a-z0-9]*-*([A-Z]+[a-z0-9]*-*)*$/', + match: '/^[A-Z][A-Za-z0-9]*(?:-[A-Z][A-Za-z0-9]*)*$/', }, // Define an alternate message for the header pattern validation diff --git a/packages/ruleset/src/rules/schema-casing-convention.js b/packages/ruleset/src/rules/schema-casing-convention.js index 76579a13..b3cefacc 100644 --- a/packages/ruleset/src/rules/schema-casing-convention.js +++ b/packages/ruleset/src/rules/schema-casing-convention.js @@ -15,7 +15,7 @@ module.exports = { then: { function: schemaCasingConvention, functionOptions: { - match: '/^[A-Z]+[a-z0-9]+([A-Z]+[a-z0-9]*)*$/', + match: '/^[A-Z](?:[A-Z]*[a-z0-9]+)+$/', }, }, }; diff --git a/packages/ruleset/test/rules/parameter-casing-convention.test.js b/packages/ruleset/test/rules/parameter-casing-convention.test.js index b6f828b5..0d337f26 100644 --- a/packages/ruleset/test/rules/parameter-casing-convention.test.js +++ b/packages/ruleset/test/rules/parameter-casing-convention.test.js @@ -178,6 +178,37 @@ describe(`Spectral rule: ${ruleId}`, () => { const results = await testRule(ruleId, rule, testDocument); expect(results).toHaveLength(0); }); + + it('Should handle potential ReDoS patterns efficiently', async () => { + const testDocument = makeCopy(rootDocument); + + // Pattern that would cause catastrophic backtracking in old regex + // Old pattern: /^[A-Z]+[a-z0-9]*-*([A-Z]+[a-z0-9]*-*)*$/ + // This input would cause exponential time complexity + const maliciousHeader = 'A' + 'A-'.repeat(50); + + testDocument.paths['/v1/movies'].get.parameters.push({ + description: 'Header with ReDoS attack pattern', + name: maliciousHeader, + required: false, + in: 'header', + schema: { + type: 'string', + }, + }); + + const startTime = Date.now(); + const results = await testRule(ruleId, rule, testDocument); + const duration = Date.now() - startTime; + + // Should complete quickly (well under 500ms) with fixed regex + // Note: Includes test overhead; actual regex execution is much faster + expect(duration).toBeLessThan(500); + // Should still validate correctly and reject the malformed header + expect(results).toHaveLength(1); + expect(results[0].code).toBe(ruleId); + expect(results[0].message).toBe(expectedMsgHeader); + }); }); describe('Should yield errors', () => { diff --git a/packages/ruleset/test/rules/schema-casing-convention.test.js b/packages/ruleset/test/rules/schema-casing-convention.test.js index 67a89087..ad9e5094 100644 --- a/packages/ruleset/test/rules/schema-casing-convention.test.js +++ b/packages/ruleset/test/rules/schema-casing-convention.test.js @@ -65,6 +65,33 @@ describe(`Spectral rule: ${ruleId}`, () => { const results = await testRule(ruleId, rule, testDocument); expect(results).toHaveLength(0); }); + + it('Should handle potential ReDoS patterns efficiently', async () => { + const testDocument = makeCopy(rootDocument); + + // Pattern that would cause catastrophic backtracking in old regex + // Old pattern: /^[A-Z]+[a-z0-9]+([A-Z]+[a-z0-9]*)*$/ + // This input would cause exponential time complexity + const maliciousSchemaName = 'A' + 'Aa'.repeat(50); + + testDocument.components.schemas[maliciousSchemaName] = { + type: 'object', + properties: { + id: { type: 'string' }, + }, + }; + + const startTime = Date.now(); + const results = await testRule(ruleId, rule, testDocument); + const duration = Date.now() - startTime; + + // Should complete quickly (well under 500ms) with fixed regex + // Note: Includes test overhead; actual regex execution is much faster + expect(duration).toBeLessThan(500); + // Should still validate correctly - this pattern should actually pass + // as it matches the upper camel case pattern + expect(results).toHaveLength(0); + }); }); describe('Should yield errors', () => {