Skip to content

Use 'self' instead of 'strict-dynamic' in script-src CSP (fixes blank UI in Chrome 158+) - #4126

Merged
tegan-temporal merged 1 commit into
temporalio:mainfrom
scoobyluu:fix/csp-strict-dynamic-chrome-158
Nov 6, 2026
Merged

tegan-temporal merged 1 commit into
temporalio:mainfrom
scoobyluu:fix/csp-strict-dynamic-chrome-158

Conversation

@scoobyluu

Copy link
Copy Markdown
Contributor

Description & motivation 💭

Fixes #4125. In Chrome 158+ the UI loads as a blank page: the CSP meta tag blocks the inline bootstrap's import() of /_app/immutable/entry/start.*.js and app.*.js.

-      directives: { 'script-src': ['strict-dynamic'] },
+      directives: { 'script-src': ['self'] },

Why: the UI is a static build, so mode: 'auto' authorizes the inline bootstrap with a hash, not a nonce. Hashes don't propagate to import(), so under 'strict-dynamic' the entry imports are parser-inserted requests with no nonce, and CSP3 blocks them. This only worked before because the entry chunks were already in the module map via <link rel="modulepreload">, and module-map hits weren't re-checked against CSP. Chrome 158 closed that as a CSP bypass in chromium/chromium@78b1ee2 (crbug 515948871). Chrome 158 reaches stable on 2026-11-17.

With 'self', the inline bootstrap is still hash-only, and the same-origin entry and chunk imports are allowed. Adding 'self' alongside 'strict-dynamic' would not work, because browsers ignore host sources when 'strict-dynamic' is present.

Design Considerations 🎨

Security trade-off (details in #4125):

  • Unchanged: inline script, event-handler attributes, and javascript: URLs are still blocked (hash-only).
  • Tighter: trusted scripts can no longer load script from arbitrary origins.
  • Looser: an injected <script src="/…"> pointing at the same origin is allowed. The same-origin surface is only build assets. /api/v1, /render and /auth can't execute as script because middleware.Secure() sets nosniff.

A stronger long-term option is a per-request nonce injected by ui-server's index handler, keeping 'strict-dynamic'. That's a larger change, so this PR is the immediate fix.

Testing 🧪

How was this tested 👻

  • Manual testing
  • E2E tests added
  • Unit tests added

Chrome 158.0.8127.19 (macOS arm64, headless), vite build output served statically:

build/index.html CSP Result
script-src 'strict-dynamic' 'sha256-…' (current) blank page, 2 CSP violations, Failed to fetch dynamically imported module
script-src 'self' 'sha256-…' (this PR) app boots and the router renders, 0 CSP violations

A minimal repro (hashed inline import() ± modulepreload) is in #4125.

Not yet tested against a running Temporal backend.

Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️

  1. pnpm build:local, then serve build/ (e.g. python3 -m http.server --directory build).
  2. Open it in Chrome 158+ and check the console: no CSP violations, and the app renders.
  3. On main, the same steps give a blank page.

Checklists

Issue(s) closed

Fixes #4125

Docs

Any docs updates needed?

No.

@scoobyluu
scoobyluu requested a review from a team as a code owner November 5, 2026 15:04
@vercel

vercel Bot commented Nov 5, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Temporal Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Nov 5, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

The UI is a static build, so SvelteKit's `mode: 'auto'` CSP authorizes
the inline bootstrap with a hash rather than a nonce. Hashes don't
propagate to the bootstrap's `import()` of the entry chunks, so under
`'strict-dynamic'` those imports are parser-inserted requests with no
nonce and are blocked per CSP3.

This previously worked only because the entry chunks were already in
the module map via <link rel="modulepreload">, and module-map hits
weren't re-checked against CSP. Chrome 158 closed that bypass
(chromium 78b1ee2b21, crbug.com/515948871), so the UI now loads as a
blank page in Chrome 158+.

Switching to `'self'` keeps the inline bootstrap hash-only and allows
the same-origin `/_app/immutable/*` imports.

Fixes temporalio#4125
@vercel

vercel Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
holocene Ready Ready Preview Nov 6, 2026 6:55pm UTC

Request Review

@tegan-temporal
tegan-temporal merged commit 9f017f0 into temporalio:main Nov 6, 2026
18 checks passed
tegan-temporal added a commit that referenced this pull request Nov 6, 2026
Auto-generated version bump from 2.58.0 to 2.58.1

Bump type: patch

Changes included:
- [`ae4835ed`](ae4835e) Save at most two scaling groups when editing a Version (#4086)
- [`d1b26617`](d1b2661) Fix delayed badges for time-skipped workflows (#4110)
- [`31b4ed29`](31b4ed2) update Dockerfiles (#4114)
- [`ebf166ed`](ebf166e) Move search into table container (#4103)
- [`5696caf3`](5696caf) Raise content-tertiary contrast to meet WCAG AA (#4113)
- [`120ade71`](120ade7) Implement IO TextLink (#4115)
- [`fb4cc801`](fb4cc80) Add breadcrumbs component with navigation styling (#4116)
- [`8c7378a7`](8c7378a) Match saved scaling groups to regions when editing a Version (#4118)
- [`17729104`](1772910) Fix focus on Input clear button (#4121)
- [`d1950572`](d195057) Allow Serverless Workers in only the replica region (#4119)
- [`c2b35f9d`](c2b35f9) Add filtering to table header columns (#4106)
- [`191a9447`](191a944) Limit status filter options to the statuses each table supports (#4135)
- [`9f017f05`](9f017f0) Use 'self' instead of 'strict-dynamic' in script-src CSP (#4126)

Co-authored-by: tegan-temporal <277832131+tegan-temporal@users.noreply.github.com>
vishal-seshagiri-infinitusai added a commit to vishal-seshagiri-infinitusai/helm-charts that referenced this pull request Nov 6, 2026
Chrome 158 blocks the way UI versions before 2.58.1 load their entry
scripts under their CSP, so the UI renders a blank page
(temporalio/ui#4125). The fix (temporalio/ui#4126) shipped in UI v2.58.1.

This branch was successfully deployed

1 active deployment
Preview — 42054a0f Deployed Nov 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI loads blank in Chrome 158+: script-src 'strict-dynamic' blocks entry import() (hash-mode CSP)

4 participants