Common Weakness Enumeration

CWE-625

Allowed

Permissive Regular Expression

Abstraction: Base · Status: Draft

The product uses a regular expression that does not sufficiently restrict the set of allowed values.

36 vulnerabilities reference this CWE, most recent first.

GHSA-F8R2-VG7X-GH8M

Vulnerability from github – Published: 2026-03-13 20:55 – Updated: 2026-03-13 20:55
VLAI
Summary
OpenClaw: Exec approval allowlist patterns overmatched on POSIX paths
Details

Summary

matchesExecAllowlistPattern normalized patterns and targets with lowercasing and compiled glob matching too broadly on POSIX. In addition, the ? wildcard could match /, which allowed matches to cross path segments.

Impact

These matching rules could overmatch allowlist entries and permit commands or executable paths that an operator did not intend to approve.

Affected versions

openclaw <= 2026.3.8

Patch

Fixed in openclaw 2026.3.11 and included in later releases such as 2026.3.12. Exec allowlist matching now respects the intended path semantics, and regression tests cover the POSIX case-folding and slash-crossing cases.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2026.3.8"
      },
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.3.11"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-178",
      "CWE-625"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-13T20:55:03Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Summary\n\n`matchesExecAllowlistPattern` normalized patterns and targets with lowercasing and compiled glob matching too broadly on POSIX. In addition, the `?` wildcard could match `/`, which allowed matches to cross path segments.\n\n### Impact\n\nThese matching rules could overmatch allowlist entries and permit commands or executable paths that an operator did not intend to approve.\n\n### Affected versions\n\n`openclaw` `\u003c= 2026.3.8`\n\n### Patch\n\nFixed in `openclaw` `2026.3.11` and included in later releases such as `2026.3.12`. Exec allowlist matching now respects the intended path semantics, and regression tests cover the POSIX case-folding and slash-crossing cases.",
  "id": "GHSA-f8r2-vg7x-gh8m",
  "modified": "2026-03-13T20:55:03Z",
  "published": "2026-03-13T20:55:03Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-f8r2-vg7x-gh8m"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/releases/tag/v2026.3.11"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "OpenClaw: Exec approval allowlist patterns overmatched on POSIX paths"
}

GHSA-JXJR-3G7G-3944

Vulnerability from github – Published: 2026-09-08 21:03 – Updated: 2026-09-08 21:03
VLAI
Summary
xmldom: requireWellFormed element/attribute name validation is bypassable via an embedded line terminator
Details

Summary

An embedded line terminator bypasses the requireWellFormed serializer check for element and attribute names. The check was added to fix GHSA-w2rr-34g9-rvrj and GHSA-4w3w-2rp5-g8jm; a name whose first line is well-formed slips past it and is serialized verbatim, so the characters after the line terminator break out of the start/end tag or attribute. Callers who enabled requireWellFormed specifically to neutralize those name-injection issues remain exposed.

Details

xmldom builds every grammar production through a shared regexp builder that compiles with the m flag. The anchored full-string matcher used for element and attribute names, QName_exact = reg('^', QName, '$'), therefore inherits m. When it is applied as QName_exact.test(name) against an already-assembled node name, the m flag makes $ match at an interior line terminator, so the matcher accepts any value in which at least one line is a valid QName; the other lines are never constrained. A payload whose first line is a valid QName, followed by a line terminator and breakout markup, is what yields a working injection.

The serializer emits the accepted name verbatim into element start/end tags and attribute names, so the bytes after the line terminator break out of the intended syntactic position. The check is reached whenever a caller serializes, with requireWellFormed: true, a node whose name was set through programmatic DOM construction (createElement, createElementNS, createAttribute, createAttributeNS) with attacker-influenced input.

Root Cause

  1. A shared regexp builder compiles anchored productions with the m flag.
  2. ^…$ under m are line anchors, not string anchors.
  3. A full-string validator built on such a production (.test()) accepts any string with one conforming line, so a line terminator followed by breakout markup passes.

The triggering line terminators are the ECMAScript LineTerminator set: U+000A, U+000D, U+2028, U+2029.

Proof of Concept

const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');

// Element name carrying an embedded line terminator + breakout markup:
const doc = new DOMImplementation().createDocument(null, 'root', null);
const el = doc.createElement('a\n><script>alert(1)</script');
doc.documentElement.appendChild(el);

