How I Built a 100% Client-Side Bank Statement Redactor Using HTML5 Canvas & WebAssembly
When applying for an apartment lease, mortgage underwriting, or a travel visa, almost everyone is asked to submit 2 to 3 months of bank statements or payslips. However, sharing unredacted financial records exposes sensi
When applying for an apartment lease, mortgage underwriting, or a travel visa, almost everyone is asked to submit 2 to 3 months of bank statements or payslips.
However, sharing unredacted financial records exposes sensitive Personally Identifiable Information (PII)βincluding full 10β16 digit account numbers, 9-digit ABA routing transit codes, and private transaction histories.
Most users try to protect themselves using two flawed methods:
Uploading confidential files to free online PDF editors: This sends sensitive financial data to unknown third-party cloud servers.
Drawing black rectangles in preview software: This merely places a cosmetic overlay on top of vector text. Anyone can simply copy-paste or inspect the text underneath.
To solve this, I built BankXLSβa privacy-first, zero-cloud financial document redactor that operates 100% inside the user's browser using HTML5 Canvas, WebAssembly (Wasm), and destructive raster flattening.
Here is an architectural breakdown of how it works under the hood.
The Core Architecture: Why "Cosmetic" Redaction Fails
A standard digital PDF contains multiple distinct layers:
- Vector text streams (raw characters, fonts, and bounding boxes)
- Metadata streams (author, software, creation dates)
- Form field definitions (AcroForms / XFA)
- Raster image assets
If you draw a black rectangle using a basic PDF viewer, the underlying character codes remain completely intact in the content stream.
ββββββββββββββββββββββββββββββββββββββββββββββββ
β [ Visual Blackout Box (Cosmetic Layer) ] β <-- Visible on screen
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β Account No: 4821 9081 2291 β <-- Still selectable!
ββββββββββββββββββββββββββββββββββββββββββββββββ
To achieve true, unrecoverable redaction without sending data to an external server, the pipeline must perform destructive pixel rasterization directly in browser RAM.
Step 1: Client-Side PDF Parsing via WebAssembly (pdf.js)
Instead of parsing files on a Node.js or Python backend, we read the uploaded document using the HTML5 File API and pass the raw ArrayBuffer directly to Mozilla's pdf.js worker, compiled to WebAssembly:
// Read document locally into memory
const fileBuffer = await file.arrayBuffer();
// Initialize client-side PDF document
const loadingTask = pdfjsLib.getDocument({ data: fileBuffer });
const pdfDoc = await loadingTask.promise;
const numPages = pdfDoc.numPages;
Because this worker runs entirely in a client-side Web Worker thread, zero bytes leave the user's machine.
Step 2: Interactive Coordinate Mapping with HTML5 Canvas
Each page of the PDF is rendered onto an off-screen HTML5 element at 2.0x device pixel ratio to maintain razor-sharp text fidelity.
When the user selects an area to blackout (such as an account number or balance column), we compute normalized bounding coordinates:
interface RedactionBox {
pageNumber: number;
x: number; // Normalized (0.0 to 1.0)
y: number; // Normalized (0.0 to 1.0)
width: number;
height: number;
}
By normalizing coordinates relative to viewport dimensions, user-drawn redactions remain pixel-perfect across responsive mobile screens, tablets, and desktop displays.
Step 3: Destructive Canvas Rasterization (Burning the Redactions)
This is where the magic happens. Rather than appending visual annotation objects to a PDF syntax tree, we composite the redaction bounding boxes directly into the 2D rendering context:
// Grab the 2D rendering context of the rasterized page
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (ctx) {
// Burn solid opaque black pixels directly over sensitive coordinates
ctx.fillStyle = '#000000';
ctx.fillRect(
box.x * canvas.width,
box.y * canvas.height,
box.width * canvas.width,
box.height * canvas.height
);
}
At this stage, the underlying vector characters are permanently replaced by black RGB pixels (0, 0, 0). The character codes cease to exist in memory.
Step 4: Flattening and Assembling the Clean PDF
Once the redactions are burned into the canvas, we export the canvas buffer as high-quality lossless image slices and rebuild an unalterable, flattened PDF using jspdf:
import { jsPDF } from 'jspdf';
const exportDoc = new jsPDF({
orientation: canvas.width > canvas.height ? 'landscape' : 'portrait',
unit: 'pt',
format: [canvas.width, canvas.height]
});
// Convert canvas to image data
const imgData = canvas.toDataURL('image/jpeg', 0.95);
// Add the flattened raster layer to the new PDF
exportDoc.addImage(imgData, 'JPEG', 0, 0, canvas.width, canvas.height);
exportDoc.save('redacted_statement.pdf');
The resulting document:
- Contains zero selectable text in redacted areas.
- Has all original metadata, author tags, and font objects stripped.
- Cannot be reversed or inspected using Adobe Acrobat or developer tools.
What We Learned About Web Privacy in 2026
- Users care deeply about financial privacy: Many people hesitate to use cloud tools for bank statements and tax filings. Providing a client-only architecture with zero server logs immediately establishes user trust.
- Modern WebAssembly and Canvas engines are extraordinarily fast: Parsing and flattening a 5-page bank statement in client memory takes less than 800 milliseconds on a standard smartphone.
- Privacy by design simplifies compliance: By refusing to store user documents on a database, GDPR and CCPA data retention liabilities drop to zero.
If you want to test the live implementation or securely sanitize your own bank statements, payslips, or invoices, feel free to try the live tool at BankXLS.
Have you built privacy-first client-side web tools? What libraries do you prefer for in-browser PDF manipulation? Let me know in the comments!
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.