FormFix

Security

FormFix is built so that privacy and security are not features bolted on later — they are consequences of the architecture. Every choice below is verifiable in the open-source code on GitHub.

Zero-upload architecture

Files are read into browser memory using the File and Blob Web APIs and processed by your own CPU via Canvas, OffscreenCanvas, Web Workers, and WebAssembly. There is no upload endpoint, no S3 bucket, and no server function. Network tab shows zero file traffic while you work.

Strict Content Security Policy

CSP is enforced via the Cloudflare Pages _headers file with strict directives: default-src 'self', script-src 'self' 'unsafe-inline' (allows Astro's small inline module scripts but blocks all remote script origins), style-src 'self' 'unsafe-inline', img-src 'self' data: blob:, font-src 'self', connect-src 'self' (no external endpoints — 100% client-side confirmed), object-src 'none', worker-src 'self' blob:, frame-ancestors 'none', base-uri 'self', form-action 'self'. There is no 'unsafe-eval' anywhere. The browser refuses to execute any script or fetch any resource outside our own origin.

EXIF metadata stripped by default

When you process an image with any tool that re-encodes it (compress, resize, crop, convert, watermark, etc.), the output is freshly encoded from a canvas or ImageBitmap. Original EXIF tags — including GPS, camera serial, and timestamps — do not transfer to the output unless the tool explicitly preserves orientation.

File validation (validateFile)

Every tool routes your file through validateFile() before processing. MIME type is checked against an allowlist (image/png, image/jpeg, image/webp, application/pdf). Size is capped at 20 MB for images and 100 MB for PDFs. Anything else is rejected before any work begins.

Filename sanitization

User-supplied filenames pass through sanitizeFilename() before being used in downloads. Path traversal sequences (../), null bytes, control characters, and reserved names are stripped. Filenames are truncated to 100 characters. The output extension is always set by the tool, never by the user.

Blob URL lifecycle

Every URL.createObjectURL() call has a matching URL.revokeObjectURL() in a finally block or cleanup listener. ResultCard auto-revokes the previous preview URL before applying a new one and on astro:before-swap / pagehide. No blob URL outlives its preview element.

Service Worker cache

A registered Service Worker caches the application shell and static assets (HTML, CSS, JS, fonts, WASM) so the site loads instantly and works offline. The cache contains only same-origin static files — never user data. Updates ship via skipWaiting on next navigation.

Cryptography for sensitive tools

The Password Generator uses crypto.getRandomValues with rejection sampling to eliminate modulo bias. Output is never logged, never transmitted, and cleared from the DOM when you leave the page.

Reporting a vulnerability

Found a security issue? Please do not open a public GitHub issue. Email hello@formfix.vexr.dev with details and a proof of concept. We aim to acknowledge within 72 hours and to ship a fix within 30 days for verified high-severity reports.

Because FormFix has no servers, no databases, and no user accounts, the realistic blast radius of any vulnerability is limited to the browser session in which it occurs.

Curious about what data we do and do not collect? See the Privacy Policy. For usage terms, see the Terms of Service.