Files
computer-playbook/roles/web-app-dashboard/files/playwright/test-csp-canonical-domain.js
kevinveenbirkenbachandClaude Opus 5.5 d721e887a5 fix(web-app-dashboard): run inline scripts by hash instead of 'unsafe-inline'
The dashboard sent script-src 'unsafe-inline' from two sources. Its own
meta/csp.yml granted it to script-src-attr, and build_csp_header adds it
to script-src-elem and script-src-attr for every app with
services.logout.enabled. Both reach script-src through the family union,
and the token in script-src-elem also closes the hash gate, so the
hashes that sys-front-inj-javascript, -dashboard, -logout and -matomo
register for their inline snippets were discarded.

build_csp_header now skips the logout grant on a directive whose
meta/csp.yml sets unsafe-inline: false. Only web-app-dashboard sets it:
on script-src and script-src-elem. Its injected snippets now run by hash
and the logout one-liner is hashed like the others. A render of
build_csp_header for every role, variant, logout on/off and tor on/off
(1712 headers) differs from origin/main only in the 8 web-app-dashboard
headers; the 86 other roles that declare a logout service render
byte-identical policies.

script-src-attr keeps 'unsafe-inline': port-ui 2.2.2 still renders
inline event handlers (menu onclick entries, openDynamicPopup,
closeAllModals, the card image onerror). It goes once the port-ui
release without inline handlers is pinned and header.yml.j2 moves from
onclick to action.

The dashboard Playwright spec asserts that script-src and script-src-elem
carry no 'unsafe-inline' on the landing page, next to its existing check
for CSP violations.

Refs #1122

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:27:49 +01:00

44 lines
1.6 KiB
JavaScript

const { test, expect } = require("./fixtures/onion-test");
const {
assertCspMetaParity,
assertCspResponseHeader,
decodeDotenvQuotedValue,
expectNoCspViolations,
installCspViolationObserver,
gotoOnion,
} = require("./personas");
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN);
exports.register = function (shared) {
test.beforeEach(async ({ page }) => {
await installCspViolationObserver(page);
});
test("dashboard enforces Content-Security-Policy and exposes canonical domain from applications lookup", async ({ page }) => {
const response = await gotoOnion(page,"/");
expect(response, "Expected dashboard landing response").toBeTruthy();
expect(response.status(), "Expected dashboard landing response to be successful").toBeLessThan(400);
const directives = assertCspResponseHeader(response, "dashboard landing");
await assertCspMetaParity(page, directives, "dashboard landing");
for (const directive of ["script-src", "script-src-elem"]) {
expect(
directives[directive],
`dashboard landing: ${directive} MUST NOT carry 'unsafe-inline', inline scripts run by hash`
).not.toContain("'unsafe-inline'");
}
const documentHtml = await response.text();
const documentUrl = response.url();
expect(
documentHtml.includes(canonicalDomain) || documentUrl.includes(canonicalDomain),
`Expected canonical domain "${canonicalDomain}" (from applications lookup) to appear in the dashboard document`
).toBe(true);
await shared.waitForDashboardReady(page);
await expectNoCspViolations(page, null, "dashboard landing");
});
};