Skip to content

fix(media): add crossOrigin="anonymous" to Image component to fix Firefox OpaqueResponseBlocking - #1108

Merged
stipsan merged 6 commits into
mainfrom
cursor/port-media-crossorigin-firefox-967b
Aug 3, 2026
Merged

fix(media): add crossOrigin="anonymous" to Image component to fix Firefox OpaqueResponseBlocking#1108
stipsan merged 6 commits into
mainfrom
cursor/port-media-crossorigin-firefox-967b

Conversation

@stipsan

@stipsan stipsan commented Jun 17, 2026

Copy link
Copy Markdown
Member

Ports sanity-io/sanity-plugin-media#291 by @oxygensmith into the monorepo.

Problem

In Firefox, all thumbnail images in the media browser fail to load with OpaqueResponseBlocking errors (NS_BINDING_ABORTED in the network panel). This does not affect Chrome or Safari.

Root cause: Sanity's CDN returns Vary: Origin on all asset responses. Firefox's stricter Opaque Response Blocking (ORB) implementation blocks opaque (no-cors) cross-origin responses when the server declares Vary: Origin. The Image component renders a plain <img> without a crossOrigin attribute, so the browser makes a no-cors request and receives an opaque response — which Firefox blocks.

Fix

Adding .attrs({crossOrigin: 'anonymous' as const}) to the Image styled component causes the browser to include an Origin header with every image request. Sanity's CDN already responds with Access-Control-Allow-Origin when an Origin is present, so Firefox accepts the response. This affects every place in the plugin that renders image thumbnails (card view, table view, edit dialog).

Port notes

  • The commit is authored as the original author Rob Butz (@oxygensmith) and the changeset credits @oxygensmith so the release notes thank them.
  • The upstream PR's one-line diff accidentally dropped the styled-component's template-literal backtick (leaving an unbalanced template literal that would not compile). This port applies the author's clear intent — adding .attrs({crossOrigin: 'anonymous' as const}) — while keeping the template literal valid so it builds and passes this monorepo's strict CI.
  • Also includes a small AGENTS.md note (separate commit): the cloud VM's default Node (v22.14.0) is below tsdown's ^22.18.0 floor, which breaks the full pnpm build / pnpm test run at @repo/generators until a newer Node is put on PATH.

Testing

Verified in the test studio (Kitchen Sink workspace → Media tool) that every rendered thumbnail <img> carries crossorigin="anonymous" and loads correctly. A console snippet confirmed 100% coverage (total: 20, withCrossOriginAnonymous: 20, sampleCrossOrigin: "anonymous").

media_thumbnails_crossorigin_anonymous_demo.mp4

Media browser thumbnails loaded
Elements panel showing crossorigin attribute
Console proof: all CDN images have crossOrigin anonymous

Programmatic checks (run with Node v22.22.2):

  • pnpm format — no changes
  • pnpm lint — passes
  • pnpm build — 40/40 tasks pass
  • pnpm test run — 852/852 tests pass (149 media tests included)

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

…efox OpaqueResponseBlocking

In Firefox, all thumbnail images in the media browser fail to load with OpaqueResponseBlocking errors (NS_BINDING_ABORTED). Sanity's CDN returns Vary: Origin on asset responses; Firefox's stricter ORB blocks opaque cross-origin responses in that case. Adding crossOrigin='anonymous' makes the browser send an Origin header, and the CDN responds with Access-Control-Allow-Origin so Firefox accepts the response.

Ports sanity-io/sanity-plugin-media#291
@stipsan stipsan added the 🤖 bot label Jun 17, 2026 — with Cursor
@changeset-bot

changeset-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4b414a0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
sanity-plugin-media Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
plugins-studio Ready Ready Preview Aug 3, 2026 11:21am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plugins-e2e-test-studio Ignored Ignored Aug 3, 2026 11:21am

Request Review

The cloud VM's default node (v22.14.0) is below tsdown's ^22.18.0 floor, so 'pnpm build' and 'pnpm test run' fail at @repo/generators with a 'Failed to import module "unrun"' error. Document the cause and how to put a new-enough nvm Node on PATH.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a Firefox-specific thumbnail loading failure in sanity-plugin-media by ensuring image requests are made as CORS requests (avoiding Firefox ORB blocking on Sanity CDN responses that use Vary: Origin).

Changes:

  • Add crossOrigin="anonymous" to the shared styled <img> thumbnail component used across the media UI.
  • Add an agent-facing note explaining a Node.js version pitfall affecting full monorepo builds/tests in the cloud VM environment.
  • Add a patch changeset crediting the original author.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
