From 0e93613403cbae17ccd470da6332271740630106 Mon Sep 17 00:00:00 2001 From: Chris Zuber Date: Thu, 5 Mar 2026 13:37:31 -0800 Subject: [PATCH 1/2] Harden --- home.js | 31 +++++++++++++++++++------------ http.config.js | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/home.js b/home.js index 4a6614a..aadb198 100644 --- a/home.js +++ b/home.js @@ -2,11 +2,12 @@ import { COMMANDS, ROOT_COMMANDS } from './consts.js'; import { getCommandWithArgs } from './utils.js'; import { Importmap } from '@shgysk8zer0/importmap'; -const importmap = new Importmap(); -await importmap.importLocalPackage(); -const integrity = await importmap.getIntegrity(); - -const script = `import { observeCommands, initRootCommands, registerRootCommand, registerCommand } from '/commands.js' +const importmap = new Importmap(); // Serializes to `{"imports": {...}, "scropes": {}}` from a trusted library +await importmap.importLocalPackage(); // Adds the local project/package to the `imports` via `package.json` +const importmapSRI = await importmap.getIntegrity({ algo: 'SHA-384' }); +const POLYFILL_SRI = 'sha384-7yz8QDqFdoZyAnsrpHCA/lGoTyWgsFBb4jWnbCDDEDI/AUhhqgBPDhAejhhSRgAN'; +const script = ` +import { observeCommands, initRootCommands, registerRootCommand, registerCommand } from '/commands.js' import properties from '@aegisjsproject/styles/css/properties.css' with { type: 'css' }; import theme from '@aegisjsproject/styles/css/theme.css' with { type: 'css' }; import btn from '@aegisjsproject/styles/css/button.css' with { type: 'css' }; @@ -17,8 +18,7 @@ registerRootCommand('--menu', () => document.getElementById('menu').showPopover( registerRootCommand('--help', () => document.getElementById('help').showPopover()); registerRootCommand('--log', (event, ...args) => console.log({ event, args })); observeCommands(); -initRootCommands(); -`; +initRootCommands();`; const style = `:root { color-scheme: dark; @@ -29,26 +29,33 @@ const style = `:root { }`; const sri = async text => await crypto.subtle.digest('SHA-384', new TextEncoder().encode(text)) - .then(digest => new Uint8Array(digest).toBase64()) - .then(hash => `sha384-${hash}`); + .then(digest => 'sha384-' + new Uint8Array(digest).toBase64()); const scriptSRI = await sri(script); const styleSRI = await sri(style); const headers = new Headers({ 'Content-Type': 'text/html', - 'Content-Security-Policy': `default-src 'self'; script-src 'self' '${scriptSRI}' '${integrity}'; style-src ${importmap.resolve('@aegisjsproject/styles/css/')} '${styleSRI}'; media-src https://0eff4f4c-7f45-405c-8cf6-f7a3b3c1f07e.mdnplay.dev; trusted-types aegis-sanitizer#html; require-trusted-types-for 'script';`, + 'Referrer-Policy': 'no-referrer', + // Cannot add `integrity` to an `import`ed module + // 'Integrity-Policy': 'blocked-destinations=(script)', + 'Content-Security-Policy': `default-src 'none'; script-src 'self' '${scriptSRI}' '${POLYFILL_SRI}' '${importmapSRI}'; style-src ${importmap.resolve('@aegisjsproject/styles/css/')} '${styleSRI}'; font-src 'self'; base-uri 'self'; manifest-src 'self'; worker-src 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; child-src 'self'; connect-src 'self'; img-src 'self' blob:; media-src https://0eff4f4c-7f45-405c-8cf6-f7a3b3c1f07e.mdnplay.dev; sandbox allow-scripts allow-modals allow-popups allow-forms allow-downloads allow-top-navigation-by-user-activation allow-same-origin; trusted-types aegis-sanitizer#html; require-trusted-types-for 'script'; upgrade-insecure-requests;`, + 'Permissions-Policy': 'accelerometer=(), ambient-light-sensor=(), attribution-reporting=(), autoplay=(), battery=(), bluetooth=(), browsing-topics=(), camera=(self), compute-pressure=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(self), gamepad=(), geolocation=(self), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(self), magnetometer=(), microphone=(self), midi=(), otp-credentials=(), payment=(), picture-in-picture=(self), publickey-credentials-get=(self), screen-wake-lock=(self), serial=(), speaker-selection=(), storage-access=(), usb=(), web-share=(self), window-management=(), xr-spatial-tracking=()', + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Resource-Policy': 'same-site', + 'Origin-Agent-Cluster': '?1', }); - const doc = ` + ${await importmap.getScript()} - + diff --git a/http.config.js b/http.config.js index 8bf6bfe..b356d7f 100644 --- a/http.config.js +++ b/http.config.js @@ -1,6 +1,32 @@ +import { readFile } from 'node:fs/promises'; +const icon = ` + +`; + +const sri = async text => await crypto.subtle.digest('SHA-384', new TextEncoder().encode(text)) + .then(digest => 'sha384-' + new Uint8Array(digest).toBase64()); + +const commands = await readFile('commands.js', { encoding: 'utf-8' }); +const scriptSRI = await sri(commands); +const iconSRI = await sri(icon); + export default { open: true, routes: { '/': import.meta.resolve('./home.js'), + '/favicon.svg': () => new Response(icon, { + headers: { + 'Content-Type': 'image/svg+xml', + 'Access-Control-Allow-Origin': 'http://localhost:8080', + 'Integrity': iconSRI, + }, + }), + '/commands.js': () => new Response(commands, { + headers: { + 'Content-Type': 'application/javascript', + 'Access-Control-Allow-Origin': 'http://localhost:8080', + 'Integrity': scriptSRI, + }, + }) } }; From 996c7c47cf9251055a2a35a0cf8a46e3ed6ffc29 Mon Sep 17 00:00:00 2001 From: Chris Zuber Date: Thu, 5 Mar 2026 14:09:29 -0800 Subject: [PATCH 2/2] Update home.js --- home.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/home.js b/home.js index aadb198..50ef464 100644 --- a/home.js +++ b/home.js @@ -13,12 +13,21 @@ import theme from '@aegisjsproject/styles/css/theme.css' with { type: 'css' }; import btn from '@aegisjsproject/styles/css/button.css' with { type: 'css' }; import misc from '@aegisjsproject/styles/css/misc.css' with { type: 'css' }; +trustedTypes.createPolicy('default', { + createHTML(input) { + const el = document.createElement('template'); // Ensures it is inert + el.setHTML(input); // Uses the Sanitizer API + return el.innerHTML; // Returns safe, sanitized HTML; + } +}); + document.adoptedStyleSheets = [properties, theme, btn, misc]; registerRootCommand('--menu', () => document.getElementById('menu').showPopover()); registerRootCommand('--help', () => document.getElementById('help').showPopover()); registerRootCommand('--log', (event, ...args) => console.log({ event, args })); observeCommands(); -initRootCommands();`; +initRootCommands(); +`; const style = `:root { color-scheme: dark; @@ -39,7 +48,7 @@ const headers = new Headers({ 'Referrer-Policy': 'no-referrer', // Cannot add `integrity` to an `import`ed module // 'Integrity-Policy': 'blocked-destinations=(script)', - 'Content-Security-Policy': `default-src 'none'; script-src 'self' '${scriptSRI}' '${POLYFILL_SRI}' '${importmapSRI}'; style-src ${importmap.resolve('@aegisjsproject/styles/css/')} '${styleSRI}'; font-src 'self'; base-uri 'self'; manifest-src 'self'; worker-src 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; child-src 'self'; connect-src 'self'; img-src 'self' blob:; media-src https://0eff4f4c-7f45-405c-8cf6-f7a3b3c1f07e.mdnplay.dev; sandbox allow-scripts allow-modals allow-popups allow-forms allow-downloads allow-top-navigation-by-user-activation allow-same-origin; trusted-types aegis-sanitizer#html; require-trusted-types-for 'script'; upgrade-insecure-requests;`, + 'Content-Security-Policy': `default-src 'none'; script-src 'self' '${scriptSRI}' '${POLYFILL_SRI}' '${importmapSRI}'; style-src ${importmap.resolve('@aegisjsproject/styles/css/')} '${styleSRI}'; font-src 'self'; base-uri 'self'; manifest-src 'self'; worker-src 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; child-src 'self'; connect-src 'self'; img-src 'self' blob:; media-src https://0eff4f4c-7f45-405c-8cf6-f7a3b3c1f07e.mdnplay.dev; sandbox allow-scripts allow-modals allow-popups allow-forms allow-downloads allow-top-navigation-by-user-activation allow-same-origin; trusted-types default aegis-sanitizer#html; require-trusted-types-for 'script'; upgrade-insecure-requests;`, 'Permissions-Policy': 'accelerometer=(), ambient-light-sensor=(), attribution-reporting=(), autoplay=(), battery=(), bluetooth=(), browsing-topics=(), camera=(self), compute-pressure=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(self), gamepad=(), geolocation=(self), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(self), magnetometer=(), microphone=(self), midi=(), otp-credentials=(), payment=(), picture-in-picture=(self), publickey-credentials-get=(self), screen-wake-lock=(self), serial=(), speaker-selection=(), storage-access=(), usb=(), web-share=(self), window-management=(), xr-spatial-tracking=()', 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Opener-Policy': 'same-origin',