From 6c94fca66f6392831a1730ac901f4c04ea585de0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 04:29:20 +0000 Subject: [PATCH 1/2] Bump sanitize-html from 2.17.4 to 2.17.6 Bumps [sanitize-html](https://github.com/apostrophecms/apostrophe/tree/HEAD/packages/sanitize-html) from 2.17.4 to 2.17.6. - [Changelog](https://github.com/apostrophecms/apostrophe/blob/main/packages/sanitize-html/CHANGELOG.md) - [Commits](https://github.com/apostrophecms/apostrophe/commits/HEAD/packages/sanitize-html) --- updated-dependencies: - dependency-name: sanitize-html dependency-version: 2.17.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 114 ++++++++++++++++++++++++++++++++-- packages/backend/package.json | 2 +- packages/shared/package.json | 2 +- 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74f3f4889..27677404e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10996,16 +10996,122 @@ "license": "MIT" }, "node_modules/sanitize-html": { - "version": "2.17.4", + "version": "2.17.6", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.6.tgz", + "integrity": "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==", "license": "MIT", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", - "htmlparser2": "^10.1.0", + "htmlparser2": "^12.0.0", "is-plain-object": "^5.0.0", "launder": "^1.7.1", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/sanitize-html/node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/sanitize-html/node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/sanitize-html/node_modules/htmlparser2": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-12.0.0.tgz", + "integrity": "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "domutils": "^4.0.2", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" } }, "node_modules/scheduler": { @@ -12888,7 +12994,7 @@ "pg-boss": "^8.0.0", "pg-cursor": "^2.21.0", "qs": "^6.15.2", - "sanitize-html": "^2.13.1", + "sanitize-html": "^2.17.6", "socket.io": "^4.7.5", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1" @@ -12990,7 +13096,7 @@ "@equal-vote/star-vote-backend": "^1.0.0", "@equal-vote/star-vote-frontend": "^0.1.0", "@types/sanitize-html": "^2.13.0", - "sanitize-html": "^2.13.1", + "sanitize-html": "^2.17.6", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "typescript-json-schema": "^0.65.1" diff --git a/packages/backend/package.json b/packages/backend/package.json index 8b2b497fc..7fb1faf63 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -47,7 +47,7 @@ "pg-boss": "^8.0.0", "pg-cursor": "^2.21.0", "qs": "^6.15.2", - "sanitize-html": "^2.13.1", + "sanitize-html": "^2.17.6", "socket.io": "^4.7.5", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1" diff --git a/packages/shared/package.json b/packages/shared/package.json index 0385009f3..0be84b79d 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -49,7 +49,7 @@ "@equal-vote/star-vote-backend": "^1.0.0", "@equal-vote/star-vote-frontend": "^0.1.0", "@types/sanitize-html": "^2.13.0", - "sanitize-html": "^2.13.1", + "sanitize-html": "^2.17.6", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "typescript-json-schema": "^0.65.1" From d8c5019a8d346bcb5d170122af70393cb11330e4 Mon Sep 17 00:00:00 2001 From: Arend Peter Date: Mon, 3 Aug 2026 12:17:50 -0700 Subject: [PATCH 2/2] Mock sanitize-html --- CLAUDE.md | 1 + package-lock.json | 10 +++++----- .../backend/src/__mocks__/sanitize-html.js | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 packages/backend/src/__mocks__/sanitize-html.js diff --git a/CLAUDE.md b/CLAUDE.md index 92aced0ef..de926d976 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Notes on dependencies - The root `package.json` `overrides` for `qs` exists because Netlify's npm mirror lagged behind npmjs.org for a freshly published patch (`qs@6.15.2`, May 2026) and `npm ci` failed with `ETARGET`. Safe to remove once you can confirm Netlify deploys without it. +- `sanitize-html` >=2.17.6 pulls in `htmlparser2@12`, which dropped its CommonJS build (pure ESM, no `require` export condition). Node 22.12+/24 handles this fine via native `require(esm)`, so the app runs unaffected, but Jest's own module system can't parse it, so backend tests mock `sanitize-html` — see `packages/backend/src/__mocks__/sanitize-html.js` (auto-applied by Jest's node-module manual-mock convention, no config wiring needed). Do **not** "fix" this by downgrading/overriding `htmlparser2` — the 2.17.6 bump is a real XSS security patch (GHSA-jxwj-j7wr-gfrw) and the htmlparser2 upgrade is part of it. Upstream tracked the Jest/ESM friction at apostrophecms/apostrophe#5526 and closed it with no fix — they consider it expected, not a bug in their library. ## Commands diff --git a/package-lock.json b/package-lock.json index 27677404e..e728508b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,11 +24,11 @@ "rimraf": "^5.0.5" }, "optionalDependencies": { - "@rspack/binding-darwin-arm64": "*", - "@rspack/binding-darwin-x64": "*", - "@rspack/binding-linux-arm64-gnu": "*", - "@rspack/binding-linux-x64-gnu": "*", - "@rspack/binding-linux-x64-musl": "*" + "@rspack/binding-darwin-arm64": "latest", + "@rspack/binding-darwin-x64": "latest", + "@rspack/binding-linux-arm64-gnu": "latest", + "@rspack/binding-linux-x64-gnu": "latest", + "@rspack/binding-linux-x64-musl": "latest" } }, "node_modules/@ai-hero/sandcastle": { diff --git a/packages/backend/src/__mocks__/sanitize-html.js b/packages/backend/src/__mocks__/sanitize-html.js new file mode 100644 index 000000000..faa8b8bdc --- /dev/null +++ b/packages/backend/src/__mocks__/sanitize-html.js @@ -0,0 +1,18 @@ +// Jest-only stand-in for `sanitize-html`. The real package pulls in htmlparser2@12, +// which dropped its CommonJS build (ESM-only) and can't be parsed by Jest's module +// system, even though Node itself handles it fine via native require(esm) support. +// See CLAUDE.md "Notes on dependencies" for the full story. +// +// No test in this repo asserts on actual sanitization output, so this passthrough +// is sufficient here. The real, security-patched sanitize-html is still what runs +// in dev/prod — this mock only takes effect under Jest. +function sanitizeHtml(html) { + return html; +} + +sanitizeHtml.defaults = { + allowedTags: [], + allowedAttributes: {}, +}; + +module.exports = sanitizeHtml;