Repository navigation
Description
The lexxy 1.0.2 and 1.0.3 gems on RubyGems bundle DOMPurify 3.3.0, not the 3.4.17 that both tags' package.json and yarn.lock pin (#1315). The 1.0.1 gem bundled 3.4.13, so anyone who updates the gem past 1.0.1 gets an older sanitizer. That brings back 20 DOMPurify advisories that were already fixed, one of them rated high. The npm packages declare dompurify: ^3.4.17 as a dependency and are fine. Only the JS built into the gem is affected.
The same thing happened with 0.9.33.
Evidence
for v in 1.0.1 1.0.2 1.0.3; do gem fetch lexxy -v $v && gem unpack lexxy-$v.gem; done
grep -o '@license DOMPurify [0-9.]*' lexxy-1.0.1/app/assets/javascript/lexxy.js # @license DOMPurify 3.4.13
grep -o '@license DOMPurify [0-9.]*' lexxy-1.0.2/app/assets/javascript/lexxy.js # @license DOMPurify 3.3.0
grep -o '@license DOMPurify [0-9.]*' lexxy-1.0.3/app/assets/javascript/lexxy.js # @license DOMPurify 3.3.0
grep -o "DOMPurify.version = '[0-9.]*'" lexxy-1.0.3/app/assets/javascript/lexxy.js # DOMPurify.version = '3.3.0'lexxy.min.js in both gems has the same banner, and the 1.0.2 source map resolves node_modules/dompurify/dist/purify.es.mjs. The 1.0.3 gem we checked matches the SHA-256 that RubyGems publishes (2af129e6…221928).
| Gem | Bundled DOMPurify | yarn.lock at the tag |
|---|---|---|
| 0.9.31, 0.9.32, 1.0.0, 1.0.1 | 3.4.13 | 3.4.13 |
| 0.9.33 | 3.3.0 | 3.4.13 |
| 1.0.2 | 3.3.0 | 3.4.17 |
| 1.0.3 | 3.3.0 | 3.4.17 |
Advisories 3.3.0 reintroduces
These are fixed in 3.4.13, which 1.0.1 shipped, and are back in 1.0.2 and 1.0.3:
| Fixed in | Severity | Advisory | Summary |
|---|---|---|---|
| 3.3.2 | medium | GHSA-h8r8-wccr-v5f2 | mXSS via re-contextualization |
| 3.3.2 | medium | GHSA-cj63-jhhr-wcxv | USE_PROFILES prototype pollution allows event handlers |
| 3.3.2 | medium | GHSA-cjmm-f4jc-qw8r | ADD_ATTR predicate skips URI validation |
| 3.4.0 | medium | GHSA-v9jr-rg53-9pgp | Prototype pollution to XSS bypass via CUSTOM_ELEMENT_HANDLING fallback |
| 3.4.0 | medium | GHSA-39q2-94rc-95cp | ADD_TAGS function form bypasses FORBID_TAGS |
| 3.4.0 | medium | GHSA-h7mw-gpvr-xq4m | FORBID_TAGS bypassed by function-based ADD_TAGS predicate |
| 3.4.0 | medium | GHSA-crv5-9vww-q3g8 | SAFE_FOR_TEMPLATES bypass in RETURN_DOM mode |
| 3.4.0 | low | GHSA-9p3w-6h5p-cv75 | XSS via ADD_ATTR/ADD_TAGS predicate state leaking across sanitize() calls |
| 3.4.4 | low | GHSA-4w3q-35jp-p934 | IN_PLACE fails to sanitize cross-window DOM elements |
| 3.4.5 | high | GHSA-87xg-pxx2-7hvx | XSS via selectedcontent re-clone |
| 3.4.6 | medium | GHSA-hpcv-96wg-7vj8 | Cross-realm IN_PLACE sanitization leaves executable markup |
| 3.4.6 | medium | GHSA-r47g-fvhr-h676 | IN_PLACE keeps attributes of a clobbered root element |
| 3.4.7 | medium | GHSA-76mc-f452-cxcm | Hook mutation of data.allowedTags/allowedAttributes pollutes the defaults |
| 3.4.7 | medium | GHSA-rp9w-3fw7-7cwq | IN_PLACE bypass via attached shadow root inside <template>.content |
| 3.4.7 | low | GHSA-x4vx-rjvf-j5p4 | IN_PLACE trusts attacker-controlled nodeName |
| 3.4.8 | low | GHSA-gvmj-g25r-r7wr | SAFE_FOR_TEMPLATES bypass inside <template> content on DOM output modes |
| 3.4.9 | low | GHSA-vxr8-fq34-vvx9 | Trusted Types policy survives clearConfig() |
| 3.4.11 | low | GHSA-cmwh-pvxp-8882 | Permanent ALLOWED_ATTR pollution via setConfig() |
| 3.4.12 | low | GHSA-c2j3-45gr-mqc4 | CUSTOM_ELEMENT_HANDLING bypasses afterSanitizeElements |
| 3.4.13 | low | GHSA-55q2-fjhq-7xh7 | IN_PLACE hook removal leaves a detached subtree executable |
Five more are fixed in 3.4.16 and 3.4.17, the version #1315 moved to: GHSA-6688-9rhm-gjv2, GHSA-p98j-92pf-mc4p, GHSA-c6gr-qvv4-5v3f, GHSA-cg2g-xh64-v4q7 and GHSA-mrv2-3pqr-j5jr. Compared with what v1.0.2's lockfile intends, the gem is missing 25 fixes.
Lexxy calls sanitize() on strings rather than with IN_PLACE, so the IN_PLACE-only advisories shouldn't apply. We haven't audited whether the others can be reached through Lexxy's hooks and per-editor allowlists. That includes the high-severity GHSA-87xg-pxx2-7hvx and the mXSS one, GHSA-h8r8-wccr-v5f2.
It also breaks Lexxy's own Trusted Types opt-out
trustedTypesPolicy() hands DOMPurify an explicit TRUSTED_TYPES_POLICY: null when Lexxy has no policy of its own. The comment above that code says the explicit null exists so that DOMPurify never falls back to creating a dompurify policy. DOMPurify 3.3.0 has no null branch: a falsy value lands in the else branch and creates the dompurify policy anyway (around line 5487 of the 1.0.2 lexxy.js). With 3.4.13 the opt-out works.
Likely cause
app/assets/javascript/ is gitignored, and the gem's JS is built at release time. The Rakefile hooks yarn build into rake build without running yarn install, and none of the workflows in .github/workflows publishes the gem. So the gem bundles whatever is in the node_modules of the machine that runs rake release. DOMPurify 3.3.0 was the locked version until #1229, which moved to 3.4.13 and shipped in 0.9.31. The node_modules used for 0.9.33, 1.0.2 and 1.0.3 must predate that change. The Rakefile at v1.0.3 is unchanged, so nothing in the release process stops it from happening again.
Suggested fix
#1326 implements the build changes below.
- Publish a new release from a clean install.
- Run
yarn install --frozen-lockfilein theyarn_buildtask beforeyarn build, so a release can't pick up stale dependencies. - Optionally, fail the build when the bundled
@license DOMPurifyversion doesn't matchyarn.lock.
We found this while updating an app from 1.0.1 to 1.0.2, and checked 1.0.3 when it came out. We're staying on 1.0.1 until a fixed gem is out.
Activity
[-]Gem 1.0.2 bundles DOMPurify 3.3.0 instead of 3.4.17, bringing back 20 fixed advisories[/-][+]Gems 1.0.2 and 1.0.3 bundle DOMPurify 3.3.0 instead of 3.4.17, bringing back 20 fixed advisories[/+]romansklenar commentedon Oct 30, 2026
@jorgemanrubia it happened again in 1.0.3: the gem on RubyGems bundles DOMPurify 3.3.0 in both
lexxy.jsandlexxy.min.js, while the v1.0.3yarn.lockand the npm package pin 3.4.17. So 1.0.3, like 1.0.2, brings back the 20 DOMPurify advisories listed above. I've updated the description with the 1.0.3 evidence.#1326 runs a frozen
yarn installbefore the gem build and stops the build if the bundled DOMPurify doesn't matchyarn.lock, so this can't ship silently again. A re-release built from a clean install would fix it for gem users now.romansklenar commentedon Nov 8, 2026
Update for 1.0.4: the gem bundles DOMPurify 3.4.13 again, so the 20 advisories listed above are no longer reintroduced. The build is still stale, though. Compared with the v1.0.4
yarn.lock:yarn.lockat v1.0.4lexical"0.44.0+prod.esm"inlexxy.js)markedThe source map carries full
sourcesContent. Every package bundled into the 1.0.4 gem (lexical, all@lexical/*,prismjs,marked,dompurify) is byte-identical to its counterpart in the 1.0.1 gem. So 1.0.4 ships 1.0.1's dependencies: the five DOMPurify fixes in 3.4.16 and 3.4.17 and the Lexical 0.45–0.52 updates aren't in the gem.#1327 builds from a frozen
yarn installand checks every bundled package againstyarn.lock, which would have stopped this release.