Skip to content

Add subresource integrity to the CDN assets in the GraphiQL and error pages - #8395

Open
ofri-peretz wants to merge 2 commits into
Shopify:mainfrom
ofri-peretz:fix/cdn-subresource-integrity
Open

Add subresource integrity to the CDN assets in the GraphiQL and error pages#8395
ofri-peretz wants to merge 2 commits into
Shopify:mainfrom
ofri-peretz:fix/cdn-subresource-integrity

Conversation

@ofri-peretz

Copy link
Copy Markdown

What

Three assets loaded from a public CDN carry no integrity attribute, and two more carry a hash pinned against a floating version range.

File Asset Today
cli-kit/.../templates/graphiql.tsx @shopify/polaris@12.10.0/build/esm/styles.css no integrity
cli-kit/.../templates/graphiql.tsx graphiql@3.0.4/graphiql.min.css no integrity
cli-kit/.../templates/graphiql.tsx graphiql@3.0.4/graphiql.min.js no integrity — 3 MB of executed script
cli-kit/.../templates/unauthorized.tsx @shopify/polaris@12.10.0/build/esm/styles.css no integrity
theme/.../hot-reload/error-page.ts @shopify/polaris@13.9.2 (unpkg) no integrity
cli-kit/.../templates/graphiql.tsx react@17, react-dom@17 hash pinned, URL floating

Without integrity, whatever the CDN returns is what the page runs. graphiql.min.js executes in a page that is already authenticated against the developer's store.

The React case is a different bug in the same area. The URLs float on @17 while the integrity hashes are fixed, so the two disagree by construction: the day jsdelivr resolves 17.0.3, the hash stops matching and the script silently fails to load. The hashes in the file today are exactly 17.0.2:

$ curl -sL https://cdn.jsdelivr.net/npm/react@17.0.2/umd/react.development.js | openssl dgst -sha512 -binary | openssl base64 -A
Vf2xGDzpqUOEIKO+X2rgTLWPY+65++WPwCHkX2nFMu9IcstumPsf/uKKRd5prX3wOu8Q0GBylRpsDB26R6ExOg==

which matches the file. So pinning the URLs to 17.0.2 changes nothing today and removes the failure mode.

How the hashes were produced

Each one is sha384 over exactly the bytes at the URL in the diff:

curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A

crossorigin="anonymous" is required for SRI to be enforced on a cross-origin fetch. Both CDNs return access-control-allow-origin: * on all five URLs, so the assets keep loading — I checked each one rather than assuming.

Test plan

  • Recomputed every hash from the exact URL in the diff (commands above), including confirming the two existing React sha512 hashes match 17.0.2.
  • Verified access-control-allow-origin: * on all five CDN URLs, so crossorigin="anonymous" does not block them.
  • Checked for snapshots covering these templates — the GraphiQL templates have none, and error-page.js is vi.mocked in html.test.ts, so no fixture needed updating.
  • I did not run the full suite locally; happy to if you'd like, but the change is HTML attributes in three templates.

A follow-up I deliberately left out

I found this with an ESLint rule (require-dependency-integrity in eslint-plugin-node-security) and the natural companion to this PR is that rule, scoped to these three files, so the next CDN asset added without a hash fails lint rather than shipping.

I did not add it here. Your .github/dependabot.yml sets cooldown: default-days: 7, and the current release is about a day old — proposing it now would be asking you to make an exception to your own policy in the same PR. I'm glad to open that as a separate PR once it has aged past your window, if it's of interest.

🤖 Generated with Claude Code

The GraphiQL page loads five assets from jsdelivr and the theme error page
one from unpkg. Three of them carry no integrity attribute, including
graphiql.min.js, three megabytes of script the page executes.

react and react-dom already carry hashes but their URLs float on @17, so the
hash and the URL disagree: the day jsdelivr resolves 17.0.3 the hash stops
matching and the script silently does not load. The pinned hashes match
17.0.2 exactly, so pinning the URLs to 17.0.2 changes nothing today and
removes that failure mode.
@ofri-peretz
ofri-peretz requested review from a team as code owners August 25, 2026 01:57
Adds eslint-plugin-node-security and enables require-dependency-integrity on
the three templates this PR fixes. Without a rule the next CDN asset added
without a hash ships unguarded, which is how the five in this diff got there.

Scoped to those files: they are clean under the rule after this change and
were not before, so it turns CI red only on a new one.

Pinned to 4.13.1, published nine days ago, so it clears the
cooldown: default-days: 7 in .github/dependabot.yml rather than asking for
an exception. 5.x crosses that window in a couple of days and is a one-line
bump whenever you want it.
@ofri-peretz

Copy link
Copy Markdown
Author

Added the regression guard I'd left out of the first push.

eslint-plugin-node-security@4.13.1 as a devDependency, with require-dependency-integrity enabled on the three templates this PR fixes. Without a rule, the next CDN asset added without a hash ships unguarded — which is how the five in this diff got there.

I said in the description that your cooldown made this a separate PR. That was me checking the wrong number: I looked at the age of our latest release rather than asking which release already satisfies your policy.

  • .github/dependabot.yml sets cooldown: default-days: 7
  • eslint-plugin-node-security@4.13.1 was published nine days ago

So the pin clears your window as it stands. No exception requested, and no version newer than your own policy allows. 5.x crosses the same line in a couple of days if you'd rather have it — a one-line bump.

Two things checked before proposing it:

  • The scope is clean after this change and was not before. Running the rule over those three files reports zero errors now; before the SRI additions it reported five. It turns CI red only on a newly added asset.
  • Exact pin, matching how the rest of devDependencies is written (eslint-plugin-jsdoc: 50.8.0, @nx/eslint-plugin: 22.7.7).

pnpm install --lockfile-only for the lockfile, so pnpm-lock.yaml moves and nothing else does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant