Leaf Reader is local-first. Its helper listens only on 127.0.0.1, and every library, book, cover, progress, and settings API route requires a random per-process reader session. The reader receives a one-time launch capability in its URL fragment; the first authenticated bootstrap immediately invalidates it, rotates to a new session secret, and stores that secret only in a strict same-site, HTTP-only cookie and the private runtime-state file. Browser-facing library records and bootstrap responses omit source paths, cache paths, and tokens. State and cache directories are private to the user.
Book metadata is always rendered as plain text in the Omarchy panel. Before Python's ZIP parser allocates for an EPUB, Leaf Reader reads its bounded end record and rejects ZIP64, multi-disk, oversized, or inconsistent central directories. It then caps archive members, duplicate/unsafe names, individual and total expanded sizes, and encrypted content before the book reaches the browser. EPUB container XML is capped at 64 KiB and package XML at 2 MiB from ZIP descriptors before decompression, with the actual reads independently capped; sidecar package metadata uses the same 2 MiB ceiling. Raw cover files never reach the long-running shell: Leaf Reader accepts only bounded JPEG/PNG inputs, rejects sources above 8 MiB, 4096 pixels on either side, or 12 megapixels, then decodes the first frame in a short-lived ImageMagick process with explicit memory, mapped-memory, disk, thread, time, width, height, and area limits. Only a verified PNG no larger than 512×768 and 3 MiB is cached for the panel, whose QML Image items also set explicit sourceSize bounds.
Fixed writable JSON inputs have per-file byte ceilings and are normalized into bounded fields and record counts before use. Library scans reject symlinked books and sidecars, enforce file/record/time ceilings, and send a reduced payload to the long-running shell. The loopback server bounds request bodies, worker concurrency, and streamed book sizes. Conversion is serialized, diagnostics are bounded, and each output is validated from an unpredictable private staging path before being cached. The service stops its exact reader process when the window closes, the plugin unloads, or the plugin checkout is removed or changed; ebook-tool stop is also available for recovery.
Please report a vulnerability through GitHub's private security-advisory form for this repository. Do not include private ebook files, API keys, or reading-history data in a public issue.