iKit
Security · 10 min read ·

Privacy-First Color Tools: Why Cloud Pickers Leak (2026)

Privacy-first color tools keep brand assets on your machine. Here is how to test whether an online color picker uploads your image, and why it matters.

Privacy-First Color Tools: Why Cloud Pickers Leak (2026)

Privacy-First Color Tools: Why Cloud Pickers Leak

You have an unreleased logo, a client screenshot, or a competitor's app UI, and you need one hex value out of it. Most online color tools ask you to upload the file. That upload is the whole problem: a privacy-first color tool never needs it, because everything required to read a pixel already ships in the browser. Here is how to tell the two apart, and what the risk actually is.

TL;DR

  • Reading a pixel needs zero network calls — canvas getImageData() does it locally.
  • Test any color tool with DevTools offline mode; working offline proves client-side.
  • Brand assets and screenshots carry far more than color: metadata, names, roadmap.
  • The EyeDropper API requires transient user activation and cannot silently scrape.
  • GDPR data minimisation makes "upload the whole file for one hex" hard to justify.

What actually happens when you upload an image to a color tool

The interaction feels identical either way — you drop a file, you get a swatch. The difference is where the bytes went in between.

The three places your file can end up

A browser-only tool passes your file to FileReader or createImageBitmap(), paints it into a canvas element in your own tab, and reads four bytes back. The file never crosses a network boundary; closing the tab is a complete delete.

A server-based tool sends the file to an origin server, which decodes it, samples it, and returns JSON. Your file now exists in at least three places: the request log, the temp directory, and whatever object store the framework's upload handler defaulted to.

A hybrid tool is the one that catches people out. The picking is local, but the "save palette", "share link", or "analytics" path quietly ships a thumbnail or the raw file anyway. This is why testing beats reading the marketing copy.

Why "we delete after 24 hours" is a policy, not a mechanism

Retention promises describe intent. They do not describe the CDN edge cache that held the response, the error tracker that captured the request body on a 500, the backup snapshot taken at 03:00, or the subprocessor whose GPU did the decoding. None of those are bad faith. They are just what normal infrastructure does with bytes it receives.

Client-side processing sidesteps the entire category. There is no retention question when there was never a transfer.

Is it safe to upload brand assets to an online color tool

For a public asset already on your website, the practical risk is near zero. For anything else, the honest answer is that you are extending your confidentiality boundary to a company you have not diligenced, for a task that does not require it. Unreleased identity work, acquisition-related mockups, and client screenshots under NDA are the usual candidates, and they are exactly the files people reach for a quick color picker with.

How to check if an online color picker uploads your image

Three tests, in ascending order of confidence. Any developer can run all three in under two minutes.

The Network-tab test in 30 seconds

Open DevTools, go to Network, click the clear button, then load your image into the tool and pick a color. Watch what fires.

// Or instrument it from the console before you load a file:
const realFetch = window.fetch;
window.fetch = (...args) => {
  console.warn("fetch →", args[0]);
  return realFetch(...args);
};

const send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (body) {
  console.warn("xhr →", this.__url, body);
  return send.apply(this, arguments);
};

Analytics beacons are normal and expected. A request whose payload is measured in hundreds of kilobytes is your image.

The offline test (the one that actually proves it)

Load the page fully, then set DevTools' network throttling to Offline, then load your image and pick a color. If the tool still returns a hex value, no server was involved — it cannot be, there is no network. This is the test worth trusting, because it cannot be faked by deferring the upload.

Do the same check on color.ikit.app, palette.ikit.app, and gradient.ikit.app — all three keep working offline after first load, which is the point.

Why does getImageData throw a SecurityError

If you build your own picker, you will meet the tainted-canvas rule fast. Per MDN's guidance on using cross-origin images in a canvas, drawing cross-origin content into a canvas without CORS approval marks it as tainted, and getImageData(), toBlob(), toDataURL(), and captureStream() all throw a SecurityError afterwards.

That restriction is worth reading as a signal. The browser treats "read pixels back out of a canvas" as a privileged operation precisely because pixels leak information. A tool that routes around it by proxying the image through its own server has moved your data to solve a browser security rule — a trade most people would not accept if it were stated out loud.

Why pasting brand colors into a cloud service is worth a second thought

Colors themselves are rarely the sensitive part. The container they arrive in usually is.

The file carries more than the pixel you wanted