// Caller opted into well-formed serialization, expecting invalid names to be rejected:
console.log(new XMLSerializer().serializeToString(doc, { requireWellFormed: true }));
// Observed on the affected version: NO throw; the output contains the injected `><script>…`
// breakout, because the name's first line ("a") satisfies the m-anchored QName check.
// Expected: InvalidStateError (the name is not a valid XML QName).

// Control — a single-line invalid name IS correctly rejected, proving the check is active and
// that only the line terminator defeats it:
const ctrl = new DOMImplementation().createDocument(null, 'root', null);
ctrl.documentElement.appendChild(ctrl.createElement('a b'));
new XMLSerializer().serializeToString(ctrl, { requireWellFormed: true });
// => throws InvalidStateError: The element name "a b" is not a valid XML QName

Impact

  • Bypass of a previously shipped security mitigation. Applications that adopted requireWellFormed: true specifically to neutralize GHSA-w2rr-34g9-rvrj / GHSA-4w3w-2rp5-g8jm remain exposed to element/attribute name injection.
  • XML / markup structure injection, and, where the serialized output is placed into an HTML context, downstream XSS.

Fix Applied

The anchored XML Name/QName validators used by the requireWellFormed serializer no longer treat interior line terminators as satisfying the anchors, so a name is validated against the whole string. A name containing a line terminator is rejected with InvalidStateError, closing the bypass for element and attribute names. The default serialization path is unchanged.

⚠ Opt-in required. Protection is not automatic. Existing serialization calls remain vulnerable unless { requireWellFormed: true } is explicitly passed. Applications that serialize untrusted DOM content should audit all serializeToString() call sites and add it.

Proof of Concept - fixed path

const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');
const doc = new DOMImplementation().createDocument(null, 'root', null);
const el = doc.createElement('a\n><script>alert(1)</script');
doc.documentElement.appendChild(el);

// Default path (require-well-formed off) — unchanged, still emits the name verbatim,
// so the `><script>…` bytes break out of the start tag:
new XMLSerializer().serializeToString(doc);

// Opted-in path — now rejected:
new XMLSerializer().serializeToString(doc, { requireWellFormed: true });
// throws InvalidStateError: The element name "a\n><script>alert(1)</script" is not a valid XML QName

Why the default stays verbatim

The W3C DOM Parsing require-well-formed flag defaults to false, and browser XMLSerializer emits names verbatim when it is unset. Throwing unconditionally would be an unjustified breaking change, so the check stays gated on the caller opting in with { requireWellFormed: true }.

Residual limitation

