VYPR
Moderate severityNVD Advisory· Published Sep 26, 2022· Updated May 21, 2025

Prototype Pollution

CVE-2022-21169

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].

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.

PackageAffected versionsPatched versions
express-xss-sanitizernpm
< 1.1.31.1.3

Affected products

2

Patches

1
3bf8aaaf4dbb

fix 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

News mentions

0

No linked articles in our index yet.