A screenshot of a dashboard contains customer names, email addresses, internal URLs, ticket IDs, and feature flags. A photo carries EXIF, including camera serial and often GPS coordinates. A design export can carry layer names that describe an unshipped roadmap. You uploaded it to read #4F46E5; you transferred all of the rest.

What you wanted What the file also contained
One hex value Customer names, internal URLs
A 5-swatch palette EXIF GPS, camera serial number
A gradient's two stops Layer names for unreleased features
A contrast check Account IDs visible in the UI

Brand identity is confidential before launch, public after

Rebrands are embargoed for good commercial reasons: campaign timing, trademark filings, partner coordination, and occasionally securities-sensitive M&A context. The window where a palette is secret is short, but it is the exact window in which the design team is iterating and reaching for quick color tools every hour.

Data minimisation is a legal principle, not a vibe

Under Article 5(1)(c) of Regulation (EU) 2016/679, personal data must be adequate, relevant, and limited to what is necessary for the purpose of processing. Uploading a full customer-facing screenshot in order to obtain a six-character hex string is difficult to describe as "limited to what is necessary" — especially when a local alternative produces an identical result.

What the browser already gives you for free

The reason cloud color processing is unnecessary is that every primitive it needs has been in browsers for years.

Pixel access with canvas, no server required

The complete implementation of "read the color at a coordinate" is about ten lines:

async function pickPixel(file, x, y) {
  const bitmap = await createImageBitmap(file);
  const c = new OffscreenCanvas(bitmap.width, bitmap.height);
  const ctx = c.getContext("2d", { willReadFrequently: true });
  ctx.drawImage(bitmap, 0, 0);
  const [r, g, b] = ctx.getImageData(x, y, 1, 1).data;
  const hex = (n) => n.toString(16).padStart(2, "0");
  return `#${hex(r)}${hex(g)}${hex(b)}`;
}

OffscreenCanvas works inside a Worker, so batch extraction across dozens of files never blocks the UI thread — see MDN's OffscreenCanvas reference for the transfer patterns.

The EyeDropper API and its user-activation rule

Sampling a pixel from outside the page is the one operation that genuinely needs privileged access, and the WICG specification is deliberately conservative about it. The API is SecureContext-only, rejects with a NotAllowedError unless the global has transient activation, rejects with InvalidStateError if another eyedropper is already open, and suppresses all UI events to the page while active.

The threat model is stated plainly in the spec: an implementation

should not allow a web page to "screen scrape" information the user didn't intend to share with the web application

WICG EyeDropper API. Hence the one-click-one-color design.

const btn = document.querySelector("#pick");
btn.addEventListener("click", async () => {
  if (!window.EyeDropper) return; // Chromium desktop only
  try {
    const { sRGBHex } = await new EyeDropper().open();
    console.log(sRGBHex); // "#4f46e5"
  } catch {
    // User pressed Escape — not an error worth surfacing
  }
});

Note what never happens here: no image is uploaded, and no pixel data reaches the page except the single color you deliberately clicked.

Where a server is still genuinely required

Being honest about the boundary makes the rest of the argument credible. A server earns its place when the work is collaborative or heavy:

  • Real-time multiplayer editing of a shared palette across a team
  • Long-term versioned storage of design tokens with an audit trail
  • Rendering brand assets into PDFs or press kits on a schedule
  • Very large model inference that cannot fit in a WASM bundle
  • Programmatic token distribution into a CI pipeline

Extracting a hex, converting notations, generating a ramp, and checking contrast are not on that list. They are arithmetic on four bytes.

A privacy-first color workflow that still ships fast

Nothing here asks you to work slower. The substitutions are one-for-one.

Task Local approach
Pixel → hex Canvas getImageData() in-tab
Screen sampling EyeDropper API, one click
Tonal ramp OKLCH lightness steps, computed client-side
Contrast audit WCAG ratio maths in JavaScript

Practical rules that hold up in a real team:

  • Treat any unreleased asset as NDA material by default, including screenshots.
  • Prefer tools that keep working with DevTools set to Offline.
  • Crop before you sample — if only the button matters, do not carry the dashboard.
  • Store final tokens in your repo, not in a third-party palette account.
  • Re-test tools after major redesigns; architecture changes and privacy pages lag.

If you do need to trim a screenshot down to just the region you care about before sampling, imagecropper.ikit.app runs the same way — the file is decoded in your tab and never leaves it.

References

Related on iKit

Related posts