How WebAssembly became our privacy architecture (not just a performance trick)
Most WebAssembly articles talk about performance. Yours runs 5x faster than pure JavaScript. Great. This one is about something different: how Wasm's sandboxed execution model makes certain privacy failures structurally impossible -- and how we built 90+ browser tools around that property. The Problem We Were Solving When someone uses iLovePDF, Smallpdf, or Adobe Acrobat Online to process a PDF, their file travels to a remote server, gets processed, and sits there until "deleted" (usually 1-24 hours). For a casual PDF merge, fine. For a tax return, medical record, or legal contract -- that's a real data exposure event. Most "privacy-focused" alternatives solve this with a policy: "We delete your files after processing." That's still an upload. The file still left the device. We wanted something different: make the upload architecturally impossible, not just unlikely. Why WebAssembly Works Here WebAssembly modules run inside the browser's sandboxed execution environment. The critical property for privacy: Wasm modules cannot initiate network connections. They have no access to fetch() , XMLHttpRequest , or any network API. The only way a Wasm module can communicate with the outside world is through explicitly provided JavaScript host functions. This means if you build your processing pipeline correctly, there is no point at which a network request carrying file data can exist. It's not a privacy setting. It's a structural constraint of the runtime. The In-Browser Pipeline Here's the exact pipeline for every file operation on our site: // Step 1: Read file locally -- stays in browser memory const arrayBuffer = await file.arrayBuffer(); // Step 2: Pass to Wasm module -- sandboxed, no network access const result = await wasmModule.process(arrayBuffer); // Step 3: Create output Blob -- still in browser memory const blob = new Blob([result], { type: 'application/pdf' }); // Step 4: Create a local URL for download const url = URL.createObjectURL(blob); // Step 5: Trigger download -- from memory directly to device downloadLink.href = url; downloadLink.click(); // Step 6: Clean up -- no trace remains URL.revokeObjectURL(url); At no point does file data touch a network socket. The ArrayBuffer goes in, a processed ArrayBuffer comes out, and a Blob URL triggers a local download. The Open Source Stack All processing uses open source WebAssembly libraries: PDF Processing -- pdf-lib (MIT) pdf-lib handles PDF creation and modification in pure JavaScript. No Wasm compilation needed -- it operates directly on ArrayBuffer data. Powers: Merge PDF, Split, Compress, Rotate, Crop, Watermark, Page Numbers, Protect (AES encryption), Unlock, Rearrange Pages. PDF Parsing -- pdfjs-dist (Apache 2.0) PDF.js from Mozilla handles rendering and text extraction. We run it inside a dedicated Web Worker -- isolated from the main thread, with no network API access. Powers: Extract Text from PDF, Extract Images, PDF preview. OCR -- Tesseract.js (Apache 2.0) Tesseract.js compiles the Tesseract OCR engine (C++) to WebAssembly. The full LSTM neural network runs in the browser: const worker = await createWorker('eng'); const { data: { text } } = await worker.recognize(imageFile); // 'text' contains the recognized content // zero network requests containing image data occurred await worker.terminate(); The Wasm binary (~10MB) downloads once and is cached by the browser. After that, OCR works completely offline. Powers: Image to Text, Extract Text from scanned PDFs. HEIC Decoding -- heic2any (MIT) heic2any decodes iPhone HEIC photos client-side: const blob = await heic2any({ blob: heicFile, toType: 'image/jpeg', quality: 0.9 }); // blob is a local JPEG -- never left the device Powers: HEIC to JPG, HEIC to PNG. Web Workers for CPU-Intensive Operations For heavy operations, we offload to a Web Worker to avoid blocking the main thread: // Main thread -- transfer ArrayBuffer (zero-copy) const worker = new Worker('/workers/pdf-processor.js'); worker.postMessage( { type: 'COMPRESS', buffer: arrayBuffer }, [arrayBuffer] // transferable -- ownership moves, no copy ); worker.onmessage = ({ data }) => { if (data.type === 'DONE') { deliverDownload(data.result); } }; Transferring ArrayBuffer objects rather than copying them keeps memory flat regardless of file size. Verifying the Privacy Claim This is the part we care most about. Any user can verify this in 60 seconds: - Open DevTools -> Network tab - Check "Preserve log" and clear existing entries - Select a file and run any tool - After processing completes, examine the Network tab You'll see requests for static assets -- JS bundles, CSS, the Wasm binary on first load. You will not see any request with your file's bytes as the payload. We documented the full pipeline at ihaveatoolforthat.com/privacy-guarantee with the specific intent that any technically inclined user can verify the claims themselves. The Tradeoffs Client-side Wasm processing is not free. The honest tradeoffs: Advantages: - Zero network latency -- no upload/download round trip - Works offline once Wasm binaries are cached - No server infrastructure that scales with usage - No breach surface for file data (nothing to steal from a server that never received it) Disadvantages: - First load: Wasm binaries can be large (Tesseract ~10MB) - CPU-bound on the client -- slow devices process slowly - Memory limited to browser tab allocation (~2GB practical limit) - No server-side caching of results For our use case -- occasional document processing -- the tradeoffs are strongly in favor of client-side processing. What This Architecture Gets You Beyond privacy, building this way has some unexpected benefits: Zero infrastructure cost for processing. Our servers host static files. The actual PDF merging, OCR, and image conversion runs on users' CPUs. We never pay for compute proportional to usage. Offline capability. Once the Wasm binaries are cached, every tool works without an internet connection. We added manifest.json and service worker support -- users can install it as a PWA and process files on a plane. No rate limits. Cloud services throttle you because they're paying for compute. We're not -- so there are no daily limits, no file size caps based on subscription tier. The Broader Point WebAssembly's sandboxed execution model is usually discussed as a security property -- Wasm can't break out of its sandbox and do malicious things. But the same property works in the other direction: it also prevents Wasm code from doing things the user didn't explicitly authorize, like sending their files to a server. If you're building tools that handle sensitive user data, this is worth thinking about not as a privacy feature you add -- but as a design constraint that makes certain privacy failures simply impossible by construction. Tools: ihaveatoolforthat.com Technical details: ihaveatoolforthat.com/privacy-guarantee Built with pdf-lib, Tesseract.js, pdfjs-dist, and heic2any -- all MIT or Apache 2.0 licensed. Top comments (0)
Comments
No comments yet. Start the discussion.