Files
computer-playbook/roles/web-svc-mirror/files/playwright/playwright.spec.js
kevinveenbirkenbachandClaude Opus 4.8 bbb1220d7d feat(web-svc-mirror): first-party privacy proxy for third-party assets, coupled to tor
New web-svc-mirror role: an nginx vhost that proxies and caches exactly
the external asset hosts aggregated from all deployed applications' CSP
whitelists (external_asset_origins filter) under first-party paths
(/<origin-host>/...), refusing every other upstream. svc-net-tor
consumes the service, so any tor deployment automatically serves the
mirror on its onion.

For apps with services.tor.enabled the shared body filter rewrites
external asset URLs in HTML to the onion mirror (onion sessions only;
clearnet untouched) and the CSP builder allows the mirror origin in the
asset directives. The dashboard's keycloak-js vendoring is reverted:
onion sessions now load the adapter through the mirror, clearnet loads
jsdelivr directly as before.

Verified: variant=0 deploy green (failed=0), dashboard 14/14 over onion
with keycloak-js served through the mirror, mirror spec green, health
check accepts the 404 vhost root.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 10:56:13 +02:00

35 lines
1.5 KiB
JavaScript

const { test, expect } = require("@playwright/test");
const { decodeDotenvQuotedValue, expectHstsWhenTls, gotoOnion, normalizeBaseUrl } = require("./personas");
test.use({ ignoreHTTPSErrors: true });
const mirrorBaseUrl = normalizeBaseUrl(process.env.MIRROR_BASE_URL || "");
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN);
test.beforeEach(async ({ page }) => {
expect(mirrorBaseUrl, "MIRROR_BASE_URL must be set").toBeTruthy();
expect(canonicalDomain, "CANONICAL_DOMAIN must be set").toBeTruthy();
await page.context().clearCookies();
});
test("mirror is served under canonical domain and refuses unmapped paths", async ({ page }) => {
const response = await gotoOnion(page, `${mirrorBaseUrl}/`);
expect(response, "Expected mirror root response").toBeTruthy();
expect(response.status(), "Expected mirror root to refuse unmapped paths").toBe(404);
expect(
response.url().includes(canonicalDomain),
`Expected canonical domain "${canonicalDomain}" to back the mirror URL`
).toBe(true);
const headers = response.headers();
expectHstsWhenTls(headers, mirrorBaseUrl, "Mirror");
});
test("mirror refuses origins outside the CSP-derived allowlist", async ({ page }) => {
const response = await gotoOnion(page, `${mirrorBaseUrl}/evil.example/payload.js`);
expect(response, "Expected mirror allowlist response").toBeTruthy();
expect(
response.status(),
"Expected a non-whitelisted origin path to be refused"
).toBe(404);
});