Sanitize field content in editor

The editor already strips script tags from fields, but was allowing
through Javascript in things like onclick handlers. We block this now,
as the editor context has access to internal APIs that we don't want to
expose to untrusted third-party code.
This commit is contained in:
Damien Elmes 2025-04-15 15:44:13 +10:00
parent 066f5fd281
commit 1c156905f8
6 changed files with 62 additions and 7 deletions

View file

@ -69,6 +69,7 @@
"bootstrap-icons": "^1.10.5",
"codemirror": "^5.63.1",
"d3": "^7.0.0",
"dompurify": "^3.2.5",
"fabric": "^5.3.0",
"hammerjs": "^2.0.8",
"intl-pluralrules": "^2.0.0",

View file

@ -133,7 +133,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
for (const [index, [, fieldContent]] of fs.entries()) {
fieldStores[index].set(fieldContent);
fieldStores[index].set(sanitize(fieldContent));
}
fieldNames = newFieldNames;
@ -424,6 +424,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} from "../routes/image-occlusion/store";
import CollapseLabel from "./CollapseLabel.svelte";
import * as oldEditorAdapter from "./old-editor-adapter";
import { sanitize } from "$lib/domlib";
$: isIOImageLoaded = false;
$: ioImageLoadedStore.set(isIOImageLoaded);

View file

@ -5,4 +5,5 @@ export * from "./content-editable";
export * from "./location";
export * from "./move-nodes";
export * from "./place-caret";
export * from "./sanitize";
export * from "./surround";

10
ts/lib/domlib/sanitize.ts Normal file
View file

@ -0,0 +1,10 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import DOMPurify from "dompurify";
export function sanitize(html: string): string {
// We need to treat the text as a document fragment, or a style tag
// at the start of input will be discarded.
return DOMPurify.sanitize(html, { FORCE_BODY: true });
}

View file

@ -57,6 +57,12 @@
"path": "node_modules/@tootallnate/once",
"licenseFile": "node_modules/@tootallnate/once/LICENSE"
},
"@types/trusted-types@2.0.7": {
"licenses": "MIT",
"repository": "https://github.com/DefinitelyTyped/DefinitelyTyped",
"path": "node_modules/@types/trusted-types",
"licenseFile": "node_modules/@types/trusted-types/LICENSE"
},
"abab@2.0.6": {
"licenses": "BSD-3-Clause",
"repository": "https://github.com/jsdom/abab",
@ -95,8 +101,8 @@
"repository": "https://github.com/TooTallNate/node-agent-base",
"publisher": "Nathan Rajlich",
"email": "nathan@tootallnate.net",
"path": "node_modules/agent-base",
"licenseFile": "node_modules/agent-base/README.md"
"path": "node_modules/http-proxy-agent/node_modules/agent-base",
"licenseFile": "node_modules/http-proxy-agent/node_modules/agent-base/README.md"
},
"asynckit@0.4.0": {
"licenses": "MIT",
@ -436,6 +442,14 @@
"path": "node_modules/domexception",
"licenseFile": "node_modules/domexception/LICENSE.txt"
},
"dompurify@3.2.5": {
"licenses": "(MPL-2.0 OR Apache-2.0)",
"repository": "https://github.com/cure53/DOMPurify",
"publisher": "Dr.-Ing. Mario Heiderich, Cure53",
"email": "mario@cure53.de",
"path": "node_modules/dompurify",
"licenseFile": "node_modules/dompurify/LICENSE"
},
"empty-npm-package@1.0.0": {
"licenses": "ISC",
"path": "node_modules/canvas"
@ -572,6 +586,14 @@
"path": "node_modules/lodash-es",
"licenseFile": "node_modules/lodash-es/LICENSE"
},
"lru-cache@10.4.3": {
"licenses": "ISC",
"repository": "https://github.com/isaacs/node-lru-cache",
"publisher": "Isaac Z. Schlueter",
"email": "i@izs.me",
"path": "node_modules/lru-cache",
"licenseFile": "node_modules/lru-cache/LICENSE"
},
"marked@5.1.2": {
"licenses": "MIT",
"repository": "https://github.com/markedjs/marked",
@ -768,16 +790,16 @@
"repository": "https://github.com/jsdom/whatwg-url",
"publisher": "Sebastian Mayr",
"email": "github@smayr.name",
"path": "node_modules/whatwg-url",
"licenseFile": "node_modules/whatwg-url/LICENSE.txt"
"path": "node_modules/jsdom/node_modules/whatwg-url",
"licenseFile": "node_modules/jsdom/node_modules/whatwg-url/LICENSE.txt"
},
"whatwg-url@11.0.0": {
"licenses": "MIT",
"repository": "https://github.com/jsdom/whatwg-url",
"publisher": "Sebastian Mayr",
"email": "github@smayr.name",
"path": "node_modules/data-urls/node_modules/whatwg-url",
"licenseFile": "node_modules/data-urls/node_modules/whatwg-url/LICENSE.txt"
"path": "node_modules/whatwg-url",
"licenseFile": "node_modules/whatwg-url/LICENSE.txt"
},
"ws@8.18.0": {
"licenses": "MIT",

View file

@ -1662,6 +1662,13 @@ __metadata:
languageName: node
linkType: hard
"@types/trusted-types@npm:^2.0.7":
version: 2.0.7
resolution: "@types/trusted-types@npm:2.0.7"
checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.60.1":
version: 5.62.0
resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0"
@ -2018,6 +2025,7 @@ __metadata:
cross-env: "npm:^7.0.2"
d3: "npm:^7.0.0"
diff: "npm:^5.0.0"
dompurify: "npm:^3.2.5"
dprint: "npm:^0.47.2"
esbuild: "npm:^0.25.0"
esbuild-sass-plugin: "npm:^2"
@ -3169,6 +3177,18 @@ __metadata:
languageName: node
linkType: hard
"dompurify@npm:^3.2.5":
version: 3.2.5
resolution: "dompurify@npm:3.2.5"
dependencies:
"@types/trusted-types": "npm:^2.0.7"
dependenciesMeta:
"@types/trusted-types":
optional: true
checksum: 10c0/b564167cc588933ad2d25c185296716bdd7124e9d2a75dac76efea831bb22d1230ce5205a1ab6ce4c1010bb32ac35f7a5cb2dd16c78cbf382111f1228362aa59
languageName: node
linkType: hard
"domutils@npm:^3.0.1":
version: 3.1.0
resolution: "domutils@npm:3.1.0"