Skip to content

fix(pattern): address CVE-2025-69873 by implementing safeguards against ReDoS attacks in pattern validation#2585

Closed
KsAkira10 wants to merge 1 commit intoajv-validator:masterfrom
KsAkira10:fix/cve-2025-69873-redos-attack
Closed

fix(pattern): address CVE-2025-69873 by implementing safeguards against ReDoS attacks in pattern validation#2585
KsAkira10 wants to merge 1 commit intoajv-validator:masterfrom
KsAkira10:fix/cve-2025-69873-redos-attack

Conversation

@KsAkira10
Copy link

What issue does this pull request resolve?

This PR resolves CVE-2025-69873: Regular Expression Denial of Service (ReDoS) vulnerability in ajv when the $data option is enabled with the pattern keyword.

The vulnerability allows attackers to inject malicious regex patterns through $data references that exhibit catastrophic backtracking behavior, causing complete denial of service. A single HTTP request with a payload like:

{
  "pattern": "^(a|a)*$",
  "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaX"
}

Can block the server for 44+ seconds, rendering the entire Node.js service unresponsive.

CVE Reference: https://www.cve.org/CVERecord?id=CVE-2025-69873


What changes did you make?

Core Fix - lib/vocabularies/validation/pattern.ts

  1. Added import: useFunc from ../../compile/util to inject custom regex engines into generated code

  2. Split $data and static code paths:

    • $data path (previously vulnerable): Now uses the configured opts.code.regExp engine instead of hardcoded new RegExp()
    • This allows RE2 or any custom regex engine to be applied to runtime data patterns
    • Static path (unchanged): Continues to use usePattern() as before
  3. Added try/catch protection:

    • Wrapped dynamic regex creation and .test() call in gen.try/catch
    • Invalid regex patterns now result in validation failure instead of crashing the validator
    • Addresses the TODO comment that was in the original code

Generated Code Behavior

Before (vulnerable):

!(new RegExp(vSchema, "u")).test(data)  // Hardcoded, ignores configured engine

After (secure):

let valid;
try {
  valid = re2(vSchema, "u").test(data);  // Uses configured engine (RE2, etc.)
} catch(e) {
  valid = false;  // Invalid patterns fail gracefully
}
!(valid)

Test Coverage

  1. spec/issues/cve_2025_69873_redos_attack.spec.ts (new)

    • 6 comprehensive test cases covering:
      • ReDoS pattern prevention with RE2 engine
      • Invalid regex handling (graceful failure, no crash)
      • Normal pattern validation continues to work correctly
      • Timing benchmarks to verify safe execution
  2. spec/extras/$data/pattern.json (updated)

    • Added test case for invalid regex patterns via $data
    • Validates graceful failure on malformed regex

Test Results: 7,610 tests passing, 0 failures


Is there anything that requires more attention while reviewing?

Key Points for Review

1. Behavioral Change

  • Before: Invalid regex patterns via $data would throw an unhandled SyntaxError, crashing the validator
  • After: Invalid patterns result in false (validation failure)
  • Impact: This is a security improvement, not a breaking change to valid use cases

2. Custom Regex Engine Support

  • The fix now correctly applies custom RegExpEngine implementations (RE2, etc.) to $data patterns
  • Previously, custom engines were only used for static patterns; $data patterns always used new RegExp
  • This aligns $data behavior with the static pattern path

3. Backwards Compatibility

  • All 7,610 existing tests pass without modification
  • Valid $data pattern matching behavior is completely unchanged
  • Only invalid/ReDoS patterns behave differently (fail gracefully instead of hanging/crashing)

4. Performance Impact

  • Zero impact on non-$data patterns (static path unchanged)
  • Minimal overhead for $data patterns: lightweight try/catch around regex execution
  • With RE2 or other safe engines, exponential backtracking is structurally impossible

5. Security Validation

  • Comprehensive test suite validates protection across multiple ReDoS patterns:
    • ^(a+)+$
    • ^(a|a)*$
    • ^(a|ab)*$
    • (x+x+)+y
    • (a*)*b

Implementation Notes

  • The fix mirrors the exact pattern used in usePattern() (lib/vocabularies/code.ts:93-105) for handling custom regex engines
  • Uses useFunc() to properly inject engine references into generated code
  • Generated standalone code correctly includes the required require statement for custom engines
  • All import statements follow existing project patterns

Summary

This is a targeted security fix that resolves CVE-2025-69873 by:

  1. Respecting the configured regex engine for $data patterns
  2. Adding graceful error handling for invalid patterns
  3. Zero impact on existing functionality
  4. Complete backwards compatibility
  5. Comprehensive test coverage

The vulnerability is structurally eliminated when using RE2, making ReDoS attacks impossible.

@epoberezkin
Copy link
Member

Before: Invalid regex patterns via $data would throw an unhandled SyntaxError, crashing the validator
After: Invalid patterns result in false (validation failure)

It may a correct approach, as in this case we take part of schema from data, so technically it's data that is invalid. But I think application should still be catching exceptions rather than the validator, and some applications that differentiated these scenarios will start working differently. I think throwing exception and letting applications catch it is better - applications should be catching exceptions anyway, especially with dynamic schemas.

@KsAkira10 what do you think?

@KsAkira10
Copy link
Author

Before: Invalid regex patterns via $data would throw an unhandled SyntaxError, crashing the validator
After: Invalid patterns result in false (validation failure)

It may a correct approach, as in this case we take part of schema from data, so technically it's data that is invalid. But I think application should still be catching exceptions rather than the validator, and some applications that differentiated these scenarios will start working differently. I think throwing exception and letting applications catch it is better - applications should be catching exceptions anyway, especially with dynamic schemas.

@KsAkira10 what do you think?

I focused only on mitigating the vulnerability, but I think the idea of leaving the exceptions for the application to handle is valid.

But would that be a problem to deal with now?

@epoberezkin
Copy link
Member

epoberezkin commented Feb 13, 2026

Not sure why, but 2 of added tests run for 54 seconds each for me, so it's not really solving the problem... What am I missing?

Edit: was running the new tests with the old code, so it actually proved the fix works.

@epoberezkin
Copy link
Member

merged #2586 with this commit. Thank you!

@pahud pahud mentioned this pull request Feb 16, 2026
1 task
@mikedorfman
Copy link

mikedorfman commented Feb 17, 2026

@epoberezkin Thank you so much for addressing this! Do you have any plans to backport this fix to earlier versions of AJV? Specifically, we’re on "^6.12.3" and are trying to determine whether we should plan to upgrade to v8.18.0 or if a backport to v6.x.x is expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants