From 82f37783401676bfd4c8d68a9c415168a4c7db85 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 15 Apr 2025 15:44:13 +1000 Subject: [PATCH] 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. (cherry picked from commit 1c156905f8f282397fc4514dff59efdf41ae645e) --- package.json | 1 + ts/editor/NoteEditor.svelte | 3 ++- ts/lib/domlib/index.ts | 1 + ts/lib/domlib/sanitize.ts | 10 ++++++++++ ts/licenses.json | 34 ++++++++++++++++++++++++++++------ yarn.lock | 20 ++++++++++++++++++++ 6 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 ts/lib/domlib/sanitize.ts diff --git a/package.json b/package.json index d3c771927..3a4d6ad27 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index bf959e266..d4ebcbdda 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -132,7 +132,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; @@ -413,6 +413,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); diff --git a/ts/lib/domlib/index.ts b/ts/lib/domlib/index.ts index 27b21016d..0834e0e77 100644 --- a/ts/lib/domlib/index.ts +++ b/ts/lib/domlib/index.ts @@ -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"; diff --git a/ts/lib/domlib/sanitize.ts b/ts/lib/domlib/sanitize.ts new file mode 100644 index 000000000..d6ae4f049 --- /dev/null +++ b/ts/lib/domlib/sanitize.ts @@ -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 }); +} diff --git a/ts/licenses.json b/ts/licenses.json index 9d6f7d2c1..58815d397 100644 --- a/ts/licenses.json +++ b/ts/licenses.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 4fa454795..886ad262d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1648,6 +1648,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" @@ -2004,6 +2011,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.19.10" esbuild-sass-plugin: "npm:^2" @@ -3154,6 +3162,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"