fix mask editor on cloud by allowing crossorigin on image data (#5957)

Fixed cross-origin canvas taint
[error](https://comfy-org.sentry.io/issues/6927234287/?referrer=slack&notification_uuid=e2ac931f-c955-43a2-a345-76fa8b164504&alert_rule_id=16146009&alert_type=issue)
in mask editor by adding CORS support to image loading.

When images from different origins are drawn to canvas without CORS
headers, browsers "taint" the canvas to prevent data extraction attacks.
This breaks `getImageData()` calls with a SecurityError. The [W3C
standard
solution](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin)
is `crossOrigin = 'anonymous'`.

Intended flow:

1. Frontend sets img.crossOrigin = 'anonymous'
2. Browser sends CORS preflight to GCS: "Can cloud.comfy.org access this
image?"
3. GCS must respond: "Yes, that origin is allowed"
4. Browser loads image with CORS headers enabled
5. Canvas operations work without taint

Canvas security model compliance and compatibility with cloud deployment
image redirects to GCS.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5957-fix-mask-editor-on-cloud-by-allowing-crossorigin-on-image-data-2856d73d3650819a84b2fed19d85d815)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2025-10-07 18:04:38 -07:00
committed by Richard Yu
parent 388fdcbfde
commit d1577bf18f

View File

@@ -1223,6 +1223,7 @@ class MaskEditorDialog extends ComfyDialog {
// Create and set new image
const newImage = new Image()
newImage.src = mkFileUrl({ ref: actualFilepath, preview: true })
newImage.crossOrigin = 'anonymous'
ComfyApp.clipspace.imgs[paintedIndex] = newImage
// Update images array - create if it doesn't exist
@@ -1275,6 +1276,7 @@ class MaskEditorDialog extends ComfyDialog {
// Create and set new image
const newImage = new Image()
newImage.src = mkFileUrl({ ref: actualFilepath, preview: true })
newImage.crossOrigin = 'anonymous'
ComfyApp.clipspace.imgs[indexToSaveTo] = newImage
// Update images array - create if it doesn't exist
@@ -4291,6 +4293,7 @@ class UIManager {
this.image = await new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => resolve(img)
img.onerror = reject
img.src = rgb_url.toString()
@@ -4302,6 +4305,7 @@ class UIManager {
this.paint_image = await new Promise<HTMLImageElement>(
(resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => resolve(img)
img.onerror = reject
img.src = paintURL.toString()
@@ -4437,6 +4441,7 @@ class UIManager {
private loadImage(imagePath: URL): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const image = new Image() as HTMLImageElement
image.crossOrigin = 'anonymous'
image.onload = function () {
resolve(image)
}