The default serialization path (no requireWellFormed) still emits names verbatim by design (above). Names introduced through createElement / setAttribute are never validated at creation — those APIs store the name unchecked by design — so the opt-in serializer check remains the only guard on that path.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@xmldom/xmldom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.9.11"
            },
            {
              "fixed": "0.9.12"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.9.11"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-83617"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-625",
      "CWE-91"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-08T21:03:17Z",
    "nvd_published_at": "2026-09-01T15:17:40Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nAn embedded line terminator bypasses the `requireWellFormed` serializer check for element and\nattribute names. The check was added to fix GHSA-w2rr-34g9-rvrj and GHSA-4w3w-2rp5-g8jm; a name whose\nfirst line is well-formed slips past it and is serialized verbatim, so the characters after the line\nterminator break out of the start/end tag or attribute. Callers who enabled `requireWellFormed`\nspecifically to neutralize those name-injection issues remain exposed.\n\n## Details\n\nxmldom builds every grammar production through a shared regexp builder that compiles with the `m`\nflag. The anchored full-string matcher used for element and attribute names, `QName_exact =\nreg(\u0027^\u0027, QName, \u0027$\u0027)`, therefore inherits `m`. When it is applied as `QName_exact.test(name)` against\nan already-assembled node name, the `m` flag makes `$` match at an interior line terminator, so the\nmatcher accepts any value in which **at least one line** is a valid `QName`; the other lines are never\nconstrained. A payload whose first line is a valid `QName`, followed by a line terminator and breakout\nmarkup, is what yields a working injection.\n\nThe serializer emits the accepted name verbatim into element start/end tags and attribute names, so\nthe bytes after the line terminator break out of the intended syntactic position. The check is\nreached whenever a caller serializes, with `requireWellFormed: true`, a node whose name was set\nthrough programmatic DOM construction (`createElement`, `createElementNS`, `createAttribute`,\n`createAttributeNS`) with attacker-influenced input.\n\n### Root Cause\n\n1. A shared regexp builder compiles anchored productions with the `m` flag.\n2. `^\u2026$` under `m` are line anchors, not string anchors.\n3. A full-string validator built on such a production (`.test()`) accepts any string with one\n   conforming line, so a line terminator followed by breakout markup passes.\n\nThe triggering line terminators are the ECMAScript `LineTerminator` set: U+000A, U+000D, U+2028, U+2029.\n\n## Proof of Concept\n\n```js\nconst { DOMImplementation, XMLSerializer } = require(\u0027@xmldom/xmldom\u0027);\n\n// Element name carrying an embedded line terminator + breakout markup:\nconst doc = new DOMImplementation().createDocument(null, \u0027root\u0027, null);\nconst el = doc.createElement(\u0027a\\n\u003e\u003cscript\u003ealert(1)\u003c/script\u0027);\ndoc.documentElement.appendChild(el);\n\n// Caller opted into well-formed serialization, expecting invalid names to be rejected:\nconsole.log(new XMLSerializer().serializeToString(doc, { requireWellFormed: true }));\n// Observed on the affected version: NO throw; the output contains the injected `\u003e\u003cscript\u003e\u2026`\n// breakout, because the name\u0027s first line (\"a\") satisfies the m-anchored QName check.\n// Expected: InvalidStateError (the name is not a valid XML QName).\n\n// Control \u2014 a single-line invalid name IS correctly rejected, proving the check is active and\n// that only the line terminator defeats it:\nconst ctrl = new DOMImplementation().createDocument(null, \u0027root\u0027, null);\nctrl.documentElement.appendChild(ctrl.createElement(\u0027a b\u0027));\nnew XMLSerializer().serializeToString(ctrl, { requireWellFormed: true });\n// =\u003e throws InvalidStateError: The element name \"a b\" is not a valid XML QName\n```\n\n## Impact\n\n- **Bypass of a previously shipped security mitigation.** Applications that adopted\n  `requireWellFormed: true` specifically to neutralize GHSA-w2rr-34g9-rvrj / GHSA-4w3w-2rp5-g8jm\n  remain exposed to element/attribute name injection.\n- **XML / markup structure injection**, and, where the serialized output is placed into an HTML\n  context, downstream XSS.\n\n## Fix Applied\n\nThe anchored XML `Name`/`QName` validators used by the `requireWellFormed` serializer no\nlonger treat interior line terminators as satisfying the anchors, so a name is validated against the\nwhole string. A name containing a line terminator is rejected with `InvalidStateError`, closing the\nbypass for element and attribute names. The default serialization path is unchanged.\n\n\u003e **\u26a0 Opt-in required.** Protection is not automatic. Existing serialization calls remain\n\u003e vulnerable unless `{ requireWellFormed: true }` is explicitly passed. Applications that\n\u003e serialize untrusted DOM content should audit all `serializeToString()` call sites and add it.\n\n### Proof of Concept - fixed path\n\n```js\nconst { DOMImplementation, XMLSerializer } = require(\u0027@xmldom/xmldom\u0027);\nconst doc = new DOMImplementation().createDocument(null, \u0027root\u0027, null);\nconst el = doc.createElement(\u0027a\\n\u003e\u003cscript\u003ealert(1)\u003c/script\u0027);\ndoc.documentElement.appendChild(el);\n\n// Default path (require-well-formed off) \u2014 unchanged, still emits the name verbatim,\n// so the `\u003e\u003cscript\u003e\u2026` bytes break out of the start tag:\nnew XMLSerializer().serializeToString(doc);\n\n// Opted-in path \u2014 now rejected:\nnew XMLSerializer().serializeToString(doc, { requireWellFormed: true });\n// throws InvalidStateError: The element name \"a\\n\u003e\u003cscript\u003ealert(1)\u003c/script\" is not a valid XML QName\n```\n\n### Why the default stays verbatim\n\nThe W3C DOM Parsing require-well-formed flag defaults to false, and browser `XMLSerializer` emits\nnames verbatim when it is unset. Throwing unconditionally would be an unjustified breaking change, so\nthe check stays gated on the caller opting in with `{ requireWellFormed: true }`.\n\n### Residual limitation\n\nThe default serialization path (no `requireWellFormed`) still emits names verbatim by design (above).\nNames introduced through `createElement` / `setAttribute` are never validated at creation \u2014 those APIs\nstore the name unchecked by design \u2014 so the opt-in serializer check remains the only guard on that\npath.",
  "id": "GHSA-jxjr-3g7g-3944",
  "modified": "2026-09-08T21:03:17Z",
  "published": "2026-09-08T21:03:17Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/security/advisories/GHSA-jxjr-3g7g-3944"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-83617"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/pull/1071"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/commit/7b2ec67e1750daadd0bb06c92e875e726544a362"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/xmldom/xmldom"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/releases/tag/0.9.12"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "xmldom: requireWellFormed element/attribute name validation is bypassable via an embedded line terminator"
}