plugins/sanity-plugin-media/src/components/Image/index.tsx Sets crossOrigin: 'anonymous' on the styled image component to fix Firefox ORB thumbnail loading failures.
AGENTS.md Documents the Node/tsdown version constraint and a workaround for cloud VMs with older node on PATH.
.changeset/media-firefox-crossorigin.md Adds a patch changeset for sanity-plugin-media with author credit and release note text.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .changeset/media-firefox-crossorigin.md Outdated
juice49
juice49 previously approved these changes Jun 19, 2026
…nvention

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 30, 2026 13:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Resolve AGENTS.md Node version notes conflict by keeping main's more complete guidance (covers build, format, and lint needing Node >= 22.18).

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 30, 2026 13:31
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

✅ E2E Tests

🟢 24 passedview full reportview run

Studio: https://plugins-e2e-test-studio-itlvfoixi.sanity.dev

Datasets: pr-1108-chromium-30809124030, pr-1108-firefox-30809124030

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 30.66% 4987 / 16264
🔵 Statements 30.37% 5244 / 17266
🔵 Functions 26.38% 1274 / 4828
🔵 Branches 21.74% 2485 / 11426
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
plugins/sanity-plugin-media/src/components/Image/index.tsx 33.33% 0% 0% 33.33% 17-26
Generated in workflow #8447 for commit 4b414a0 by the Vitest Coverage Report Action

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@stipsan
stipsan merged commit 1dc3a7b into main Aug 3, 2026
26 checks passed
@stipsan
stipsan deleted the cursor/port-media-crossorigin-firefox-967b branch August 3, 2026 11:47
@squiggler-app squiggler-app Bot mentioned this pull request Aug 3, 2026
stipsan pushed a commit that referenced this pull request Aug 3, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @sanity/sanity-plugin-async-list@3.0.0

### Major Changes

