Trust & verifiability
How SafePaper stays private
“Runs locally” is easy to claim. SafePaper is different in a way you can check yourself: there is no upload endpoint for your files at all. Every tool processes your documents inside this browser tab, and you can prove it in under a minute.
Verify it yourself in the Network tab
- Open DevTools. Press F12 (or Cmd+Option+I on macOS) and switch to the Network tab. Tick 'Preserve log' so requests are not cleared.
- Clear and record. Clear the existing requests so the list is empty, then keep recording while you use a tool.
- Drop a file and run a tool. Add a document and run any operation — merge, compress, redact, convert. Watch the request list as it processes.
- Confirm zero uploads. You will see the app shell, JS/CSS chunks, fonts, and same-origin engine assets — but no request that carries your file's bytes to a SafePaper server.
The one thing you will never see is your file's contents leaving your machine for SafePaper to process. That is the whole design.
The architecture, in detail
SafePaper is built so your PDFs are processed in your browser, not on SafePaper servers. The sections below explain this in plain language.
Hosting & delivery
- The site is a Next.js (App Router) application. Production is static HTML, JavaScript bundles, CSS, and assets—served like any modern front-end (e.g. on Vercel).
- There is no Route Handler, no
pages/api, and no server action that accepts your PDF for processing. We do not operate an upload endpoint for document bodies.
Runtime (your machine)
- UI is React 19 + TypeScript, styled with Tailwind CSS, with motion via Framer Motion where noted in the interface.
- Dropped files are read with the browser File API into
ArrayBuffer/Uint8Arrayand kept in client-side React state (a shared provider across tool pages in one visit). The visible queue is also mirrored to sessionStorage when it fits browser limits so a refresh in the same tab can restore filenames and bytes without contacting a server. - Heavy merge, split, organize, compress, crop (CropBox and multi-region PDF exports), and redact PDF output runs in a dedicated Web Worker (
pdf-operations.worker.ts,compress-pdf.worker.ts,bookmarks-outline.worker.ts,extract-embedded-images.worker.ts,crop-pdf.worker.ts) so the main UI thread stays responsive. Buffers are passed withpostMessage+Transferablewhere supported to avoid extra copies. - Organize's optional duplicate-page scan runs in
pdf-duplicate-pages.worker.tswith pdf.js +OffscreenCanvas—still only on your device.
Security Vault
- Security Vault (
/security) runs JWT decode, hashing, AES file encrypt, key generation, TOTP, bcrypt, and Shamir sharing with Web Crypto and jose in your tab only. - Optional site analytics and the consent banner are disabled on Security Vault routes even if you opted in elsewhere—so secrets pasted here are not on pages that load Vercel Analytics.
Share Desk (peer-to-peer)
- Share Desk (
/share) transfers files with WebRTC data channels directly between two browsers. SafePaper does not store or process file bytes on our infrastructure. - A separate WebSocket signaling service (configured via
NEXT_PUBLIC_SIGNALING_URL) relays SDP offers/answers and ICE candidates so peers can find each other. That relay carries connection metadata only—not your file contents. - Large receives stream into OPFS via
opfs-writer.worker.tsso the main UI thread stays responsive. Senders read files in 64KB slices with data-channel backpressure. - Peers may use Google's public STUN (
stun:stun.l.google.com:19302) to discover network paths. Optional TURN env vars (NEXT_PUBLIC_TURN_URL, etc.) can relay traffic on restrictive networks—that is separate third-party network traffic, not a SafePaper file upload.
PDF stack
- Structural PDF work (merge order, page extraction, rotation, removal) runs in the worker path described above.
- Optional password protection applies AES-256 encryption on the main thread after the worker returns bytes, using the browser's Web Crypto API. Your open password is not sent to any server.
- Previews and raster export (PNG images and plain-text extraction) use a canvas-based PDF renderer in a separate worker. The preview worker script is served as static
/pdf.worker.mjsfrom this site. CMaps, standard font metrics, ICC profiles, and WASM helpers for pdf.js live under/pdfjs/(copied frompdfjs-distonnpm install) so workers can fetch glyph data from the same origin. - That preview worker is separate from the merge/split/organize worker; both use Web Workers and stay entirely on your device.
Network & storage
- Over the wire you should see requests for the app shell, JS chunks, CSS, fonts (next/font self-hosted subsets), icons, the PDF preview worker script, and (after
npm install) same-origin OCR assets under/tesseract/—not outbound uploads of your document to SafePaper for processing. - Downloads use normal browser mechanisms (
Blob/URL.createObjectURL) initiated by you. - We do not persist your PDFs to SafePaper storage; lifetime is in-memory for that tab session (and the shared client state resets when you leave the tool area for the home page).
Tool-by-tool: where work runs
| Tool / area | Where work runs | Extra network |
|---|---|---|
| Merge, split, organize, protect | Browser + workers | App shell only |
| Compress, redact, clean | Browser + workers | App shell only |
| Export (PNG / JPEG / text) | Browser (pdf.js canvas) | App shell only |
| Forms, watermark, page #, insert | Browser (pdf-lib) | App shell only |
| OCR | Browser (pdf.js + Tesseract) | Same-origin WASM/worker/lang after postinstall (no third-party OCR CDN) |
| Duplicate scan (Organize) | Browser worker (pdf.js) | App shell only |
| Share Desk (P2P) | Browser WebRTC + OPFS worker | Signaling WebSocket + STUN (optional TURN) — no file bytes to SafePaper |
| Media Studio | Browser + FFmpeg WASM (lazy) + image workers | Same-origin FFmpeg under /vendor/ffmpeg/ |
| Data Zone | Web Workers + DuckDB-wasm + OPFS queues | DuckDB bundles may fetch from jsDelivr on first SQL use — not your table rows |
| Security Vault | Browser Web Crypto + jose (no upload) | Analytics disabled on these routes |
| AI Desk (RAG) | Embedding + chat workers; IndexedDB index | One-time model weight downloads (Transformers.js / WebLLM CDN) |
| Record Desk | Capture + OPFS mux; Whisper worker | Whisper ONNX/WASM may fetch from jsDelivr on first caption run |
| Vision Studio | Image inference worker (ONNX / Transformers.js) | One-time model weight downloads (Hugging Face / jsDelivr); images stay local |
| Audio Lab | FFmpeg WASM worker + Web Audio; Whisper for transcription | One-time FFmpeg/Whisper asset downloads; audio samples stay local |
| Health Desk | dicom-parser + canvas; pure-JS HL7/FHIR; localStorage lab history | None — scans, records, and lab data are parsed and stored entirely on-device |
| Archive Desk | zip.js + fflate in archive worker | App shell only (central directory parsed locally) |
| Data Room | crypto-packager worker + offline HTML export | Same-origin /dataroom-template.html at build time |
“App shell only” means HTML/JS/CSS/fonts and static assets from your host—no SafePaper API that accepts PDF bodies. A production service worker precaches the home page, PDF preview worker, and vendored OCR files for calmer offline reloads after the first visit.
Offline & limits
- After the app has loaded once, cached bundles mean the core tools can keep working offline for files already held in memory—subject to browser cache eviction.
- Practical limits are those of the device: available RAM, CPU, and browser tab limits. Malicious PDFs are still a local execution concern the same way opening a file in any desktop viewer is; we do not add a separate server-side malware scan because files never leave your machine for our processing.
Design rule
Zero server uploads for PDF work. New tools are expected to follow the same model: browser-first, worker offload where needed, and no silent exfiltration of document bytes to SafePaper infrastructure.