Skip to content
reader.me

Technical notes

Security model

What reader.me protects against, what it doesn't, and how decisions like "use AES-128 instead of AES-256" or "keep 'unsafe-inline' in CSP" were actually made. Written for security reviewers and skeptical readers — no marketing.

Threat model

reader.me is a single-page web application that runs inside the user's browser. The threat surface is shaped by that fact.

We do defend against

  • Document leak via upload. The whole architecture is built to make uploading documents structurally impossible.
  • Document leak via third-party JS. The CSP locks script-src down to 'self' + a small allowlist.
  • Malicious PDFs targeting PDF.js. Pinned to ≥4.2.67 (CVE-2024-4367 fix) with isEvalSupported: false and enableXfa: false.
  • Password brute-force on PDF Protect. AES-128 with random IV — conservative choice over the AES-256 mode with known hash-iteration weaknesses (Cryptanalysis of PDF Security Handler, 2020).
  • XSS via filename. All filenames rendered as text; metadata strings written as PDFHexString.fromText.
  • Memory pressure on big files. Operations over 20 MB dispatched to a Comlink Web Worker with zero-copy transferables.

We do not defend against

  • Browser-extension keyloggers / screen recorders. An extension with activeTab can read everything we read. Outside our perimeter.
  • OS-level compromise. If the host machine is rooted, the attacker has the file already.
  • Network operator MitM with cooperating CA. A nation-state attacker with a CA can do bad things to any HTTPS site.
  • CPU side channels. Spectre/Meltdown-class — we rely on browser mitigations.

Content Security Policy

Reader's CSP lives in reader-web/public/_headers and is enforced by Cloudflare Pages on every response. Highlights:
  • default-src 'self' — every fetch defaults to first-party.
  • script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' + analytics origins. The 'unsafe-inline' relaxation is required by Astro hydration; chosen in Phase 1 because hashing inlines hurt LCP by ~600 ms on mobile. Tracked for revisit.
  • connect-src restricted to 'self' + analytics origins + Tesseract CDN. No endpoint accepts document data.
  • frame-ancestors 'none' — clickjacking impossible.
  • upgrade-insecure-requests — anything HTTP forced to HTTPS.

Dependency policy & CVE response

Three engines are load-bearing for security: PDF.js, pdf-lib and Tesseract.js. Our policy:
  • Pinned ranges in package.json with tilde, patch releases via Dependabot.
  • Critical-CVE patch SLA: 72 h from public disclosure when the CVE affects code paths we exercise. PDF.js CVE-2024-4367 was the calibrating event.
  • Tesseract WASM and trained data are self-hosted at /tesseract/ to avoid third-party CDN dependency for security-critical bytes.
  • Verify the privacy claim in your browser — DevTools → Network tab while running any tool. Zero document requests leave your machine. See the 30-second verification recipe.

Quality gates in CI

  • Engine purity check — enforces that pdf-engine/core/** never touches DOM, fetch, window, navigator.
  • Unit tests — Vitest covers every pure function at 97% lines / 100% functions.
  • End-to-end tests — Playwright drives real browser flows for merge/compress/split/PDF-A on every PR.
  • Accessibility — axe-core asserts zero WCAG 2.1 / 2.2 AA violations on 8 critical routes (incl. RTL).
  • Lighthouse CI — performance ≥95, a11y =100, SEO =100 on every push to main.

Reporting a vulnerability

If you find a security issue, please disclose it responsibly via email so users aren't put at risk by a public report:

[email protected] — include a description and minimal reproduction steps.

We aim to acknowledge within 48 h and patch critical issues within 72 h. Credit will be given in the changelog unless you prefer otherwise.

Related

reader.me is an idea by David Carrero, built at Color Vivo Internet S.L.