- [#1087](#1087)
[`f4e7fcc`](f4e7fcc)
Thanks [@stipsan](https://github.com/stipsan)! - Refactor the input into
a real React component and fix the `async-list-undefined` namespace/id

- **Breaking:** `AsyncList` is now a regular React component that takes
a single `props` argument (the standard Sanity input props plus an
`options` field), instead of being called as `AsyncList(props,
options)`. For the `components.input` slot, use the new
`createAsyncListInput(options)` factory: `input:
createAsyncListInput({loader})`. This makes the input safe under the
Rules of Hooks and lets the React Compiler optimize it.
- **Breaking:** the secrets namespace and DOM `id` are no longer derived
as `async-list-${schemaType}` (which became the literal
`async-list-undefined` for component usage). The DOM `id` now uses
Sanity's stable per-field id, and the secrets namespace falls back to
`async-list` (instead of `async-list-undefined`) when no
`schemaType`/`secrets.namespace` is available. When using the component
with `secrets`, set an explicit `secrets.namespace`; a dev warning is
logged if it is missing.
- Fix: the debounced search handler is stable and is cancelled on
unmount, so it no longer drops queued calls or updates state on an
unmounted tree.
- Fix: the value-change handler no longer depends on the whole `props`
object, avoiding unnecessary `Autocomplete` re-renders.
- Fix: loader results are validated to ensure each option's `value` is a
string.

### Patch Changes

- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## @sanity/personalization-plugin@3.1.0

### Minor Changes

- [#1226](#1226)
[`1143bd6`](1143bd6)
Thanks [@jjburbridge](https://github.com/jjburbridge)! - Add
`fieldLevelPersonalization` export for segment-based field
personalization. Segments can be defined statically, fetched from an
external API, or stored in your Sanity dataset. `FieldPluginConfig` is
renamed to `ExperimentFieldPluginConfig` (a deprecated type alias is
kept for compatibility).

### Patch Changes

- [#1208](#1208)
[`51317b2`](51317b2)
Thanks [@stipsan](https://github.com/stipsan)! - Address review feedback
from the migration into the monorepo:

- Fix LaunchDarkly experiment pagination so `offset` advances between
pages, preventing duplicate results and a possible infinite loop
- Correct misspelled plugin names (`personalistaion` →
`personalization`) and stop the LaunchDarkly entry point from
identifying itself as the GrowthBook plugin
- Fix an operator-precedence bug in the experiment field preview that
could set the title to the entire field object
- Walk reference preview paths with optional chaining to avoid throwing
on missing intermediate fields
- Move the experiment item activation patch out of render and into an
effect
  - Remove unused `Select` props
- Fix the GrowthBook (`project` config key) and LaunchDarkly
(`fieldLevelExperiments` usage) docs and drop the stale standalone-repo
Studio version / tooling references

- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## sanity-plugin-cloudinary@2.1.0

### Minor Changes

- [#1233](#1233)
[`36f3fe2`](36f3fe2)
Thanks [@ChrisLaRocque](https://github.com/ChrisLaRocque)! - Add
performant preview images

Asset previews and diffs now build an optimized, scaled-down preview URL
with `@cloudinary/url-gen` (a 400px-wide transformation) when a cloud
name is configured, instead of loading the full-size original. This
keeps the Studio fast when previewing large Cloudinary assets, and falls
back to the stored asset URL when no cloud name is available.

- [#1227](#1227)
[`3b02635`](3b02635)
Thanks [@pgurley](https://github.com/pgurley),
[@atlvis](https://github.com/atlvis)! - Add `cloudinaryReferencePlugin`
for storing Cloudinary assets as reusable document references

- New `cloudinaryReferencePlugin` registers the schema types needed to
reference Cloudinary assets as documents
- New `cloudinaryAssetDocument` type stores a Cloudinary asset as a
standalone document
- New `cloudinaryAssetReference` type references those asset documents,
with a custom input for selecting and managing assets through the
Cloudinary Media Library
- `openMediaSelector` now supports a `showHandler` callback and a
`folder` option, so the select button can show a loading state and scope
the library to a folder
- Fixed the internal name of `cloudinaryAssetSourcePlugin`
(`cloudinart-asset-source` → `cloudinary-asset-source`)

### Patch Changes

- [#1236](#1236)
[`7ec89b6`](7ec89b6)
Thanks [@stipsan](https://github.com/stipsan)! - - Fix the
`cloudinaryAssetSourcePlugin` name, which was mistakenly registered as
`cloudinart-asset-source`
- Wait for the Cloudinary Media Library script to finish loading before
opening it, avoiding runtime errors when several inputs mount at once
- Fix a user-facing typo in the asset source loading message ("Media
Libary" → "Media Library")
- Remove an invalid `src`-less `<track>` element from the video preview
- Correct the README usage examples (`defineConfg` → `defineConfig`) and
drop stale standalone-repo "Develop & test" / "Release new version"
instructions
- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## sanity-plugin-media@6.1.0

### Minor Changes

- [#1119](#1119)
[`ab39674`](ab39674)
Thanks [@lud-hu](https://github.com/lud-hu)! - Add an "Edit Media" asset
source so media details (alt text, title, tags, etc.) can be edited
directly from an image or file field on a document, without opening the
full Media tool.

- [#1117](#1117)
[`6f4316d`](6f4316d)
Thanks [@nickeforsberg](https://github.com/nickeforsberg)! - Add an
`excludeTags` option that hides assets referencing the listed
`media.tag` slugs (`name.current` values). Excluded assets are omitted
from the Media browser grid and asset-picker queries, and the matching
tags are hidden from the tag sidebar and tag search facet. The asset
edit dialog still lists all tags so you can assign or remove them on an
open asset.

- [#1121](#1121)
[`30fa7e2`](30fa7e2)
Thanks [@bobbygeo](https://github.com/bobbygeo),
[@pedrobonamin](https://github.com/pedrobonamin)! - Add folder
management to the media browser

- Organise assets into nested folders, backed by a new `media.folder`
document type (a `name` plus a weak `parent` reference) and a single
weak `opt.media.folder` reference on each asset (mirroring how tags
work). Renaming a folder is a one-field document write regardless of how
many assets it contains.
- Browse folders in a dedicated sidebar tree, with breadcrumb
navigation, create / rename / delete flows, a bulk "Move to folder"
dialog, and per-asset folder controls in the asset details dialog. The
default "All assets" view lists every asset; opening a folder filters
the list to that folder.
- Deleting a folder removes only the folder document — its assets stay
in the library with their folder assignment cleared, and nested folders
move up one level.
- Support multi-select insert when the media plugin is used as an asset
source for array (multiple) fields.

- [#1115](#1115)
[`e7ec6e6`](e7ec6e6)
Thanks [@am0wa](https://github.com/am0wa)! - Extract `image`
(`sanity.imageExifTags`) metadata on asset upload and include it in
asset queries. The asset edit dialog now pre-fills the Description field
from the image's `ImageDescription` EXIF tag as a fallback for image
assets.

- [#1118](#1118)
[`186e29c`](186e29c)
Thanks [@Polleke007](https://github.com/Polleke007),
[@joepvandepol](https://github.com/joepvandepol)! - Add the ability to
replace a selected asset with another one and update all of its
references across documents

When a single asset is selected, a new **Replace** action opens an
overview where you can pick a replacement asset. Every document that
references the original asset (including deeply nested image fields) is
re-pointed to the chosen asset. Ported from
sanity-io/sanity-plugin-media#236.

### Patch Changes

- [#1108](#1108)
[`1dc3a7b`](1dc3a7b)
Thanks [@oxygensmith](https://github.com/oxygensmith)! - Add
`crossOrigin="anonymous"` to the image thumbnail component so thumbnails
load in Firefox. Firefox's Opaque Response Blocking would otherwise
block Sanity CDN asset responses (which send `Vary: Origin`) when
requested without an `Origin` header.
## @sanity/plugin-kit@10.0.3

### Patch Changes

- [#1698](#1698)
[`d3dedd3`](d3dedd3)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency oxlint to ^1.76.0

- [#1779](#1779)
[`5555795`](5555795)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency oxfmt to ^0.61.0

- [#1799](#1799)
[`93858fc`](93858fc)
Thanks [@stipsan](https://github.com/stipsan)! - Prefer function
components via `react/prefer-function-component` (with
`allowErrorBoundary`) instead of banning `Component`/`PureComponent`
imports
## @sanity/cross-dataset-duplicator@2.0.13

### Patch Changes

- [#1228](#1228)
[`71d77d5`](71d77d5)
Thanks [@rohanvachheta](https://github.com/rohanvachheta)! - Handle
reference errors during duplication by fetching missing referenced
documents (including transitive refs, respecting `filter`), re-uploading
any recovered assets, and retrying the transaction, with a one-by-one
commit fallback

- Updated dependencies
[[`c61bb44`](c61bb44)]:
  - @sanity/studio-secrets@4.0.15
## @sanity/embeddings-index-ui@4.0.13

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## @sanity/form-toolkit@3.0.13

### Patch Changes

- [#1234](#1234)
[`1dd36c8`](1dd36c8)
Thanks [@stipsan](https://github.com/stipsan)! - Address post-migration
review feedback:

- `FormRenderer` now renders interactive (uncontrolled) inputs when no
`getFieldState` is provided, so the documented native HTML form usage
works out of the box
- Uncontrolled text and textarea fields honor `options.defaultValue`
(controlled fields keep form-library state only, so UI and submit values
stay in sync)
- `FormRenderer` falls back to `field.name` for the React key when a
field has no `_key`
- The shared HubSpot/Mailchimp request handler defaults to the
Next.js-compatible handler instead of throwing when no framework
environment variable is detected, and short-circuits CORS preflight
(`OPTIONS`) requests
- Corrected the `formSchema`, `formiumInput`, and `mailchimpInput` usage
examples (import paths and required options), README typos, and
example/dev-workflow references

- Updated dependencies
[[`f4e7fcc`](f4e7fcc)]:
  - @sanity/sanity-plugin-async-list@3.0.0
## @sanity/language-filter@5.0.14

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## @sanity/studio-secrets@4.0.15

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## sanity-plugin-hotspot-array@5.0.12

### Patch Changes

- [#1789](#1789)
[`9628df4`](9628df4)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency motion to ^12.43.0
## sanity-plugin-iframe-pane@5.0.30

### Patch Changes

- [#1789](#1789)
[`9628df4`](9628df4)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency motion to ^12.43.0
## sanity-plugin-internationalized-array@5.1.25

### Patch Changes

- [#1765](#1765)
[`97937c4`](97937c4)
Thanks [@pedrobonamin](https://github.com/pedrobonamin)! - Add a stable
`data-testid` on the document-level add-translations panel for e2e
coverage
## sanity-plugin-mux-input@5.0.8

### Patch Changes

- [#1792](#1792)
[`c61bb44`](c61bb44)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency react-rx to ^4.2.5
## sanity-plugin-studio-smartling@5.0.14

### Patch Changes

- [#1216](#1216)
[`d4dd45b`](d4dd45b)
Thanks [@stipsan](https://github.com/stipsan)! - Harden the Smartling
adapter and fix docs:

- Send the secret credentials JSON verbatim during authentication
instead of double-encoding it, which could break auth with proxies that
forward the request body as-is
- Throw a clear error (surfacing Smartling's message) when
authentication does not return an access token, instead of a cryptic
`TypeError`
- Avoid throwing when no existing job is found or when a
translation/progress response is missing its expected payload
- Guard the progress calculation against a zero total word count so
empty documents no longer report `NaN`/`Infinity`
- Fix the `additionalDeserializers` option name in the advanced
configuration docs (was `additonalDeserializers`) and update the README
development/release instructions for the monorepo
## sanity-plugin-workflow@3.0.39

### Patch Changes

- [#1789](#1789)
[`9628df4`](9628df4)
Thanks [@squiggler-app](https://github.com/apps/squiggler-app)! -
fix(deps): update dependency motion to ^12.43.0
## @sanity/document-internationalization@6.2.28


## @sanity/sfcc@1.0.25

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/bugbot) is generating a
summary for commit 9246b02. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants