Skip to content

Gems 1.0.2 and 1.0.3 bundle DOMPurify 3.3.0 instead of 3.4.17, bringing back 20 fixed advisories #1325

Open

Description

@romansklenar

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-lockfile in the yarn_build task before yarn build, so a release can't pick up stale dependencies.
  • Optionally, fail the build when the bundled @license DOMPurify version doesn't match yarn.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

  1. changed the title [-]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[/+] on Oct 30, 2026
  2. romansklenar commented on Oct 30, 2026

    @romansklenar
    ContributorAuthor

    @jorgemanrubia it happened again in 1.0.3: the gem on RubyGems bundles DOMPurify 3.3.0 in both lexxy.js and lexxy.min.js, while the v1.0.3 yarn.lock and 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 install before the gem build and stops the build if the bundled DOMPurify doesn't match yarn.lock, so this can't ship silently again. A re-release built from a clean install would fix it for gem users now.

  3. romansklenar commented on Nov 8, 2026

    @romansklenar
    ContributorAuthor

    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:

    Package Bundled in the 1.0.4 gem yarn.lock at v1.0.4
    DOMPurify 3.4.13 3.4.17
    lexical 0.44.0 ("0.44.0+prod.esm" in lexxy.js) 0.52.0
    marked 16.4.1 16.4.2

    The 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 install and checks every bundled package against yarn.lock, which would have stopped this release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions