Chromium never treats plaintext .onion origins as a secure context, so
keycloak-js (PKCE via crypto.subtle) could not run. A custom Playwright
browser fixture launches Firefox with the Tor-Browser prefs
(dom.securecontext.allowlist_onions, origin/mixed-content handling) for
onion targets and keeps Chromium for clearnet; all dashboard specs share
it via the register pattern.
keycloak-js is now vendored from the deployment's own CDN as an ES
module (jsdelivr is unreachable over Tor) with CORS opened on the CDN
proxy. Personas and the oidc-login spec wait for adapter readiness and
navbar settle before driving login/logout, which the async code->token
exchange needs over Tor latency.
Verified: full deploy matrix green (onion 14/14 twice, clearnet pass).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Browser vendors deprecated X-Frame-Options/X-XSS-Protection/X-Download-Options/
X-Permitted-Cross-Domain-Policies; CSP (rendered from each role's
meta/server.yml via build_csp_header) is the modern replacement. To make the
SPOT actually win against upstream apps (e.g. Keycloak/Quarkus still emits
X-Frame-Options), the proxy now hides those headers on every location type.
Includes a fail-loud lint that catches any future add_header / Header set/add
re-introduction in .conf / .conf.j2 / .htaccess / .j2 / .jinja / .tmpl files.
- sys-svc-proxy: new upstream_strips.conf.j2 partial bundles HSTS + the 4
deprecated header strips; included in html/media/ws/upload + web-svc-logout
+ web-app-oauth2-proxy locations (nginx array-replacement inheritance forces
per-location placement)
- web-app-keycloak: meta/server.yml frame-ancestors allows the primary-domain
wildcard with the dynamic TLS protocol so dashboard silent-check-sso iframe
loads cleanly
- web-app-dashboard/playwright: drop isOidcSilentCheckNoise — root cause is
now fixed, no noise filter needed
- web-app-dashboard/nginx + web-app-nextcloud/nginx: remove the deprecated
add_header lines themselves
- tests + docs: new lint registers `deprecated-http-header` suppression rule
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Console cleanliness moved to its own scenario
---------------------------------------------
``test-header-navbar-logos.js`` failed in CI when port-ui's Keycloak
silent-check-sso iframe was blocked by frame-ancestor CSP — the
``expectNoUnexpectedDiagnostics`` call at the test's end caught a
console.error logged by chrome for the blocked iframe (chrome-error://
chromewebdata + ``Framing 'https://auth.…'``) and failed every retry.
That diagnostic was a generic "no surprises" tail, unrelated to the
logo assertions the test is named after.
- Drop the diagnostic-tail call from the logo test; it now does
exactly what its name says.
- New ``test-no-console-noise.js`` runs the diagnostic check as its
own scenario, with two noise filters enabled
(``ignoreMatomoConsoleNoise`` + ``ignoreOidcSilentCheckNoise``).
- New ``isOidcSilentCheckNoise`` helper in ``_shared.js`` matches the
``chrome-error://chromewebdata`` URL plus the ``Framing '…'`` /
``silent-check-sso`` text patterns. These are test-env config
artefacts (Keycloak ``frame-ancestors`` not whitelisted), not
dashboard regressions.
- ``expectNoUnexpectedDiagnostics`` learns a corresponding
``ignoreOidcSilentCheckNoise`` option.
_shared.js no longer hides single-consumer code
-----------------------------------------------
Audited every export of ``_shared.js`` against every ``test-*.js``
consumer; ten symbols had exactly one caller and were therefore not
"shared". Moved each to the test file that owns it:
- ``runGuestFlow`` / ``runBiberFlow`` / ``runAdminFlow`` →
matching ``test-*-persona.js`` (now ``require("./personas")`` direct)
- ``assertCspMetaParity`` / ``assertCspResponseHeader`` /
``expectNoCspViolations`` / ``installCspViolationObserver`` plus
``canonicalDomain`` → ``test-csp-canonical-domain.js`` (CSP observer
install is now a local ``test.beforeEach`` in that file)
- ``dashboardTargetRoles`` parser → ``test-tile-reachability.js``
- ``cdnBaseUrl`` + each prefix (sharedCss / roleCss / sharedJs) →
``test-shared-css.js`` / ``test-cdn-role-stylesheet.js`` /
``test-logout-js-injection.js``
- ``platformLogoUrl`` + ``platformFaviconUrl`` →
``test-header-navbar-logos.js``
- ``appBaseUrl`` / ``oidcIssuerUrl`` / ``loginUsername`` /
``loginPassword`` / ``expectedOidcAuthUrl`` → ``test-oidc-login.js``
Dropped completely as dead test code: ``companyLogoUrl`` (no
spec ever read it) and its ``COMPANY_LOGO_URL`` env render in
``playwright.env.j2``. ``DASHBOARD_COMPANY_LOGO_URL`` in
``vars/main.yml`` stays — ``config.yaml.j2`` still consumes it for
the port-ui company.logo.source field.
``_shared.js`` is down to genuinely-shared state: ``dashboardJsBaseUrl``
+ ``matomoBaseUrl`` (each ≥ 2 callers), the diagnostic plumbing
(``attachDiagnostics`` / ``expectNoUnexpectedDiagnostics`` /
``waitForDashboardReady`` / ``waitForResourceResponse``), the noise
predicates, ``beforeEach``, and the ``skipUnlessServiceEnabled``
re-export. 116 LOC of state/exports collapse to 63.
Hitchhikers
-----------
- ``vars/main.yml`` loses an outdated docstring above the branding
SPOTs (the SPOTs themselves stay; ``config.yaml.j2`` consumes them).
- ``sys-svc-compose/defaults/main.yml``: linter re-joined the
multi-line ``compose_repository_pull`` default onto one line; pure
formatting, no semantic change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>