GHSA-M4X6-WX7H-HWVH

Vulnerability from github – Published: 2026-08-10 09:31 – Updated: 2026-08-10 09:31
VLAI
Details

Tegalog -Fumy Otegaru Memo Logger- provided by Nishishi Factory contains a vulnerability due to a permissive regular expression, which may allow an attacker who can access the affected product to log in to the management console. As a result, the attacker may perform any operations available from the management console.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-64940"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-625"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-10T08:16:48Z",
    "severity": "HIGH"
  },
  "details": "Tegalog -Fumy Otegaru Memo Logger- provided by Nishishi Factory contains a vulnerability due to a permissive regular expression, which may allow an attacker who can access the affected product to log in to the management console. As a result, the attacker may perform any operations available from the management console.",
  "id": "GHSA-m4x6-wx7h-hwvh",
  "modified": "2026-08-10T09:31:23Z",
  "published": "2026-08-10T09:31:22Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-64940"
    },
    {
      "type": "WEB",
      "url": "https://jvn.jp/en/jp/JVN99975039"
    },
    {
      "type": "WEB",
      "url": "https://www.nishishi.com/cgi/tegalog/info/notice20260629.shtml"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-PV25-75Q9-7735

Vulnerability from github – Published: 2026-03-06 00:31 – Updated: 2026-03-06 00:31
VLAI
Details

Permissive regular expression in Azure Compute Gallery allows an authorized attacker to elevate privileges locally.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-23651"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-625"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-03-05T23:16:19Z",
    "severity": "MODERATE"
  },
  "details": "Permissive regular expression in Azure Compute Gallery allows an authorized attacker to elevate privileges locally.",
  "id": "GHSA-pv25-75q9-7735",
  "modified": "2026-03-06T00:31:34Z",
  "published": "2026-03-06T00:31:34Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-23651"
    },
    {
      "type": "WEB",
      "url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-23651"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-QV7J-4883-HWH7

Vulnerability from github – Published: 2026-04-02 20:35 – Updated: 2026-05-13 16:18
VLAI
Summary
Rack::Sendfile header-based X-Accel-Mapping regex injection enables unauthorized X-Accel-Redirect
Details

Summary

Rack::Sendfile#map_accel_path interpolates the value of the X-Accel-Mapping request header directly into a regular expression when rewriting file paths for X-Accel-Redirect. Because the header value is not escaped, an attacker who can supply X-Accel-Mapping to the backend can inject regex metacharacters and control the generated X-Accel-Redirect response header.

In deployments using Rack::Sendfile with x-accel-redirect, this can allow an attacker to cause nginx to serve unintended files from configured internal locations.

Details

Rack::Sendfile#map_accel_path processes header-supplied mappings using logic equivalent to:

mapping.split(',').map(&:strip).each do |m|
  internal, external = m.split('=', 2).map(&:strip)
  new_path = path.sub(/\A#{internal}/i, external)
  return new_path unless path == new_path
end

Here, internal comes from the HTTP_X_ACCEL_MAPPING request header and is inserted directly into a regular expression without escaping. This gives the header value regex semantics rather than treating it as a literal prefix.

As a result, an attacker can supply metacharacters such as .* or capture groups to alter how the path substitution is performed. For example, a mapping such as:

X-Accel-Mapping: .*=/protected/secret.txt

causes the entire source path to match and rewrites the redirect target to a clean attacker-chosen internal path.

This differs from the documented behavior of the header-based mapping path, which is described as a simple substitution. While application-supplied mappings may intentionally support regular expressions, header-supplied mappings should be treated as literal path prefixes.

The issue is only exploitable when untrusted X-Accel-Mapping headers can reach Rack. One realistic case is a reverse proxy configuration that intends to set X-Accel-Mapping itself, but fails to do so on some routes, allowing a client-supplied header to pass through unchanged.

Impact

Applications using Rack::Sendfile with x-accel-redirect may be affected if the backend accepts attacker-controlled X-Accel-Mapping headers.

In affected deployments, an attacker may be able to control the X-Accel-Redirect response header and cause nginx to serve files from internal locations that were not intended to be reachable through the application. This can lead to unauthorized file disclosure.

The practical impact depends on deployment architecture. If the proxy always strips or overwrites X-Accel-Mapping, or if the application uses explicit configured mappings instead of the request header, exploitability may be eliminated.

Mitigation

  • Update to a patched version of Rack that treats header-supplied X-Accel-Mapping values as literal strings rather than regular expressions.
  • Strip or overwrite inbound X-Accel-Mapping headers at the reverse proxy so client-supplied values never reach Rack.
  • Prefer explicit application-configured sendfile mappings instead of relying on request-header mappings.
  • Review proxy sub-locations and inherited header settings to ensure X-Accel-Mapping is consistently set on all backend routes.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "rack"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.2.23"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "rack"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0.beta1"
            },
            {
              "fixed": "3.1.21"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "rack"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.2.0"
            },
            {
              "fixed": "3.2.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-34830"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-625"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-02T20:35:23Z",
    "nvd_published_at": "2026-04-02T17:16:26Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\n`Rack::Sendfile#map_accel_path` interpolates the value of the `X-Accel-Mapping` request header directly into a regular expression when rewriting file paths for `X-Accel-Redirect`. Because the header value is not escaped, an attacker who can supply `X-Accel-Mapping` to the backend can inject regex metacharacters and control the generated `X-Accel-Redirect` response header.\n\nIn deployments using `Rack::Sendfile` with `x-accel-redirect`, this can allow an attacker to cause nginx to serve unintended files from configured internal locations.\n\n## Details\n\n`Rack::Sendfile#map_accel_path` processes header-supplied mappings using logic equivalent to:\n\n```ruby\nmapping.split(\u0027,\u0027).map(\u0026:strip).each do |m|\n  internal, external = m.split(\u0027=\u0027, 2).map(\u0026:strip)\n  new_path = path.sub(/\\A#{internal}/i, external)\n  return new_path unless path == new_path\nend\n```\n\nHere, `internal` comes from the `HTTP_X_ACCEL_MAPPING` request header and is inserted directly into a regular expression without escaping. This gives the header value regex semantics rather than treating it as a literal prefix.\n\nAs a result, an attacker can supply metacharacters such as `.*` or capture groups to alter how the path substitution is performed. For example, a mapping such as:\n\n```http\nX-Accel-Mapping: .*=/protected/secret.txt\n```\n\ncauses the entire source path to match and rewrites the redirect target to a clean attacker-chosen internal path.\n\nThis differs from the documented behavior of the header-based mapping path, which is described as a simple substitution. While application-supplied mappings may intentionally support regular expressions, header-supplied mappings should be treated as literal path prefixes.\n\nThe issue is only exploitable when untrusted `X-Accel-Mapping` headers can reach Rack. One realistic case is a reverse proxy configuration that intends to set `X-Accel-Mapping` itself, but fails to do so on some routes, allowing a client-supplied header to pass through unchanged.\n\n## Impact\n\nApplications using `Rack::Sendfile` with `x-accel-redirect` may be affected if the backend accepts attacker-controlled `X-Accel-Mapping` headers.\n\nIn affected deployments, an attacker may be able to control the `X-Accel-Redirect` response header and cause nginx to serve files from internal locations that were not intended to be reachable through the application. This can lead to unauthorized file disclosure.\n\nThe practical impact depends on deployment architecture. If the proxy always strips or overwrites `X-Accel-Mapping`, or if the application uses explicit configured mappings instead of the request header, exploitability may be eliminated.\n\n## Mitigation\n\n* Update to a patched version of Rack that treats header-supplied `X-Accel-Mapping` values as literal strings rather than regular expressions.\n* Strip or overwrite inbound `X-Accel-Mapping` headers at the reverse proxy so client-supplied values never reach Rack.\n* Prefer explicit application-configured sendfile mappings instead of relying on request-header mappings.\n* Review proxy sub-locations and inherited header settings to ensure `X-Accel-Mapping` is consistently set on all backend routes.",
  "id": "GHSA-qv7j-4883-hwh7",
  "modified": "2026-05-13T16:18:57Z",
  "published": "2026-04-02T20:35:23Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rack/rack/security/advisories/GHSA-qv7j-4883-hwh7"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34830"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rack/rack"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/rack/CVE-2026-34830.yml"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Rack::Sendfile header-based X-Accel-Mapping regex injection enables unauthorized X-Accel-Redirect"
}

GHSA-VR34-HP96-76PP

Vulnerability from github – Published: 2026-09-08 21:02 – Updated: 2026-09-08 21:02
VLAI
Summary
xmldom: requireWellFormed DocType publicId/systemId validation is bypassable via an embedded line terminator
Details

Summary

An embedded line terminator bypasses the requireWellFormed serializer check for a DocumentType's publicId and systemId. The check was added to fix GHSA-f6ww-3ggp-fr8h; an id whose first line is a valid literal slips past it and is emitted verbatim into the <!DOCTYPE …> declaration, so the markup after the line terminator breaks out into the surrounding document. Callers who enabled requireWellFormed to neutralize DocumentType injection remain exposed.

Details

publicId and systemId are stored as raw values including their surrounding quotes, and the PubidLiteral/SystemLiteral productions include those quotes. The serializer validates them with g.PubidLiteral_match.test(publicId) and g.SystemLiteral_match.test(systemId), where both matchers are reg('^', …, '$') and inherit the m flag from xmldom's shared regexp builder. Under m, $ matches at an interior line terminator, so a value such as "valid pubid"\n"><!ENTITY …> satisfies the matcher on its first line ("valid pubid" is a complete PubidLiteral) and the whole value — including the post-newline breakout — is emitted after PUBLIC/SYSTEM.

Root Cause

  1. A shared regexp builder compiles anchored productions with the m flag.
  2. ^…$ under m are line anchors, not string anchors.
  3. A full-string validator built on such a production (.test()) accepts any string with one conforming line, so a complete, valid literal on the first line passes even though a line terminator and breakout markup follow. PubidChar excluding </> does not prevent it — the breakout is appended after the literal, not embedded inside it.

The triggering line terminators are the ECMAScript LineTerminator set: U+000A, U+000D, U+2028, U+2029.

Affected Versions

Only @xmldom/xmldom 0.9.x is affected. The vulnerable matchers are built by lib/grammar.js's m-flagged reg() builder, and the DocType publicId/systemId requireWellFormed check that consumes them was introduced in 0.9.10 (the GHSA-f6ww-3ggp-fr8h fix); 0.9.10 and 0.9.11 carry it. 0.8.x performs the same requireWellFormed check with inline, non-m regular expressions and is not affected. The unscoped xmldom package has no grammar.js and no requireWellFormed serializer, so there is no check to bypass.

Proof of Concept

const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');
const impl = new DOMImplementation();

// publicId: complete literal on line 1, then newline + breakout
const dt = impl.createDocumentType('html', '"valid pubid"\n"><!ENTITY xxe SYSTEM "file:///etc/passwd">', '');
const doc = impl.createDocument(null, 'root', dt);
console.log(new XMLSerializer().serializeToString(doc, { requireWellFormed: true }));
// Observed (no throw):
//   <!DOCTYPE html PUBLIC "valid pubid"
//   "><!ENTITY xxe SYSTEM "file:///etc/passwd">><root/>
// Expected: InvalidStateError (publicId is not a valid PubidLiteral).
// Control: a single-line invalid publicId ("no-surrounding-quotes<>") DOES throw InvalidStateError,
// confirming the check is active and specifically bypassed by the line terminator.

Impact

  • Bypass of the GHSA-f6ww-3ggp-fr8h mitigation. Applications that adopted requireWellFormed: true to neutralize DocumentType injection remain exposed.
  • XML structure injection into the DOCTYPE, including injected markup / entity declarations after the public or system identifier.

Fix Applied

The anchored PubidLiteral/SystemLiteral validators used by the requireWellFormed serializer no longer treat an interior line terminator as satisfying the $ anchor, so a publicId or systemId containing any ECMAScript LineTerminator (U+000A, U+000D, U+2028, U+2029) is rejected with InvalidStateError. Valid single-line identifiers serialize unchanged, and the default serialization path is unaffected.

⚠ Opt-in required. Protection is not automatic. Existing serialization calls remain vulnerable unless { requireWellFormed: true } is explicitly passed. Applications that serialize untrusted DOM content should audit all serializeToString() call sites and add it.

Proof of Concept - fixed path

const { DOMImplementation, XMLSerializer } = require('@xmldom/xmldom');
const impl = new DOMImplementation();
const dt = impl.createDocumentType('html', '"valid pubid"\n"><!ENTITY xxe SYSTEM "file:///etc/passwd">', '');
const doc = impl.createDocument(null, 'root', dt);

// Default path (requireWellFormed off) — unchanged, still emits verbatim:
console.log(new XMLSerializer().serializeToString(doc));
//   <!DOCTYPE html PUBLIC "valid pubid"
//   "><!ENTITY xxe SYSTEM "file:///etc/passwd">><root/>

// Opt-in path — now throws instead of emitting the breakout:
new XMLSerializer().serializeToString(doc, { requireWellFormed: true });
//   InvalidStateError: DocumentType publicId is not a valid PubidLiteral

Why the default stays verbatim

The W3C DOM Parsing "require well-formed" flag defaults to false, and a browser XMLSerializer emits the DOCTYPE verbatim. Unconditionally throwing on a malformed publicId/systemId would be an unjustified breaking change to the default path, so the fix tightens only the opt-in requireWellFormed validator, matching browser and spec defaults.

Residual limitation

The guarantee holds only for callers that pass { requireWellFormed: true }; the default serialization path still emits publicId/systemId verbatim. publicId and systemId are not validated at creation (createDocumentType) or on direct property assignment (documentType.publicId = …) — the WHATWG DOM specification places no well-formedness constraint on these fields at creation time, so the serializer is the spec-aligned enforcement point.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.9.11"
      },
      "package": {
        "ecosystem": "npm",
        "name": "@xmldom/xmldom"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.9.10"
            },
            {
              "fixed": "0.9.12"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-83618"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-625",
      "CWE-91"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-08T21:02:25Z",
    "nvd_published_at": "2026-09-01T15:17:40Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nAn embedded line terminator bypasses the `requireWellFormed` serializer check for a `DocumentType`\u0027s\npublicId and systemId. The check was added to fix GHSA-f6ww-3ggp-fr8h; an id whose first line is a\nvalid literal slips past it and is emitted verbatim into the `\u003c!DOCTYPE \u2026\u003e` declaration, so the markup\nafter the line terminator breaks out into the surrounding document. Callers who enabled\n`requireWellFormed` to neutralize DocumentType injection remain exposed.\n\n## Details\n\n`publicId` and `systemId` are stored as raw values **including their surrounding quotes**, and the\n`PubidLiteral`/`SystemLiteral` productions include those quotes. The serializer validates them with\n`g.PubidLiteral_match.test(publicId)` and `g.SystemLiteral_match.test(systemId)`, where both matchers\nare `reg(\u0027^\u0027, \u2026, \u0027$\u0027)` and inherit the `m` flag from xmldom\u0027s shared regexp builder. Under `m`, `$`\nmatches at an interior line terminator, so a value such as `\"valid pubid\"\\n\"\u003e\u003c!ENTITY \u2026\u003e` satisfies\nthe matcher on its first line (`\"valid pubid\"` is a complete `PubidLiteral`) and the whole value \u2014\nincluding the post-newline breakout \u2014 is emitted after `PUBLIC`/`SYSTEM`.\n\n### Root Cause\n\n1. A shared regexp builder compiles anchored productions with the `m` flag.\n2. `^\u2026$` under `m` are line anchors, not string anchors.\n3. A full-string validator built on such a production (`.test()`) accepts any string with one\n   conforming line, so a complete, valid literal on the first line passes even though a line terminator\n   and breakout markup follow. `PubidChar` excluding `\u003c`/`\u003e` does not prevent it \u2014 the breakout is\n   appended *after* the literal, not embedded inside it.\n\nThe triggering line terminators are the ECMAScript `LineTerminator` set: U+000A, U+000D, U+2028, U+2029.\n\n## Affected Versions\n\nOnly `@xmldom/xmldom` 0.9.x is affected. The vulnerable matchers are built by `lib/grammar.js`\u0027s\n`m`-flagged `reg()` builder, and the DocType `publicId`/`systemId` `requireWellFormed` check that\nconsumes them was introduced in 0.9.10 (the GHSA-f6ww-3ggp-fr8h fix); 0.9.10 and 0.9.11 carry it.\n`0.8.x` performs the same `requireWellFormed` check with inline, non-`m` regular expressions and is not\naffected. The unscoped `xmldom` package has no `grammar.js` and no `requireWellFormed` serializer, so\nthere is no check to bypass.\n\n## Proof of Concept\n\n```js\nconst { DOMImplementation, XMLSerializer } = require(\u0027@xmldom/xmldom\u0027);\nconst impl = new DOMImplementation();\n\n// publicId: complete literal on line 1, then newline + breakout\nconst dt = impl.createDocumentType(\u0027html\u0027, \u0027\"valid pubid\"\\n\"\u003e\u003c!ENTITY xxe SYSTEM \"file:///etc/passwd\"\u003e\u0027, \u0027\u0027);\nconst doc = impl.createDocument(null, \u0027root\u0027, dt);\nconsole.log(new XMLSerializer().serializeToString(doc, { requireWellFormed: true }));\n// Observed (no throw):\n//   \u003c!DOCTYPE html PUBLIC \"valid pubid\"\n//   \"\u003e\u003c!ENTITY xxe SYSTEM \"file:///etc/passwd\"\u003e\u003e\u003croot/\u003e\n// Expected: InvalidStateError (publicId is not a valid PubidLiteral).\n// Control: a single-line invalid publicId (\"no-surrounding-quotes\u003c\u003e\") DOES throw InvalidStateError,\n// confirming the check is active and specifically bypassed by the line terminator.\n```\n\n## Impact\n\n- **Bypass of the GHSA-f6ww-3ggp-fr8h mitigation.** Applications that adopted `requireWellFormed:\n  true` to neutralize DocumentType injection remain exposed.\n- **XML structure injection into the DOCTYPE**, including injected markup / entity declarations after\n  the public or system identifier.\n\n## Fix Applied\n\nThe anchored `PubidLiteral`/`SystemLiteral` validators used by the `requireWellFormed`\nserializer no longer treat an interior line terminator as satisfying the `$` anchor, so a `publicId`\nor `systemId` containing any ECMAScript `LineTerminator` (U+000A, U+000D, U+2028, U+2029) is rejected\nwith `InvalidStateError`. Valid single-line identifiers serialize unchanged, and the default\nserialization path is unaffected.\n\n\u003e **\u26a0 Opt-in required.** Protection is not automatic. Existing serialization calls remain vulnerable\n\u003e unless `{ requireWellFormed: true }` is explicitly passed. Applications that serialize untrusted DOM\n\u003e content should audit all `serializeToString()` call sites and add it.\n\n### Proof of Concept - fixed path\n\n```js\nconst { DOMImplementation, XMLSerializer } = require(\u0027@xmldom/xmldom\u0027);\nconst impl = new DOMImplementation();\nconst dt = impl.createDocumentType(\u0027html\u0027, \u0027\"valid pubid\"\\n\"\u003e\u003c!ENTITY xxe SYSTEM \"file:///etc/passwd\"\u003e\u0027, \u0027\u0027);\nconst doc = impl.createDocument(null, \u0027root\u0027, dt);\n\n// Default path (requireWellFormed off) \u2014 unchanged, still emits verbatim:\nconsole.log(new XMLSerializer().serializeToString(doc));\n//   \u003c!DOCTYPE html PUBLIC \"valid pubid\"\n//   \"\u003e\u003c!ENTITY xxe SYSTEM \"file:///etc/passwd\"\u003e\u003e\u003croot/\u003e\n\n// Opt-in path \u2014 now throws instead of emitting the breakout:\nnew XMLSerializer().serializeToString(doc, { requireWellFormed: true });\n//   InvalidStateError: DocumentType publicId is not a valid PubidLiteral\n```\n\n### Why the default stays verbatim\n\nThe W3C DOM Parsing \"require well-formed\" flag defaults to false, and a browser `XMLSerializer` emits\nthe DOCTYPE verbatim. Unconditionally throwing on a malformed `publicId`/`systemId` would be an\nunjustified breaking change to the default path, so the fix tightens only the opt-in\n`requireWellFormed` validator, matching browser and spec defaults.\n\n### Residual limitation\n\nThe guarantee holds only for callers that pass `{ requireWellFormed: true }`; the default\nserialization path still emits `publicId`/`systemId` verbatim. `publicId` and `systemId` are not\nvalidated at creation (`createDocumentType`) or on direct property assignment\n(`documentType.publicId = \u2026`) \u2014 the WHATWG DOM specification places no well-formedness constraint on\nthese fields at creation time, so the serializer is the spec-aligned enforcement point.",
  "id": "GHSA-vr34-hp96-76pp",
  "modified": "2026-09-08T21:02:25Z",
  "published": "2026-09-08T21:02:25Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/security/advisories/GHSA-vr34-hp96-76pp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-83618"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/pull/1071"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/commit/7b2ec67e1750daadd0bb06c92e875e726544a362"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/xmldom/xmldom"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xmldom/xmldom/releases/tag/0.9.12"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "xmldom: requireWellFormed DocType publicId/systemId validation is bypassable via an embedded line terminator"
}

Mitigation
Implementation

When applicable, ensure that the regular expression marks beginning and ending string patterns, such as "/^string$/" for Perl.

No CAPEC attack patterns related to this CWE.