From da9c3a4032d2c7047b2a78dfa498793a2c7e97b0 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Thu, 21 Aug 2025 11:54:28 +0200 Subject: [PATCH] feat(js): tighten the trusted types policy - Implement a better/simpler polyfill for web browsers that don't supported trusted types yet - Use two separate policies: one to create HTML, another to create/use script urls - Instead of having the policy live in the top-level scope, they're now declared at the lowest possible scope, right before they're used, making them inaccessible outside of it. This puts their usage completely out of reach of an attacker unable to gain some control outside of those two (small) scopes, and thus removes the need to tighten the policies. - Remove the now-unused tt.js file This has been tested on Firefox (doesn't support trusted types) and on Chromium (does support trusted types). --- internal/template/templates/common/layout.html | 4 ++-- internal/ui/static/js/app.js | 13 +++++++++++++ internal/ui/static/js/tt.js | 15 --------------- internal/ui/static/static.go | 1 - 4 files changed, 15 insertions(+), 18 deletions(-) delete mode 100644 internal/ui/static/js/tt.js diff --git a/internal/template/templates/common/layout.html b/internal/template/templates/common/layout.html index 13266c7b..8c4f069d 100644 --- a/internal/template/templates/common/layout.html +++ b/internal/template/templates/common/layout.html @@ -29,7 +29,7 @@ {{ if .user }} {{ $cspNonce := nonce }} - + {{ if .user.Stylesheet -}} @@ -39,7 +39,7 @@ {{ end -}} {{ else -}} - + {{ end -}} diff --git a/internal/ui/static/js/app.js b/internal/ui/static/js/app.js index 612fcff2..a19fcc88 100644 --- a/internal/ui/static/js/app.js +++ b/internal/ui/static/js/app.js @@ -2,6 +2,17 @@ const TOP = 9999; const BOTTOM = -9999; +// Simple Polyfill for browsers that don't support Trusted Types +// See https://caniuse.com/?search=trusted%20types +if (!window.trustedTypes || !trustedTypes.createPolicy) { + window.trustedTypes = { + createPolicy: (name, policy) => ({ + createScriptURL: src => src, + createHTML: html => html, + }) + }; +} + /** * Send a POST request to the specified URL with the given body. * @@ -746,6 +757,7 @@ function handleFetchOriginalContentAction() { response.json().then((data) => { if (data.content && data.reading_time) { + const ttpolicy = trustedTypes.createPolicy('html', {createHTML: html => html}); document.querySelector(".entry-content").innerHTML = ttpolicy.createHTML(data.content); const entryReadingtimeElement = document.querySelector(".entry-reading-time"); if (entryReadingtimeElement) { @@ -1081,6 +1093,7 @@ function initializeServiceWorker() { if ("serviceWorker" in navigator) { const serviceWorkerURL = document.body.dataset.serviceWorkerUrl; if (serviceWorkerURL) { + const ttpolicy = trustedTypes.createPolicy('url', {createScriptURL: src => src}); navigator.serviceWorker.register(ttpolicy.createScriptURL(serviceWorkerURL), { type: "module" }).catch((error) => { diff --git a/internal/ui/static/js/tt.js b/internal/ui/static/js/tt.js deleted file mode 100644 index f42cc47a..00000000 --- a/internal/ui/static/js/tt.js +++ /dev/null @@ -1,15 +0,0 @@ -let ttpolicy; -if (window.trustedTypes && trustedTypes.createPolicy) { - //TODO: use an allow-list for `createScriptURL` - if (!ttpolicy) { - ttpolicy = trustedTypes.createPolicy('ttpolicy', { - createScriptURL: src => src, - createHTML: html => html, - }); - } -} else { - ttpolicy = { - createScriptURL: src => src, - createHTML: html => html, - }; -} diff --git a/internal/ui/static/static.go b/internal/ui/static/static.go index f0fc708f..d4fafa38 100644 --- a/internal/ui/static/static.go +++ b/internal/ui/static/static.go @@ -121,7 +121,6 @@ func GenerateStylesheetsBundles() error { func GenerateJavascriptBundles(webauthnEnabled bool) error { var bundles = map[string][]string{ "app": { - "js/tt.js", // has to be first "js/touch_handler.js", "js/keyboard_handler.js", "js/modal_handler.js",