Compare commits

...

4 Commits

Author SHA1 Message Date
Paul Nothaft 99df3e204f chore(stable): release 3.46.10 (#1332)
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Waiting to run
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Build and Push Docker Images / merge-backend (push) Blocked by required conditions
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Waiting to run
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Waiting to run
Build and Push Docker Images / merge-frontend (push) Blocked by required conditions
Build and Push Docker Images / summary (push) Blocked by required conditions
2026-09-07 23:03:37 +02:00
Paul Nothaft 95e3af0800 Merge pull request #1327 from PicPeak/fix/sanitize-html-2.17.7-stable
fix(security): bump sanitize-html to 2.17.7 (stable)
2026-09-07 09:18:32 +02:00
Paul Nothaft 8421b7b668 fix(setup): require Node 22.12 for sanitize-html 2026-09-07 09:00:02 +02:00
Paul Nothaft 0f426ef699 fix(security): bump sanitize-html to 2.17.7
Trivy flags the backend image on two sanitize-html advisories, both
fixed upstream:

- CVE-2026-63670 (fixed 2.17.6): a literal solidus after a raw-text end
  tag (`</textarea/>`) is treated as text by htmlparser2 and re-emitted
  unescaped, so disallowed markup passes when textarea or xmp is in
  allowedTags.
- CVE-2026-84371 (fixed 2.17.7): an SVG SMIL animation whose
  attributeName selects href lets the sibling values/from/to/by
  attributes carry URLs past the scheme policy.

2.17.5 -> 2.17.7, exact pin as before. The new version brings its own
htmlparser2 12 / domhandler 6 / domutils 4 / dom-serializer 3 /
entities 8 tree under node_modules/sanitize-html; nothing else in the
lock moves.

That tree is ESM-only, so the backend now needs unflagged require(esm):
Node 20.19+ or 22.12+. The image is node:22-alpine and CI runs 22, but
engines.node still admitted 22.0-22.11, where require('sanitize-html')
throws ERR_REQUIRE_ESM at startup (publicSiteService loads it during
initialisation). engines is now ^20.19.0 || >=22.12.0 and the native
setup script's Node check enforces the same range instead of accepting
any 22.x. On the supported versions the sanitiser behaves identically
to 2.17.5 on the tracker and newsletter fixtures.

Jest 29's CommonJS registry cannot evaluate ESM either, so every suite
importing a route or service that uses the sanitiser would fail at
import. jest.config.js now maps `sanitize-html` to jest.sanitizeHtml.js,
which hands that one module to Node's real loader via
process.getBuiltinModule('module') — a plain require('module') inside
Jest is Jest's wrapper and returns an empty object for this package.
Verified against a real 2.17.7 install: the sanitiser suites and a
settings route suite pass; without the mapper they fail with "Cannot
use import statement outside a module".
2026-09-06 23:06:45 +02:00
8 changed files with 161 additions and 19 deletions
+1 -1
View File
@@ -1 +1 @@
{".":"3.46.9"}
{".":"3.46.10"}
+9
View File
@@ -5,6 +5,15 @@ All notable changes to PicPeak will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.46.10](https://github.com/PicPeak/picpeak/compare/v3.46.9...v3.46.10) (2026-09-07)
### Bug Fixes
* **security:** bump sanitize-html to 2.17.7 ([0f426ef](https://github.com/PicPeak/picpeak/commit/0f426ef69968b395c6e3fbd0301fe3a7759a5f44))
* **security:** bump sanitize-html to 2.17.7 (stable) ([95e3af0](https://github.com/PicPeak/picpeak/commit/95e3af080039f2d31e1cb9c85a3d93b22c80ba7c))
* **setup:** require Node 22.12 for sanitize-html ([8421b7b](https://github.com/PicPeak/picpeak/commit/8421b7b668484f87cd2bacda8fb4d95a3bc07ab5))
## [3.46.9](https://github.com/PicPeak/picpeak/compare/v3.46.8...v3.46.9) (2026-09-03)
+5 -1
View File
@@ -12,5 +12,9 @@ module.exports = {
testMatch: [
'**/__tests__/**/*.test.js'
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
// sanitize-html's htmlparser2 12 is ESM-only; see jest.sanitizeHtml.js.
moduleNameMapper: {
'^sanitize-html$': '<rootDir>/jest.sanitizeHtml.js'
}
};
+18
View File
@@ -0,0 +1,18 @@
/**
* sanitize-html 2.17.6+ depends on htmlparser2 12, which ships ESM only.
* Node 22.12+ loads it fine through require(esm); Jest 29's CommonJS module
* registry cannot evaluate an ESM file and fails every suite that imports a
* route or service using the sanitiser. Rather than bolting a Babel
* transform onto node_modules for one dependency, hand this single module to
* Node's own loader.
*
* process.getBuiltinModule (Node 22.3+) is the real core `module` even inside
* Jest — a plain require('module') here returns Jest's wrapper, whose
* createRequire() hands back an empty object for this package. createRequire()
* on the real one resolves from backend/node_modules exactly like production.
*
* Wired in via moduleNameMapper in jest.config.js. The module is stateless,
* so sharing one instance across test files changes nothing; it just cannot
* be jest.mock()ed, and nothing mocks it.
*/
module.exports = process.getBuiltinModule('module').createRequire(__filename)('sanitize-html');
+110 -6
View File
@@ -49,7 +49,7 @@
"postcss": "8.5.23",
"qrcode": "^1.5.4",
"react-i18next": "^15.6.0",
"sanitize-html": "2.17.5",
"sanitize-html": "2.17.7",
"sharp": "0.35.3",
"sqlite3": "^5.1.6",
"swagger-jsdoc": "^6.2.8",
@@ -68,7 +68,7 @@
"supertest": "^6.3.3"
},
"engines": {
"node": "^20.19.0 || >=22"
"node": ">=22.12.0"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
@@ -10853,18 +10853,122 @@
"license": "MIT"
},
"node_modules/sanitize-html": {
"version": "2.17.5",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.5.tgz",
"integrity": "sha512-ZmU1joGRrvoyctKIiuwUxqR6moLoU2Wk+2bMccN6f7UwhAmwYDvWziqPxRDDN2Qip62NqnIrVrT9akbL6Wretg==",
"version": "2.17.7",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.7.tgz",
"integrity": "sha512-PGtEkc9cbnedU3s9TmzDbpsZ8w086g/0Q8k8/oIO1NLNU3i5k9yn835CrjJSajp1KMmkisbO1qPXxNKO3welAg==",
"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/selderee": {
+3 -3
View File
@@ -1,10 +1,10 @@
{
"name": "picpeak-backend",
"version": "3.46.9",
"version": "3.46.10",
"description": "Backend for PicPeak event photo sharing platform",
"main": "server.js",
"engines": {
"node": "^20.19.0 || >=22"
"node": ">=22.12.0"
},
"scripts": {
"start": "node server.js",
@@ -58,7 +58,7 @@
"postcss": "8.5.23",
"qrcode": "^1.5.4",
"react-i18next": "^15.6.0",
"sanitize-html": "2.17.5",
"sanitize-html": "2.17.7",
"sharp": "0.35.3",
"sqlite3": "^5.1.6",
"swagger-jsdoc": "^6.2.8",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "picpeak-frontend",
"private": true,
"version": "3.46.9",
"version": "3.46.10",
"type": "module",
"scripts": {
"dev": "vite",
+14 -7
View File
@@ -14,8 +14,8 @@ IFS=$'\n\t'
readonly SCRIPT_VERSION="2.1.0"
readonly APP_NAME="PicPeak"
readonly REPO_URL="https://github.com/PicPeak/picpeak.git"
readonly NODE_VERSION="20"
readonly NODE_MIN_VERSION="20.19.0" # backend engines: ^20.19.0 || >=22 (sharp 0.35, html-to-text 10)
readonly NODE_VERSION="22"
readonly NODE_MIN_VERSION="22.12.0" # backend engines: >=22.12.0 (sanitize-html 2.17.7)
readonly MIN_RAM_DOCKER=2048
readonly MIN_RAM_NATIVE=1024
readonly MIN_DISK_GB=2
@@ -721,6 +721,13 @@ EOF
# Native Installation
################################################################################
# True when a Node.js version satisfies the backend's engines range (>=22.12.0).
node_version_supported() {
local ver="$1"
[[ "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
[[ "$(printf '%s\n' "$NODE_MIN_VERSION" "$ver" | sort -V | head -1)" == "$NODE_MIN_VERSION" ]]
}
install_nodejs() {
# --update dispatches here before main() runs detect_os, so detect on demand
if [[ -z "$PACKAGE_MANAGER" ]]; then
@@ -729,8 +736,8 @@ install_nodejs() {
local node_ver
node_ver=$(command_exists node && node -v | cut -d'v' -f2 || echo "0")
# backend engines range is ^20.19.0 || >=22 (Node 21 is excluded by the glob/minimatch family)
if [[ "$(printf '%s\n' "$NODE_MIN_VERSION" "$node_ver" | sort -V | head -1)" == "$NODE_MIN_VERSION" && "${node_ver%%.*}" != "21" ]]; then
# Match sanitize-html's declared Node minimum, including strict npm installs.
if node_version_supported "$node_ver"; then
log_success "Node.js $(node -v) is already installed"
return
fi
@@ -748,10 +755,10 @@ install_nodejs() {
;;
esac
# Package managers won't downgrade a newer Node (e.g. 21), so re-verify before continuing
# Re-verify in case the package manager did not replace an unsupported Node.
node_ver=$(command_exists node && node -v | cut -d'v' -f2 || echo "0")
if [[ "$(printf '%s\n' "$NODE_MIN_VERSION" "$node_ver" | sort -V | head -1)" != "$NODE_MIN_VERSION" || "${node_ver%%.*}" == "21" ]]; then
die "Node.js v$node_ver does not satisfy the backend requirement (^$NODE_MIN_VERSION || >=22); remove the current Node.js, install a supported version, then re-run this script"
if ! node_version_supported "$node_ver"; then
die "Node.js v$node_ver does not satisfy the backend requirement (>=$NODE_MIN_VERSION); remove the current Node.js, install a supported version, then re-run this script"
fi
log_success "Node.js installed: $(node -v)"
}