Back to Home

Image Metadata Cleaner — Brave Browser Incompatibility

Technical Summary and Decision Document

Date: 2026-06-27
Affected application: Image Metadata Cleaner (secureacademic.com)
Affected browser: Brave (Android, all tested versions)
Status: Unresolved — cannot be fixed in software

1. The Symptom

On the Brave browser, on Android devices, the application fails consistently and reproducibly:

2. The Debugging Process

2.1 Initial Hypotheses and Eliminations

Cloud file (iCloud / Google Photos): The first suspicion was that the images had not been downloaded to the phone, and that file reading failed due to expiration of the cloud URI. This was partially true in an earlier version (we incorrectly flagged an InvalidStateError as a cloud-related error), but the issue remained reproducible even with fully local images stored in Samsung Gallery.

Stale File object (Android Content Provider URI expiration): We examined the possibility that Android content:// URIs expire after the first read, causing the stream behind the File object to be closed by the second attempt. We introduced a materializeFile() function that reads the file into memory via arrayBuffer() immediately after the first successful decode, and from then on we work only with this memory-based File object, independent of OS handles. This mitigated the problem — the error moved from the second attempt to the fourth or fifth — but did not eliminate it.

Brave Shields (Fingerprint Protection): Disabling Shields produced no change. This indicated that the error was not tied to the Shields user setting, but to a deeper layer.

2.2 Identifying the Actual Cause

We found Brave's own GitHub issue (#9186), which clearly lists the canvas API endpoints to which Brave applies farbling (noise-based fingerprint protection):

This issue, along with Brave's official "Fingerprinting defenses 2.0" documentation, is a reliable, verified source: the underlying farbling mechanism — deterministic, per-session, per-eTLD+1-seed noise added at the four endpoints above — is an officially documented Brave feature.

3. The Observed Phenomenon and the Hypothesized Cause

3.1 What Is Farbling? (documented fact)

Brave uses a technique called "farbling" as protection against browser fingerprinting. This means that certain outputs of the canvas API are modified with small, random noise — small enough to remain invisible to the human eye, but enough to prevent fingerprinting algorithms from getting a stable, reproducible value.

Farbling is deterministic on a per-session, per-eTLD+1 seed basis: the same site always gets the same modified value within the same session — but a different value in the next session. This means a fingerprinter gets a different value every time the page reloads, and cannot identify the user. (Source: Brave's official "Fingerprinting defenses 2.0" documentation.)

3.2 Observed Pattern (measured fact)

Based on our test results:

These are reproduced facts from our own measurements, not assumptions.

3.3 Working Hypothesis on the Cause of the Phenomenon

We have no official Brave source confirming the existence of a separate "adaptive" or "parity"-based farbling layer independent of the Shields toggle. The verified information available to us (GitHub issue #9186 and Brave's official farbling documentation) only confirms that:

  1. All four canvas serialization APIs (getImageData, toDataURL, toBlob, convertToBlob) are subject to farbling.
  2. The exact noise level of farbling, and whether it varies dynamically, is not a public specification but logic implemented in the Chromium-based Brave source code that changes between versions and that we cannot fully audit from the outside.

Based on this, the currently most likely — but unconfirmed — explanation is that the farbling noise level (whether at the Shields base level or in some partially independent internal logic) may be sensitive to the pattern of repeated, closely-spaced canvas reads that our export pipeline produces, and that this could explain both the progressively worsening failure rate and why disabling Shields only partially mitigates the issue. We treat this explanation as a working hypothesis, not a confirmed fact.

3.4 Why Are Firefox and Chrome Unaffected? (fact + well-supported inference)

This is consistent with our observed results: using the same images on the same device, the error occurred only in Brave.

4. Why Is There No Known Software Fix?

4.1 Every Known Export Path Is Affected

To produce a finished image file (JPEG/PNG/WebP) from the browser, some canvas serialization API must necessarily be used. According to Brave GitHub issue #9186, all four are subject to farbling:

getImageData()      → farbling ✗
toDataURL()          → farbling ✗
toBlob()             → farbling ✗
convertToBlob()      → farbling ✗

There is no standard web API that can produce a finished image file while bypassing these endpoints.

4.2 The WebCodecs ImageDecoder Is Not a Solution Either

We also examined the WebCodecs ImageDecoder API, which returns the decoded image as a VideoFrame object, bypassing canvas. VideoFrame.copyTo() provides a raw RGBA buffer — this is indeed canvas-free. However, producing the final image file ultimately requires calling OffscreenCanvas.convertToBlob(), which is itself subject to farbling. ImageDecoder therefore only solves the decoding side, not the export.

Furthermore, the WebCodecs ImageDecoder is only fully available on Chrome for Android from version 147 onward, and is not supported at all on Firefox for Android — making it non-viable as a baseline cross-browser fallback as well.

4.3 Disabling Shields Does Not Solve Anything (measured fact)

Since disabling Shields did not eliminate the error in our tests (it only delayed it from image 2 to images 4–5), detecting the Shields state, or suggesting the user "turn off Shields," would be misleading: the user would disable Shields, and the error would likely persist anyway.

5. The Decision and Its Rationale

5.1 What We Did in the Code (v7)

The current production v7 code represents the best available technical state:

5.2 The Final Decision

When the application loads, we detect the Brave browser (navigator.brave.isBrave()) and inform the user via a modal dialog that the application is not compatible with the Brave browser.

Rationale:

Based on our measurements, Brave documentedly modifies canvas API output (farbling), and this modification — regardless of the exact internal mechanism — makes our image-processing pipeline unreliable in practice for repeated exports. This is not a bug in the application, not a server-side bug, and, to the best of our current knowledge, cannot be fixed at the JS level without giving up the ability to produce a finished image file (which unavoidably requires a canvas serialization API).

Informing the user and leaving the decision to them — to choose a different browser — is the only honest and sustainable solution until a verifiable technical solution becomes available that bypasses the farbled APIs.

5.3 Why Not Detect Shields Instead?

Since our measurements show that disabling Shields does not solve the problem (it only delays it), detecting the Shields state or suggesting the user "turn off Shields" would be misleading. This is a worse user experience than clear, upfront communication.

6. One-Sentence Summary

Brave documentedly modifies the output of every canvas serialization API through farbling (including OffscreenCanvas.convertToBlob()), this modification is not fully eliminated by disabling Shields, and as a result, the Image Metadata Cleaner's image export pipeline fails unreliably in Brave during repeated processing; the exact internal mechanism of the phenomenon (why the failure rate worsens on repeated reads) is not officially documented and is therefore presented as a working hypothesis — however, the practical conclusion (incompatible, a different browser is recommended) holds based on the measured, reproducible behavior.