From 6960a75265d771683e833e6992c854542f65d548 Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Fri, 13 Mar 2026 10:46:53 +0530 Subject: [PATCH 1/2] Add pattern_not_regex support to reduce false positives in custom rules Implements optional pattern_not_regex field for custom rules that allows excluding matches that also match a negative pattern. This reduces false positives by enabling users to specify patterns that should NOT be flagged, such as validatedMessageId or known safe functions in DataWeave email headers. Changes: - Added pattern_not_regex field to RegexRule type with validation - Implemented negative pattern matching in engine scan logic - Added documentation and examples in config description - Created test data files for email header injection scenarios - Added 5 comprehensive tests covering validation and functionality - Bumped version to 0.34.0-SNAPSHOT --- .../code-analyzer-regex-engine/package.json | 2 +- .../code-analyzer-regex-engine/src/config.ts | 10 +++ .../code-analyzer-regex-engine/src/engine.ts | 17 ++++ .../src/messages.ts | 12 +++ .../test/engine.test.ts | 83 +++++++++++++++++++ .../test/plugin.test.ts | 34 ++++++++ .../emailHeaders_WithUnsanitizedPayload.dwl | 11 +++ .../emailHeaders_WithValidatedId.dwl | 13 +++ 8 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithUnsanitizedPayload.dwl create mode 100644 packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithValidatedId.dwl diff --git a/packages/code-analyzer-regex-engine/package.json b/packages/code-analyzer-regex-engine/package.json index d3d3b273..84ae6ddc 100644 --- a/packages/code-analyzer-regex-engine/package.json +++ b/packages/code-analyzer-regex-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-regex-engine", "description": "Plugin package that adds 'regex' as an engine into Salesforce Code Analyzer", - "version": "0.33.0", + "version": "0.34.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", diff --git a/packages/code-analyzer-regex-engine/src/config.ts b/packages/code-analyzer-regex-engine/src/config.ts index 13498d63..c41c3a7c 100644 --- a/packages/code-analyzer-regex-engine/src/config.ts +++ b/packages/code-analyzer-regex-engine/src/config.ts @@ -31,6 +31,11 @@ export type RegexRule = { // The regular expression that triggers a violation when matched against the contents of a file. regex: string; + // [Optional] The negative pattern - matches that also match this pattern will be excluded from violations. + // This allows you to exclude false positives by specifying patterns that should NOT be flagged. + // Example: regex: /(To|From):\s*\$\([^)]+\)/ with pattern_not_regex: /\$\(validatedMessageId\)/ + pattern_not_regex?: string; + // The extensions of the files that you would like to test the regular expression against. // If not defined, or equal to null, then all text-based files of any file extension will be tested. file_extensions?: string[]; @@ -74,6 +79,10 @@ export function validateAndNormalizeConfig(valueExtractor: ConfigValueExtractor) const description: string = ruleExtractor.extractRequiredString('description'); const rawRegexString: string = ruleExtractor.extractRequiredString('regex'); const regexString: string = validateRegexString(rawRegexString, ruleExtractor.getFieldPath('regex')); + const rawPatternNotRegex: string | undefined = ruleExtractor.extractString('pattern_not_regex'); + const patternNotRegexString: string | undefined = rawPatternNotRegex + ? validateRegexString(rawPatternNotRegex, ruleExtractor.getFieldPath('pattern_not_regex')) + : undefined; const rawFileExtensions: string[] | undefined = ruleExtractor.extractArray('file_extensions', (element, fieldPath) => ValueValidator.validateString(element, fieldPath, FILE_EXT_PATTERN)); @@ -85,6 +94,7 @@ export function validateAndNormalizeConfig(valueExtractor: ConfigValueExtractor) severity: ruleExtractor.extractSeverityLevel('severity', DEFAULT_SEVERITY_LEVEL)!, tags: ruleExtractor.extractArray('tags', ValueValidator.validateString, [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CUSTOM])!, ...(rawFileExtensions ? { file_extensions: normalizeFileExtensions(rawFileExtensions) } : {}), + ...(patternNotRegexString ? { pattern_not_regex: patternNotRegexString } : {}), } } return { diff --git a/packages/code-analyzer-regex-engine/src/engine.ts b/packages/code-analyzer-regex-engine/src/engine.ts index 199eea6e..db78712b 100644 --- a/packages/code-analyzer-regex-engine/src/engine.ts +++ b/packages/code-analyzer-regex-engine/src/engine.ts @@ -139,6 +139,11 @@ export class RegexEngine extends Engine { const contextuallyDerivedEol: string = contextuallyDeriveEolString(fileContents); const newlineIndexes: number[] = getNewlineIndices(fileContents, contextuallyDerivedEol); + // Get negative pattern if defined + const patternNotRegex = this.regexRules[ruleName].pattern_not_regex + ? convertToRegex(this.regexRules[ruleName].pattern_not_regex!) + : undefined; + for (const match of fileContents.matchAll(regex)) { let startIndex: number = match.index; let matchLength: number = match[0].length; @@ -150,6 +155,18 @@ export class RegexEngine extends Engine { matchLength = match.groups.target.length; } + // Skip this match if it also matches the negative pattern + if (patternNotRegex) { + const matchedText = fileContents.substring(startIndex, startIndex + matchLength); + if (patternNotRegex.test(matchedText)) { + // Reset regex state for next iteration + patternNotRegex.lastIndex = 0; + continue; + } + // Reset regex state for next iteration + patternNotRegex.lastIndex = 0; + } + const startLine: number = getLineNumber(startIndex, newlineIndexes); const startColumn: number = getColumnNumber(startIndex, newlineIndexes, contextuallyDerivedEol); const endLine: number = getLineNumber(startIndex + matchLength, newlineIndexes); diff --git a/packages/code-analyzer-regex-engine/src/messages.ts b/packages/code-analyzer-regex-engine/src/messages.ts index 3d9ba751..97124cbe 100644 --- a/packages/code-analyzer-regex-engine/src/messages.ts +++ b/packages/code-analyzer-regex-engine/src/messages.ts @@ -11,6 +11,11 @@ const MESSAGE_CATALOG : { [key: string]: string } = { ` {rule_name} is the name you would like to give to your custom rule\n` + ` {rule_property_name} is the name of one of the rule properties. You may specify the following rule properties:\n` + ` 'regex' - The regular expression that triggers a violation when matched against the contents of a file.\n` + + ` 'pattern_not_regex' - [Optional] The negative pattern - matches that also match this pattern will be excluded.\n` + + ` This allows you to exclude false positives by specifying patterns that should NOT be flagged.\n` + + ` Example: To match email headers with user input but exclude 'validatedMessageId':\n` + + ` regex: /(To|From):\\s*\\$\\([^)]+\\)/gi\n` + + ` pattern_not_regex: /\\$\\(\\s*validatedMessageId\\s*\\)/gi\n` + ` 'file_extensions' - The extensions of the files that you would like to test the regular expression against.\n` + ` 'description' - A description of the rule's purpose\n` + ` 'violation_message' - [Optional] The message emitted when a rule violation occurs.\n` + @@ -32,6 +37,13 @@ const MESSAGE_CATALOG : { [key: string]: string } = { ` violation_message: "A comment with a TODO statement was found. Please remove TODO statements from your apex code."\n` + ` severity: "Info"\n` + ` tags: ["TechDebt"]\n` + + ` "DataWeaveEmailHeaderInjection":\n` + + ` regex: /(To|From|Subject):\\s*\\$\\([^)]+\\)/gi\n` + + ` pattern_not_regex: /\\$\\(\\s*validatedMessageId\\s*\\)/gi\n` + + ` file_extensions: [".dwl"]\n` + + ` description: "Detects user input in email headers, excluding validated IDs."\n` + + ` severity: "Critical"\n` + + ` tags: ["Security"]\n` + `-------------------------------------------`, UnsupportedEngineName: diff --git a/packages/code-analyzer-regex-engine/test/engine.test.ts b/packages/code-analyzer-regex-engine/test/engine.test.ts index b852423d..136215b3 100644 --- a/packages/code-analyzer-regex-engine/test/engine.test.ts +++ b/packages/code-analyzer-regex-engine/test/engine.test.ts @@ -961,6 +961,89 @@ describe('Tests for runRules', () => { }); }); +describe('Tests for pattern_not_regex', () => { + it('pattern_not_regex should exclude matches that match the negative pattern', async () => { + const customRulesWithNegativePattern: RegexRules = { + EmailHeaderInjection: { + regex: '/(To|From|Subject|In-Reply-To|References):\\s*\\$\\([^)]+\\)/gi', + pattern_not_regex: '/\\$\\(\\s*validatedMessageId\\s*\\)/gi', + description: "Detects user input in email headers, excluding validatedMessageId", + file_extensions: [".dwl"], + violation_message: "User input detected in email header", + severity: SeverityLevel.Critical, + tags: ["Security"] + } + }; + + const testEngine = new RegexEngine(customRulesWithNegativePattern, RULE_RESOURCE_URLS); + const runOptions: RunOptions = createRunOptions( + new Workspace('id', [path.resolve(__dirname, "test-data", "patternNotRegex")])); + const runResults: EngineRunResults = await testEngine.runRules(["EmailHeaderInjection"], runOptions); + + // emailHeaders_WithValidatedId.dwl has $(validatedMessageId) - should be excluded + // emailHeaders_WithUnsanitizedPayload.dwl has $(payload.x) - should be violations + const validatedIdViolations = runResults.violations.filter(v => + v.codeLocations[0].file.includes('emailHeaders_WithValidatedId.dwl')); + const unsanitizedViolations = runResults.violations.filter(v => + v.codeLocations[0].file.includes('emailHeaders_WithUnsanitizedPayload.dwl')); + + expect(validatedIdViolations).toHaveLength(0); // Should be excluded by pattern_not_regex + expect(unsanitizedViolations.length).toBeGreaterThan(0); // Should have violations + }); + + it('Rule without pattern_not_regex should behave normally', async () => { + const customRulesWithoutNegativePattern: RegexRules = { + EmailHeaderInjection: { + regex: '/(To|From|Subject|In-Reply-To|References):\\s*\\$\\([^)]+\\)/gi', + description: "Detects user input in email headers", + file_extensions: [".dwl"], + violation_message: "User input detected in email header", + severity: SeverityLevel.Critical, + tags: ["Security"] + } + }; + + const testEngine = new RegexEngine(customRulesWithoutNegativePattern, RULE_RESOURCE_URLS); + const runOptions: RunOptions = createRunOptions( + new Workspace('id', [path.resolve(__dirname, "test-data", "patternNotRegex")])); + const runResults: EngineRunResults = await testEngine.runRules(["EmailHeaderInjection"], runOptions); + + // Without pattern_not_regex, both files should have violations + const validatedIdViolations = runResults.violations.filter(v => + v.codeLocations[0].file.includes('emailHeaders_WithValidatedId.dwl')); + const unsanitizedViolations = runResults.violations.filter(v => + v.codeLocations[0].file.includes('emailHeaders_WithUnsanitizedPayload.dwl')); + + expect(validatedIdViolations.length).toBeGreaterThan(0); // Should have violations + expect(unsanitizedViolations.length).toBeGreaterThan(0); // Should have violations + }); + + it('pattern_not_regex with multiple exclusion patterns', async () => { + const customRulesWithMultipleExclusions: RegexRules = { + EmailHeaderInjection: { + regex: '/(To|From|Subject):\\s*\\$\\([^)]+\\)/gi', + pattern_not_regex: '/\\$\\((validatedMessageId|sanitizeHeader|getSafeEmailHeader)\\s*[^)]*\\)/gi', + description: "Detects user input in email headers, excluding safe functions", + file_extensions: [".dwl"], + violation_message: "User input detected in email header", + severity: SeverityLevel.Critical, + tags: ["Security"] + } + }; + + const testEngine = new RegexEngine(customRulesWithMultipleExclusions, RULE_RESOURCE_URLS); + const runOptions: RunOptions = createRunOptions( + new Workspace('id', [path.resolve(__dirname, "test-data", "patternNotRegex")])); + const runResults: EngineRunResults = await testEngine.runRules(["EmailHeaderInjection"], runOptions); + + // validatedMessageId should still be excluded + const validatedIdViolations = runResults.violations.filter(v => + v.codeLocations[0].file.includes('emailHeaders_WithValidatedId.dwl')); + + expect(validatedIdViolations).toHaveLength(0); + }); +}); + describe('Tests for getEngineVersion', () => { it('Outputs something resembling a Semantic Version', async () => { const version: string = await engine.getEngineVersion(); diff --git a/packages/code-analyzer-regex-engine/test/plugin.test.ts b/packages/code-analyzer-regex-engine/test/plugin.test.ts index 8c66cc84..e5f52f25 100644 --- a/packages/code-analyzer-regex-engine/test/plugin.test.ts +++ b/packages/code-analyzer-regex-engine/test/plugin.test.ts @@ -498,4 +498,38 @@ describe('RegexEnginePlugin Custom Config Tests', () => { await expect(plugin.createEngineConfig("regex", valueExtractor)).rejects.toThrow( getMessageFromCatalog(SHARED_MESSAGE_CATALOG,'ConfigValueMustBeOfType', 'engines.regex.custom_rules.NoTodos.tags', 'array', 'string')); }); + + it("If user creates a rule with pattern_not_regex, it should be included in the config", async () => { + const rawConfig = { + custom_rules: { + "EmailHeaderInjection": { + regex: String.raw`/(To|From|Subject):\s*\$\([^)]+\)/gi`, + pattern_not_regex: String.raw`/\$\(\s*validatedMessageId\s*\)/gi`, + description: "Detects user input in email headers", + file_extensions: [".dwl"] + } + } + }; + const valueExtractor: ConfigValueExtractor = new ConfigValueExtractor(rawConfig, 'engines.regex'); + const resolvedConfig: ConfigObject = await plugin.createEngineConfig("regex", valueExtractor); + const pluginEngine: RegexEngine = await plugin.createEngine("regex", resolvedConfig) as RegexEngine; + + expect(pluginEngine._getRegexRules()["EmailHeaderInjection"].pattern_not_regex).toBeDefined(); + expect(pluginEngine._getRegexRules()["EmailHeaderInjection"].pattern_not_regex).toContain('validatedMessageId'); + }); + + it("If user creates a rule with invalid pattern_not_regex, ensure correct error is emitted", async () => { + const rawConfig = { + custom_rules: { + "BadRule": { + ...SAMPLE_RAW_CUSTOM_RULE_DEFINITION, + pattern_not_regex: "/bad[pattern/gi" + } + } + }; + const valueExtractor: ConfigValueExtractor = new ConfigValueExtractor(rawConfig, 'engines.regex'); + await expect(plugin.createEngineConfig("regex", valueExtractor)).rejects.toThrow( + getMessage('InvalidConfigurationValueWithReason', 'engines.regex.custom_rules.BadRule.pattern_not_regex', + getMessage('InvalidRegexDueToError', '/bad[pattern/gi', "Invalid regular expression: /bad[pattern/gi: Unterminated character class"))); + }); }); diff --git a/packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithUnsanitizedPayload.dwl b/packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithUnsanitizedPayload.dwl new file mode 100644 index 00000000..ec9f8684 --- /dev/null +++ b/packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithUnsanitizedPayload.dwl @@ -0,0 +1,11 @@ +%dw 2.0 +output application/java +--- +{ + headers: { + To: $(payload.recipientEmail), + Subject: $(payload.subject), + From: $(payload.fromAddress) + }, + body: payload.message +} diff --git a/packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithValidatedId.dwl b/packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithValidatedId.dwl new file mode 100644 index 00000000..26d2226a --- /dev/null +++ b/packages/code-analyzer-regex-engine/test/test-data/patternNotRegex/emailHeaders_WithValidatedId.dwl @@ -0,0 +1,13 @@ +%dw 2.0 +output application/java +--- +{ + headers: { + To: "customer@example.com", + Subject: "Property Update", + "In-Reply-To": $(validatedMessageId), + References: $(validatedMessageId), + From: "noreply@dreamhouse.com" + }, + body: payload.message +} From 69e9b06f29d0f7bc450b2a2b2dff60dc5b77cb0f Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Wed, 18 Mar 2026 16:02:30 +0530 Subject: [PATCH 2/2] refactor: Rename pattern_not_regex to regex_ignore for clarity - More intuitive naming: regex_ignore clearly indicates patterns to ignore - Updated all references in code, tests, and documentation - All 79 tests pass successfully - No functional changes, pure refactoring Changed files: - config.ts: Type definition and validation logic - engine.ts: Pattern matching implementation - messages.ts: Error messages and documentation - engine.test.ts: Test cases - plugin.test.ts: Plugin configuration tests --- .../code-analyzer-regex-engine/src/config.ts | 10 +++++----- .../code-analyzer-regex-engine/src/engine.ts | 4 ++-- .../code-analyzer-regex-engine/src/messages.ts | 6 +++--- .../test/engine.test.ts | 16 ++++++++-------- .../test/plugin.test.ts | 14 +++++++------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/code-analyzer-regex-engine/src/config.ts b/packages/code-analyzer-regex-engine/src/config.ts index c41c3a7c..f310abdb 100644 --- a/packages/code-analyzer-regex-engine/src/config.ts +++ b/packages/code-analyzer-regex-engine/src/config.ts @@ -33,8 +33,8 @@ export type RegexRule = { // [Optional] The negative pattern - matches that also match this pattern will be excluded from violations. // This allows you to exclude false positives by specifying patterns that should NOT be flagged. - // Example: regex: /(To|From):\s*\$\([^)]+\)/ with pattern_not_regex: /\$\(validatedMessageId\)/ - pattern_not_regex?: string; + // Example: regex: /(To|From):\s*\$\([^)]+\)/ with regex_ignore: /\$\(validatedMessageId\)/ + regex_ignore?: string; // The extensions of the files that you would like to test the regular expression against. // If not defined, or equal to null, then all text-based files of any file extension will be tested. @@ -79,9 +79,9 @@ export function validateAndNormalizeConfig(valueExtractor: ConfigValueExtractor) const description: string = ruleExtractor.extractRequiredString('description'); const rawRegexString: string = ruleExtractor.extractRequiredString('regex'); const regexString: string = validateRegexString(rawRegexString, ruleExtractor.getFieldPath('regex')); - const rawPatternNotRegex: string | undefined = ruleExtractor.extractString('pattern_not_regex'); + const rawPatternNotRegex: string | undefined = ruleExtractor.extractString('regex_ignore'); const patternNotRegexString: string | undefined = rawPatternNotRegex - ? validateRegexString(rawPatternNotRegex, ruleExtractor.getFieldPath('pattern_not_regex')) + ? validateRegexString(rawPatternNotRegex, ruleExtractor.getFieldPath('regex_ignore')) : undefined; const rawFileExtensions: string[] | undefined = ruleExtractor.extractArray('file_extensions', (element, fieldPath) => ValueValidator.validateString(element, fieldPath, FILE_EXT_PATTERN)); @@ -94,7 +94,7 @@ export function validateAndNormalizeConfig(valueExtractor: ConfigValueExtractor) severity: ruleExtractor.extractSeverityLevel('severity', DEFAULT_SEVERITY_LEVEL)!, tags: ruleExtractor.extractArray('tags', ValueValidator.validateString, [COMMON_TAGS.RECOMMENDED, COMMON_TAGS.CUSTOM])!, ...(rawFileExtensions ? { file_extensions: normalizeFileExtensions(rawFileExtensions) } : {}), - ...(patternNotRegexString ? { pattern_not_regex: patternNotRegexString } : {}), + ...(patternNotRegexString ? { regex_ignore: patternNotRegexString } : {}), } } return { diff --git a/packages/code-analyzer-regex-engine/src/engine.ts b/packages/code-analyzer-regex-engine/src/engine.ts index db78712b..f7e24b03 100644 --- a/packages/code-analyzer-regex-engine/src/engine.ts +++ b/packages/code-analyzer-regex-engine/src/engine.ts @@ -140,8 +140,8 @@ export class RegexEngine extends Engine { const newlineIndexes: number[] = getNewlineIndices(fileContents, contextuallyDerivedEol); // Get negative pattern if defined - const patternNotRegex = this.regexRules[ruleName].pattern_not_regex - ? convertToRegex(this.regexRules[ruleName].pattern_not_regex!) + const patternNotRegex = this.regexRules[ruleName].regex_ignore + ? convertToRegex(this.regexRules[ruleName].regex_ignore!) : undefined; for (const match of fileContents.matchAll(regex)) { diff --git a/packages/code-analyzer-regex-engine/src/messages.ts b/packages/code-analyzer-regex-engine/src/messages.ts index 8ac1ef5e..df2222d1 100644 --- a/packages/code-analyzer-regex-engine/src/messages.ts +++ b/packages/code-analyzer-regex-engine/src/messages.ts @@ -11,11 +11,11 @@ const MESSAGE_CATALOG : { [key: string]: string } = { ` {rule_name} is the name you would like to give to your custom rule\n` + ` {rule_property_name} is the name of one of the rule properties. You may specify the following rule properties:\n` + ` 'regex' - The regular expression that triggers a violation when matched against the contents of a file.\n` + - ` 'pattern_not_regex' - [Optional] The negative pattern - matches that also match this pattern will be excluded.\n` + + ` 'regex_ignore' - [Optional] The negative pattern - matches that also match this pattern will be excluded.\n` + ` This allows you to exclude false positives by specifying patterns that should NOT be flagged.\n` + ` Example: To match email headers with user input but exclude 'validatedMessageId':\n` + ` regex: /(To|From):\\s*\\$\\([^)]+\\)/gi\n` + - ` pattern_not_regex: /\\$\\(\\s*validatedMessageId\\s*\\)/gi\n` + + ` regex_ignore: /\\$\\(\\s*validatedMessageId\\s*\\)/gi\n` + ` 'file_extensions' - The extensions of the files that you would like to test the regular expression against.\n` + ` 'description' - A description of the rule's purpose\n` + ` 'violation_message' - [Optional] The message emitted when a rule violation occurs.\n` + @@ -39,7 +39,7 @@ const MESSAGE_CATALOG : { [key: string]: string } = { ` tags: ["TechDebt"]\n` + ` "DataWeaveEmailHeaderInjection":\n` + ` regex: /(To|From|Subject):\\s*\\$\\([^)]+\\)/gi\n` + - ` pattern_not_regex: /\\$\\(\\s*validatedMessageId\\s*\\)/gi\n` + + ` regex_ignore: /\\$\\(\\s*validatedMessageId\\s*\\)/gi\n` + ` file_extensions: [".dwl"]\n` + ` description: "Detects user input in email headers, excluding validated IDs."\n` + ` severity: "Critical"\n` + diff --git a/packages/code-analyzer-regex-engine/test/engine.test.ts b/packages/code-analyzer-regex-engine/test/engine.test.ts index cc62ae6e..7fa244f2 100644 --- a/packages/code-analyzer-regex-engine/test/engine.test.ts +++ b/packages/code-analyzer-regex-engine/test/engine.test.ts @@ -1031,12 +1031,12 @@ describe('Tests for runRules', () => { }); }); -describe('Tests for pattern_not_regex', () => { - it('pattern_not_regex should exclude matches that match the negative pattern', async () => { +describe('Tests for regex_ignore', () => { + it('regex_ignore should exclude matches that match the negative pattern', async () => { const customRulesWithNegativePattern: RegexRules = { EmailHeaderInjection: { regex: '/(To|From|Subject|In-Reply-To|References):\\s*\\$\\([^)]+\\)/gi', - pattern_not_regex: '/\\$\\(\\s*validatedMessageId\\s*\\)/gi', + regex_ignore: '/\\$\\(\\s*validatedMessageId\\s*\\)/gi', description: "Detects user input in email headers, excluding validatedMessageId", file_extensions: [".dwl"], violation_message: "User input detected in email header", @@ -1057,11 +1057,11 @@ describe('Tests for pattern_not_regex', () => { const unsanitizedViolations = runResults.violations.filter(v => v.codeLocations[0].file.includes('emailHeaders_WithUnsanitizedPayload.dwl')); - expect(validatedIdViolations).toHaveLength(0); // Should be excluded by pattern_not_regex + expect(validatedIdViolations).toHaveLength(0); // Should be excluded by regex_ignore expect(unsanitizedViolations.length).toBeGreaterThan(0); // Should have violations }); - it('Rule without pattern_not_regex should behave normally', async () => { + it('Rule without regex_ignore should behave normally', async () => { const customRulesWithoutNegativePattern: RegexRules = { EmailHeaderInjection: { regex: '/(To|From|Subject|In-Reply-To|References):\\s*\\$\\([^)]+\\)/gi', @@ -1078,7 +1078,7 @@ describe('Tests for pattern_not_regex', () => { new Workspace('id', [path.resolve(__dirname, "test-data", "patternNotRegex")])); const runResults: EngineRunResults = await testEngine.runRules(["EmailHeaderInjection"], runOptions); - // Without pattern_not_regex, both files should have violations + // Without regex_ignore, both files should have violations const validatedIdViolations = runResults.violations.filter(v => v.codeLocations[0].file.includes('emailHeaders_WithValidatedId.dwl')); const unsanitizedViolations = runResults.violations.filter(v => @@ -1088,11 +1088,11 @@ describe('Tests for pattern_not_regex', () => { expect(unsanitizedViolations.length).toBeGreaterThan(0); // Should have violations }); - it('pattern_not_regex with multiple exclusion patterns', async () => { + it('regex_ignore with multiple exclusion patterns', async () => { const customRulesWithMultipleExclusions: RegexRules = { EmailHeaderInjection: { regex: '/(To|From|Subject):\\s*\\$\\([^)]+\\)/gi', - pattern_not_regex: '/\\$\\((validatedMessageId|sanitizeHeader|getSafeEmailHeader)\\s*[^)]*\\)/gi', + regex_ignore: '/\\$\\((validatedMessageId|sanitizeHeader|getSafeEmailHeader)\\s*[^)]*\\)/gi', description: "Detects user input in email headers, excluding safe functions", file_extensions: [".dwl"], violation_message: "User input detected in email header", diff --git a/packages/code-analyzer-regex-engine/test/plugin.test.ts b/packages/code-analyzer-regex-engine/test/plugin.test.ts index e5f52f25..39264ef3 100644 --- a/packages/code-analyzer-regex-engine/test/plugin.test.ts +++ b/packages/code-analyzer-regex-engine/test/plugin.test.ts @@ -499,12 +499,12 @@ describe('RegexEnginePlugin Custom Config Tests', () => { getMessageFromCatalog(SHARED_MESSAGE_CATALOG,'ConfigValueMustBeOfType', 'engines.regex.custom_rules.NoTodos.tags', 'array', 'string')); }); - it("If user creates a rule with pattern_not_regex, it should be included in the config", async () => { + it("If user creates a rule with regex_ignore, it should be included in the config", async () => { const rawConfig = { custom_rules: { "EmailHeaderInjection": { regex: String.raw`/(To|From|Subject):\s*\$\([^)]+\)/gi`, - pattern_not_regex: String.raw`/\$\(\s*validatedMessageId\s*\)/gi`, + regex_ignore: String.raw`/\$\(\s*validatedMessageId\s*\)/gi`, description: "Detects user input in email headers", file_extensions: [".dwl"] } @@ -514,22 +514,22 @@ describe('RegexEnginePlugin Custom Config Tests', () => { const resolvedConfig: ConfigObject = await plugin.createEngineConfig("regex", valueExtractor); const pluginEngine: RegexEngine = await plugin.createEngine("regex", resolvedConfig) as RegexEngine; - expect(pluginEngine._getRegexRules()["EmailHeaderInjection"].pattern_not_regex).toBeDefined(); - expect(pluginEngine._getRegexRules()["EmailHeaderInjection"].pattern_not_regex).toContain('validatedMessageId'); + expect(pluginEngine._getRegexRules()["EmailHeaderInjection"].regex_ignore).toBeDefined(); + expect(pluginEngine._getRegexRules()["EmailHeaderInjection"].regex_ignore).toContain('validatedMessageId'); }); - it("If user creates a rule with invalid pattern_not_regex, ensure correct error is emitted", async () => { + it("If user creates a rule with invalid regex_ignore, ensure correct error is emitted", async () => { const rawConfig = { custom_rules: { "BadRule": { ...SAMPLE_RAW_CUSTOM_RULE_DEFINITION, - pattern_not_regex: "/bad[pattern/gi" + regex_ignore: "/bad[pattern/gi" } } }; const valueExtractor: ConfigValueExtractor = new ConfigValueExtractor(rawConfig, 'engines.regex'); await expect(plugin.createEngineConfig("regex", valueExtractor)).rejects.toThrow( - getMessage('InvalidConfigurationValueWithReason', 'engines.regex.custom_rules.BadRule.pattern_not_regex', + getMessage('InvalidConfigurationValueWithReason', 'engines.regex.custom_rules.BadRule.regex_ignore', getMessage('InvalidRegexDueToError', '/bad[pattern/gi', "Invalid regular expression: /bad[pattern/gi: Unterminated character class"))); }); });