Skip to content

fix: harden header iterable checks for prototype-pollution scenarios#4824

Open
mcollina wants to merge 1 commit intomainfrom
fix/header-iterator-prototype-pollution
Open

fix: harden header iterable checks for prototype-pollution scenarios#4824
mcollina wants to merge 1 commit intomainfrom
fix/header-iterator-prototype-pollution

Conversation

@mcollina
Copy link
Member

@mcollina mcollina commented Feb 12, 2026

Summary

This PR hardens header processing against prototype-pollution side effects.

Undici previously used inherited Symbol.iterator lookups for some header inputs. If Object.prototype[Symbol.iterator] was polluted, plain header objects could be misclassified as iterables and silently lose headers.

This is not a standalone vulnerability in Undici; it requires a pre-existing prototype-pollution primitive. This change improves resilience in that scenario.

Changes

Updated iterable detection in:

  • lib/core/request.js
  • lib/handler/redirect-handler.js
  • lib/util/cache.js

New behavior:

  • Treat headers as iterable only when:
    • Symbol.iterator is an own property, or
    • the object has a non-plain prototype and an iterator function (e.g. Map, Headers)
  • Otherwise treat headers as plain objects via Object.keys / Object.entries.

Tests

  • test/request.js
    • plain object headers are preserved when Object.prototype[Symbol.iterator] is polluted
  • test/interceptors/redirect.js
    • same-origin redirect preserves plain object headers under polluted iterator
  • test/cache-interceptor/cache-utils.js (new)
    • normalizeHeaders works for polluted plain objects
    • normalizeHeaders still works for Map headers

Verification

  • npx borp -p "test/request.js"
  • npx borp -p "test/interceptors/redirect.js"
  • npx borp -p "test/cache-interceptor/cache-utils.js"
  • npx borp -p "test/interceptors/cache.js"
  • npm run lint -- lib/core/request.js lib/util/cache.js lib/handler/redirect-handler.js test/request.js test/interceptors/redirect.js test/cache-interceptor/cache-utils.js

Treat headers as iterable only when Symbol.iterator is an own property, or when the object has a non-plain prototype with a function iterator.\n\nThis avoids misclassifying plain header objects as iterables when Object.prototype[Symbol.iterator] is polluted, which could otherwise cause silent header loss.\n\nApply the same hardening in request, redirect, and cache header handling, and add regression coverage for polluted Object.prototype[Symbol.iterator] while preserving support for legitimate iterable headers (e.g. Map/Headers).
@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 86.20690% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.19%. Comparing base (2453caf) to head (db2468d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
lib/util/cache.js 78.94% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4824      +/-   ##
==========================================
+ Coverage   93.17%   93.19%   +0.01%     
==========================================
  Files         109      109              
  Lines       34187    34200      +13     
==========================================
+ Hits        31855    31873      +18     
+ Misses       2332     2327       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +225 to +228
const prototype = Object.getPrototypeOf(headers)
const ownIterator = Object.prototype.hasOwnProperty.call(headers, Symbol.iterator)
const hasIterator = ownIterator || (prototype != null && prototype !== Object.prototype && typeof headers[Symbol.iterator] === 'function')
const entries = hasIterator ? headers : Object.entries(headers)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we maybe create an utility function to share it across?

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.

3 participants