Skip to content

bug(query-devtools): styleNonce prop has no effect because goober 2.1.17+ overwrites the nonce via window.__nonce__ #10820

@842u

Description

@842u

Describe the bug

Passing styleNonce to e.g. <ReactQueryDevtools> no longer prevents CSP violations. The <style id="_goober"> element injected by the devtools ends up with an empty nonce, causing the browser to reject it.

Inspecting the element in Chrome DevTools and checking its properties shows nonce="undefined".

Your minimal, reproducible example

https://stackblitz.com/edit/tanstack-query-bdtw2gqj?file=src%2Findex.tsx,package.json,vite.config.ts,README.md

Steps to reproduce

Version StackBlitz
Working open
Broken open
  1. Create a React app with Vite and install @tanstack/react-query and @tanstack/react-query-devtools at a version that includes goober 2.1.18 (e.g. 5.94.5).
  2. Add a Content-Security-Policy header with a style-src nonce-<your-nonce> directive in vite.config.ts.
  3. Pass the same nonce to <ReactQueryDevtools styleNonce="<your-nonce>" />.
  4. Start the dev server and open the browser console.
  5. Observe a CSP violation error for the <style id="_goober"> element.

Downgrading to @tanstack/react-query-devtools@5.91.3 (goober 2.1.16) resolves the error.

Expected behavior

Passing styleNonce to <ReactQueryDevtools> should be sufficient for the devtools to work under a strict style-src CSP policy. No CSP violation errors should appear in the browser console.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • OS: [Windows, Linux]
  • Browser: [Chrome, Firefox]

Tanstack Query adapter

react-query

TanStack Query version

v5.100.14

TypeScript version

5.8.3

Additional context

Root cause

The styleNonce prop flows into setupStyleSheet(60bd38ca6, PR #6320), which pre-creates a <style id="_goober" nonce="..."> element and appends it to the document head before the devtools render.

The devtools use goober for CSS-in-JS. Goober manages a single <style id="_goober"> element and, since v2.1.17 (2d9c3085), unconditionally overwrites its nonce on every access:

el.nonce = window.__nonce__ // added in goober 2.1.17 (#612)

A follow-up fix in v2.1.18 (44334ec0) corrected the insertion order but kept the window.__nonce__ assignment.

window.__nonce__ is goober's documented CSP hook. Since setupStyleSheet never set it, goober always overwrote the nonce with undefined, either clearing it on the pre-created element or inserting a brand-new element with an empty nonce, causing the CSP violation.

Why it worked before

Prior to goober 2.1.17, goober had no nonce handling at all. It would find the pre-created #_goober element and reuse it without touching the nonce, so setupStyleSheet's approach worked correctly.

The behavior changed silently when goober added window.__nonce__ support in v2.1.17 (2d9c3085).

The goober version in this repo's lockfile stayed pinned at 2.1.16 until 8f0d83438 (PR #9935), which bumped it to 2.1.18. All releases cut after that point ship with goober 2.1.18.

Version range

@tanstack/query-devtools goober in lockfile styleNonce works?
5.93.0 (a678f0957, #10069) last working 2.1.16 yes
5.94.4 (c613c2253, #10309) first broken 2.1.18 no

Fix

Set window.__nonce__ inside setupStyleSheet before the element is created or inserted:

export const setupStyleSheet = (nonce?: string, target?: ShadowRoot) => {
  if (!nonce) return
  ;(window as any).__nonce__ = nonce // goober reads this on every style element access

  // pre-creation kept as fallback for goober <2.1.17
  const styleExists =
    document.querySelector('#_goober') || target?.querySelector('#_goober')
  if (styleExists) return
  // ...
}

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions