Prototype Pollution
Description
The express-xss-sanitizer package before version 1.1.3 is vulnerable to Prototype Pollution via the allowedTags option, allowing attackers to bypass XSS sanitization.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
The express-xss-sanitizer package before version 1.1.3 is vulnerable to Prototype Pollution via the allowedTags option, allowing attackers to bypass XSS sanitization.
Vulnerability
Analysis
The express-xss-sanitizer middleware for Express.js is vulnerable to Prototype Pollution in versions prior to 1.1.3. The vulnerability exists in the way the allowedTags option is processed. When an attacker can control or influence the options object passed to the sanitizer, they can inject a property __proto__ or manipulate the prototype chain to set allowedTags to a malicious array, such as ['script']. This attack leverages JavaScript's prototype mechanism to pollute the base object prototype, thereby subverting the sanitization logic [1][3].
Exploitation and
Attack Surface
Exploitation requires the ability to pass a crafted options object to the xss() middleware. This can occur when user input is inadvertently merged into configuration options, a common pattern in Node.js applications. The fix, introduced in commit 3bf8aaaf4dbb1c209dcb8d87a82711a54c1ab39a, adds proper checks using Object.hasOwn() to ensure that allowedTags and allowedKeys originate from the actual options object and not from the prototype chain [4].
Impact
An attacker successfully exploiting this vulnerability can bypass XSS sanitization entirely, allowing injection of arbitrary HTML/JavaScript into the application's response. This could lead to cross-site scripting attacks, potentially compromising user sessions, stealing sensitive data, or performing actions on behalf of the victim [2][3].
Mitigation
Users should upgrade to express-xss-sanitizer version 1.1.3 or later, which includes the fix. Applications that cannot upgrade should avoid passing user-controlled data into the sanitizer options and ensure that the options object is constructed safely without vulnerable merge patterns [1][4].
- NVD - CVE-2022-21169
- GitHub - AhmedAdelFahim/express-xss-sanitizer: Express 4.x and 5.x middleware which sanitizes user input data (in req.body, req.query, req.headers and req.params) to prevent Cross Site Scripting (XSS) attack.
- Snyk Vulnerability Database | Snyk
- fix XSS bypass by using prototype pollution issue. · AhmedAdelFahim/express-xss-sanitizer@3bf8aaa
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
express-xss-sanitizernpm | < 1.1.3 | 1.1.3 |
Affected products
2- express-xss-sanitizer/express-xss-sanitizerdescription
Patches
13bf8aaaf4dbbfix XSS bypass by using prototype pollution issue.
3 files changed · +41 −3
.eslintrc.json+2 −1 modified@@ -21,6 +21,7 @@ "prettier/prettier": ["error"], "quotes": "off", "strict": "off", - "prefer-arrow-callback":"off" + "prefer-arrow-callback":"off", + "operator-linebreak": "off" } }
lib/sanitize.js+9 −2 modified@@ -4,12 +4,19 @@ const sanitizeHtml = require("sanitize-html"); const initializeOptions = (options) => { const sanitizerOptions = {}; - if (Array.isArray(options.allowedTags) && options.allowedTags.length > 0) { + if ( + Object.hasOwn(options, "allowedTags") && + Array.isArray(options.allowedTags) && + options.allowedTags.length > 0 + ) { sanitizerOptions.allowedTags = options.allowedTags; } return { allowedKeys: - (Array.isArray(options.allowedKeys) && options.allowedKeys) || [], + (Object.hasOwn(options, "allowedKeys") && + Array.isArray(options.allowedKeys) && + options.allowedKeys) || + [], sanitizerOptions, }; };
test/test.js+30 −0 modified@@ -1222,4 +1222,34 @@ describe("Express xss Sanitize", function () { }); }); }); + + describe("Sanitize data with custom options as function", function () { + describe("Sanitize simple object", function () { + it("should sanitize dirty body.", function (done) { + expect(sanitize({ + a: "<script>Test</script>", + b: '<p onclick="return;">Test</p>', + c: '<img src="/"/>', + }, { allowedKeys: ["c"] })).to.eql({ + a: "", + b: "<p>Test</p>", + c: '<img src="/"/>', + }); + done(); + }); + }); + + describe("XSS bypass by using prototype pollution issue", function () { + it("should sanitize dirty data after prototype pollution.", function (done) { + // eslint-disable-next-line no-extend-native + Object.prototype.allowedTags = ['script']; + expect(sanitize({ + a: "<script>Test</script>", + }, {})).to.eql({ + a: "", + }); + done(); + }); + }); + }); });
Vulnerability mechanics
Root cause
"Missing own-property check in initializeOptions allows prototype-polluted properties to control sanitizer behavior."
Attack vector
An attacker first pollutes the global `Object.prototype` with an `allowedTags` property (e.g., `Object.prototype.allowedTags = ['script']`). When the `sanitize()` function or the `xss()` middleware is subsequently called with an empty or user-controlled options object, the `initializeOptions` function reads the inherited `allowedTags` from the prototype chain [CWE-1321]. Because the inherited `allowedTags` array passes the `Array.isArray` check, the sanitizer only allows the attacker-specified tags (e.g., `
Affected code
The vulnerability resides in the `initializeOptions` function in `lib/sanitize.js`. Before the patch, the function checked `Array.isArray(options.allowedTags)` and `Array.isArray(options.allowedKeys)` without first verifying that those properties were own properties of the `options` object [patch_id=1705088]. This allowed properties inherited from the prototype chain (e.g., `Object.prototype.allowedTags`) to be used as valid sanitization options.
What the fix does
The patch adds `Object.hasOwn(options, "allowedTags")` and `Object.hasOwn(options, "allowedKeys")` checks before the existing `Array.isArray` checks in `initializeOptions` [patch_id=1705088]. `Object.hasOwn()` returns `true` only if the property exists directly on the object, not on its prototype chain. This ensures that inherited properties from a polluted `Object.prototype` are ignored, closing the prototype pollution vector that allowed attackers to bypass XSS sanitization.
Preconditions
- inputThe attacker must be able to pollute the global Object.prototype with an allowedTags or allowedKeys property (e.g., via another vulnerable library or a JSON parsing gadget).
- configThe application must call sanitize() or the xss() middleware with an options object that does not have its own allowedTags/allowedKeys property.
Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-grjp-4jmr-mjcwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-21169ghsaADVISORY
- github.com/AhmedAdelFahim/express-xss-sanitizer/commit/3bf8aaaf4dbb1c209dcb8d87a82711a54c1ab39aghsax_refsource_MISCWEB
- github.com/AhmedAdelFahim/express-xss-sanitizer/issues/4ghsax_refsource_MISCWEB
- runkit.com/embed/w306l6zfm7tughsax_refsource_MISCWEB
- security.snyk.io/vuln/SNYK-JS-EXPRESSXSSSANITIZER-3027443ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.