No upload is an architecture, not a badge
No upload is an architecture, not a badge
A file tool can say “private” while still loading analytics, sending error reports, calling an
AI API, or importing code from a third-party CDN. A static page is not automatically local. The
useful question is narrower: after a person selects a file, what code paths can move its bytes or
derived content off the device?
That question can be answered with an architecture and a bounded proof contract. It cannot be
answered by a lock icon alone.
Start with the data path
A small browser-local image tool needs only four stages:
- read a user-selected
FileorBlob; - decode it with a native browser API;
- transform it in memory; and
- create a downloadable
Blob.
For images, createImageBitmap() or an object URL can decode the source. Canvas can resize, crop,
pad, or composite it. canvas.toBlob() creates the output. An anchor with a temporary object URL
starts the download. For text files, arrayBuffer(), TextDecoder, a local parser, and a generated
Blob cover the same flow.
None of those operations requires a server. The important property is not the choice of framework.
It is the absence of a network-capable step between input and output.
Inventory every network sink
“There is no backend” is incomplete. Browser code can still transmit data through fetch,
XMLHttpRequest, WebSocket, sendBeacon, form submission, an image URL, an analytics SDK, an
error reporter, or a service worker. External fonts and scripts may not receive the selected file,
but they enlarge the trust boundary and can change later without a new application release.
A practical review therefore inventories:
- scripts, styles, fonts, frames, and workers loaded by the page;
- every API that can initiate a request;
- every place a file name, error, preview, or derived statistic reaches a URL;
- storage that persists content after the tab closes; and
- dependencies whose runtime behavior is outside the repository.
A source search is useful, but it is only one layer. A dynamically constructed request can evade a
simple search. A stronger check combines source inspection, a restrictive Content Security Policy
when hosting permits it, and a browser run with the network log preserved while synthetic files are
processed.
Treat previews and downloads as resources
Object URLs keep their backing data alive until they are revoked or the document is discarded.
Create them as late as possible and revoke preview URLs after an image loads. Revoke download URLs
after the click has had time to begin. Close ImageBitmap objects when the browser exposes
close().
This is mainly a memory and lifecycle concern, not proof of exfiltration. But large product images
make leaks visible quickly: repeated previews can retain tens or hundreds of megabytes in one tab.
Privacy-friendly software should still fail predictably under resource pressure.
Make output deterministic
A local tool is easier to inspect when its output path is boring. Given the same source dimensions,
mode, quality, preset, and product slug, it should derive the same canvas rectangle and file path.
That allows small tests to cover the geometry and naming rules without requiring a browser for every
case.
ZIP generation is another boundary. A tool can use a reviewed bundled library, or a tiny store-mode
writer for already-compressed JPG files. Either way, validate entry names, byte lengths, checksums,
and directory structure. “The browser downloaded something” does not prove that every expected file
is present or named safely.
State the proof honestly
A bounded verification report can support claims such as:
- no form, analytics script, external runtime dependency, or application network call exists in the
reviewed build; - synthetic JPG, PNG, and WebP inputs create the expected local output;
- output paths and fit/crop geometry match tested examples; and
- temporary object URLs are revoked on the reviewed success and error paths.
It cannot prove that every browser version, image, memory limit, extension, hosting layer, or future
deployment is safe. “Browser-local in this reviewed build” is a stronger and more honest claim than
“100% private forever.”
A reusable acceptance checklist
Before publishing a browser-local file tool, check:
- Can it complete the core flow with the network offline?
- Does the page load any third-party runtime code, analytics, font, frame, or worker?
- Can any file bytes, file names, errors, previews, or derived values reach a network sink?
- Are supported types and input limits explicit before processing starts?
- Are decode, transform, archive, and download failures visible to the user?
- Are object URLs and decoded resources released?
- Are geometry, naming, parsing, and archive rules tested independently of the UI?
- Does the public copy describe only what those checks establish?
MarketPack is a small MIT-licensed example that performs image decoding, Canvas transforms, JPG
export, and ZIP assembly in the browser:
- Live demo: https://softpeanut.github.io/marketpack/
- Source: https://github.com/softpeanut/marketpack
The free build processes up to three source images. A completed unlimited offline edition is listed
for a 15,000-sat Personal license. Its order path requires only a GitHub username and never asks for
source images, file names, contact details, or payment proof:
If this article was useful, it can also be zapped through the pseudonymous softpeanut tools
profile. A zap buys no product, service, support, feature, or guarantee.
Write a comment