fix(uploads): allow WebAssembly hashing under CSP
This commit is contained in:
1 parent
17c85493b7
commit
17bb0e1846
3 files changed
+51
-2
No files matched your search
@@ -13,7 +13,7 @@
|
|||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"windows": [{ "label": "main", "title": "Pipu Music", "width": 1200, "height": 800, "minWidth": 720, "minHeight": 540 }],
|
"windows": [{ "label": "main", "title": "Pipu Music", "width": 1200, "height": 800, "minWidth": 720, "minHeight": 540 }],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http://127.0.0.1:*; font-src 'self' data:; media-src 'self' blob: http://127.0.0.1:*; connect-src 'self' ipc: http://ipc.localhost http://127.0.0.1:*; object-src 'none'; base-uri 'self'; frame-src 'none'"
|
"csp": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http://127.0.0.1:*; font-src 'self' data:; media-src 'self' blob: http://127.0.0.1:*; connect-src 'self' ipc: http://ipc.localhost http://127.0.0.1:*; object-src 'none'; base-uri 'self'; frame-src 'none'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
const nginxHeaders = readFileSync(new URL('../../../deploy/nginx/security-headers.conf', import.meta.url), 'utf8');
|
||||||
|
const desktopConfig = JSON.parse(readFileSync(new URL('../../desktop/src-tauri/tauri.conf.json', import.meta.url), 'utf8'));
|
||||||
|
const policies: Record<string, string> = {
|
||||||
|
web: nginxHeaders.match(/add_header Content-Security-Policy "([^"]+)" always;/)![1],
|
||||||
|
desktop: desktopConfig.app.security.csp,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [platform, policy] of Object.entries(policies)) {
|
||||||
|
test(`upload hash worker runs under ${platform} CSP`, async ({ page }) => {
|
||||||
|
// Apply the deployed policy to both the document and worker responses.
|
||||||
|
await page.route('**/*', async (route) => {
|
||||||
|
if (new URL(route.request().url()).pathname === '/csp-upload-test') {
|
||||||
|
await route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>Upload CSP</title>', headers: { 'Content-Security-Policy': policy } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (new URL(route.request().url()).pathname === '/csp-eval-test.js') {
|
||||||
|
await route.fulfill({ contentType: 'application/javascript', body: `
|
||||||
|
try { new Function('return 1')(); document.documentElement.dataset.evalBlocked = 'false'; }
|
||||||
|
catch { document.documentElement.dataset.evalBlocked = 'true'; }
|
||||||
|
` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await route.fetch();
|
||||||
|
await route.fulfill({ response, headers: { ...response.headers(), 'Content-Security-Policy': policy } });
|
||||||
|
});
|
||||||
|
await page.goto('/csp-upload-test');
|
||||||
|
const result = await page.evaluate(() => new Promise((resolve, reject) => {
|
||||||
|
const worker = new Worker('/src/features/uploads/hash.worker.ts', { type: 'module' });
|
||||||
|
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
|
||||||
|
worker.onmessage = ({ data }) => {
|
||||||
|
if (data.type === 'progress') return;
|
||||||
|
worker.terminate();
|
||||||
|
resolve(data);
|
||||||
|
};
|
||||||
|
worker.postMessage({ id: 'csp-test', file: new File(['abc'], 'track.flac') });
|
||||||
|
}));
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'csp-test', type: 'done',
|
||||||
|
sha256: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
|
||||||
|
});
|
||||||
|
// A page script observes CSP; Playwright's evaluate bypasses the eval restriction.
|
||||||
|
await page.addScriptTag({ url: '/csp-eval-test.js' });
|
||||||
|
await expect(page.locator('html')).toHaveAttribute('data-eval-blocked', 'true');
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
add_header X-Frame-Options "DENY" always;
|
add_header X-Frame-Options "DENY" always;
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
|
# hash-wasm считает SHA-256 загрузок в Web Worker; разрешаем WASM без JavaScript eval.
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
|
||||||
Reference in new issue
Block a user