feat: inline hashes in helmet CSP (#30529)
* feat: inline hashes in helmet CSP * tests * fix: svelte3 merge * chore: clean up --------- Co-authored-by: Daniel Dietzler <mail@ddietzler.dev> Co-authored-by: Jason Rasmussen <jason@rasm.me> (cherry picked from commit 02a5d4b2060dad6f5779c196b7bb2b43f871ffb2)
This commit is contained in:
@@ -114,6 +114,7 @@
|
||||
"factory.ts": "^1.4.1",
|
||||
"globals": "^17.0.0",
|
||||
"happy-dom": "^21.0.0",
|
||||
"parse5": "^8.0.1",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^4.0.0",
|
||||
|
||||
+43
-1
@@ -5,8 +5,49 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { svelteTesting } from '@testing-library/svelte/vite';
|
||||
import dotenv from 'dotenv';
|
||||
import { defaultTreeAdapter, parse, type DefaultTreeAdapterMap } from 'parse5';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { defineConfig, type ProxyOptions, type UserConfig } from 'vite';
|
||||
import { defineConfig, type Plugin, type ProxyOptions, type UserConfig } from 'vite';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const buildDir = 'build';
|
||||
const inlineTags = ['script', 'style'];
|
||||
|
||||
const cspManifest = (): Plugin => ({
|
||||
name: 'immich:csp-manifest',
|
||||
apply: 'build',
|
||||
buildApp: {
|
||||
order: 'post',
|
||||
async handler() {
|
||||
const indexHtml = path.join(buildDir, 'index.html');
|
||||
const hashes: Record<string, string[]> = Object.fromEntries(inlineTags.map((tag) => [`${tag}-src`, []]));
|
||||
|
||||
const walk = (node: DefaultTreeAdapterMap['node']) => {
|
||||
if (
|
||||
defaultTreeAdapter.isElementNode(node) &&
|
||||
inlineTags.includes(node.tagName) &&
|
||||
!node.attrs.some(({ name }) => name === 'src')
|
||||
) {
|
||||
const [child] = node.childNodes;
|
||||
const content = child && defaultTreeAdapter.isTextNode(child) ? child.value : '';
|
||||
hashes[`${node.tagName}-src`].push(`'sha256-${createHash('sha256').update(content).digest('base64')}'`);
|
||||
}
|
||||
|
||||
if ('childNodes' in node) {
|
||||
for (const child of node.childNodes) {
|
||||
walk(child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
walk(parse(readFileSync(indexHtml, 'utf8')));
|
||||
|
||||
writeFileSync(path.join(buildDir, '.csp.json'), JSON.stringify(hashes, undefined, 2) + '\n');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const upstream = {
|
||||
target: process.env.IMMICH_SERVER_URL || 'http://immich-server:2283/',
|
||||
@@ -71,6 +112,7 @@ export default defineConfig({
|
||||
'chromecast-caf-sender': './node_modules/@types/chromecast-caf-sender/index.d.ts',
|
||||
},
|
||||
}),
|
||||
cspManifest(),
|
||||
|
||||
process.env.BUILD_STATS === 'true'
|
||||
? visualizer({
|
||||
|
||||
Reference in New Issue
Block a user