Skip to content

Conversation

@iAn-P1nt0
Copy link

Overview

This PR introduces two independent enhancements to Express.js that address long-standing issues with CDN caching and query string handling.

Changes Included

1. CORS-Aware ETag Generation (Fixes #5986)

Adds new ETag modes that include response headers in ETag calculation to prevent cache conflicts when serving content to multiple origins through CDNs.

New ETag modes:

  • 'weak-cors': Weak ETag including Access-Control-Allow-Origin header
  • 'strong-cors': Strong ETag including Access-Control-Allow-Origin header

Problem solved: CDNs returning 304 Not Modified responses that omit CORS headers, causing browsers to apply cached CORS headers from different origins, resulting in CORS errors.

Usage:
`app.set('etag', 'weak-cors');

app.use(function(req, res) {
res.set('Access-Control-Allow-Origin', req.get('Origin'));
res.send('content');
});`

Implementation:

  • Extends createETagGenerator to accept includeHeaders option
  • Updates res.send() to pass response headers to ETag function
  • Maintains full backward compatibility with existing ETag modes
  • Falls back to body-only hashing when CORS headers are not present

2. Configurable Query Parser Options (Fixes #5878)

Adds ability to configure qs library options when using the extended query parser, addressing silent parameter truncation and providing better security controls.

New API:
app.set('query parser', 'extended'); app.set('query parser options', { parameterLimit: 5000, // Increase from default 1000 arrayLimit: 50, // Increase from default 20 depth: 10, // Increase from default 5 allowPrototypes: false // Prevent prototype pollution });

Problem solved: Query strings exceeding the default 1000 parameter limit resulted in silent data loss. The default allowPrototypes: true setting poses a prototype pollution security risk.

Implementation:

  • compileQueryParser now accepts optional qsOptions parameter
  • createExtendedQueryParser factory function replaces parseExtendedQueryString
  • app.set('query parser options') triggers parser recompilation
  • Does not affect 'simple' parser mode

Backward Compatibility

Both features maintain full backward compatibility:

  • Default behavior unchanged for both features
  • Works without setting new options
  • All existing tests pass (1269 total)

Test Coverage

  • CORS ETag tests: 13 new unit tests in test/utils.js + 10 integration tests in test/res.send.cors.js
  • Query parser tests: 11 new tests in test/req.query.options.js
  • Tests cover limits, security, edge cases, and backward compatibility

Security Considerations

The query parser feature helps prevent prototype pollution attacks by allowing developers to set allowPrototypes: false. The default remains true for backward compatibility, but documentation encourages the secure option.


Add support for including response headers in ETag calculation to prevent
cache conflicts when serving content to multiple origins through CDNs.

This addresses an issue where CDNs return 304 Not Modified responses that
omit CORS headers, causing browsers to apply cached CORS headers from a
different origin, resulting in CORS errors.

New ETag modes:
- 'weak-cors': Weak ETag including Access-Control-Allow-Origin header
- 'strong-cors': Strong ETag including Access-Control-Allow-Origin header

The implementation:
- Extends createETagGenerator to accept includeHeaders option
- Updates res.send() to pass response headers to ETag function
- Maintains full backward compatibility with existing ETag modes
- Falls back to body-only hashing when CORS headers are not present

Usage:
  app.set('etag', 'weak-cors');

  app.use(function(req, res) {
    res.set('Access-Control-Allow-Origin', req.get('Origin'));
    res.send('content');
  });

Test coverage:
- 13 new unit tests in test/utils.js
- 10 new integration tests in test/res.send.cors.js
- All existing tests pass (1269 total)

Fixes expressjs#5986
Add ability to configure qs library options when using the extended
query parser, addressing silent parameter truncation and providing
better security controls.

Previously, the extended query parser used hardcoded qs defaults with
no way to customize behavior. This caused issues when query strings
exceeded the default 1000 parameter limit, resulting in silent data
loss. Additionally, the default allowPrototypes: true setting poses
a prototype pollution security risk.

New API:
  app.set('query parser', 'extended');
  app.set('query parser options', {
    parameterLimit: 5000,      // Increase from default 1000
    arrayLimit: 50,            // Increase from default 20
    depth: 10,                 // Increase from default 5
    allowPrototypes: false     // Prevent prototype pollution
  });

Changes:
- compileQueryParser now accepts optional qsOptions parameter
- createExtendedQueryParser factory function replaces parseExtendedQueryString
- app.set('query parser options') triggers parser recompilation
- Explicit defaults documented: parameterLimit=1000, arrayLimit=20, depth=5

Backward compatibility:
- Default behavior unchanged (same qs defaults apply)
- Works without setting options
- Options can be set before or after parser mode
- Does not affect 'simple' parser mode

Security note:
The default allowPrototypes: true is maintained for backward compatibility
but developers are encouraged to set allowPrototypes: false to prevent
prototype pollution attacks.

Test coverage:
- 11 new tests in test/req.query.options.js
- Tests cover limits, security, and backward compatibility
- All existing tests pass (1269 total)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ETag should consider CORS headers Query Param Silently Remove param query value if it is over 1000

1 participant