A major release because check's machine output and exit code changed (both
marked BREAKING below). Everything else is fixes, security and upkeep.
Changed
- Upgraded the validator engine to
epubveri0.17.5 (from 0.4.4) — on both the
CLI and the browser.epublift checkand the web app's client-side Validate
mode were re-pointed together, and on the 474-book test shelf they report
identical findings — status, counts, and every finding's ID, severity, file,
line, column and message — for every book. Against epubcheck's own test suite
epubveri now catches 99.7% of the cases that should be flagged with the same
message ID and raises no false alarm on the valid ones. On the shelf, 115
books change verdict compared with 0.5.9, and epubcheck 5.3.0 was run on each
of them as referee: 86 books that wrongly failed now pass (85 confirmed by
epubcheck), 29 that wrongly passed now fail (all 29 confirmed), and the one
disagreement is a documented, deliberate epubveri departure — a stray;in a
CSS declaration list, which the CSS specification allows and epubcheck's
older parser rejects. Among the fixes, many from MobileRead forum reports,
mostly EPUB 2 false positives: ,é,©and the other standard HTML named entities no
longer raise a spurious fatalRSC-016in EPUB 2 content. (The most
painful of the set: is everywhere, especially in French books.)- An EPUB 2 package with several
dc:dateelements — the creation/modification
pair Sigil and Calibre write — is no longer flaggedRSC-005, and a legacy
OpenType font no longer drawsOPF-090. Both are EPUB 3-only concepts. - A valid
<meta http-equiv="Content-Type">in EPUB 2 content is no longer
flagged: the exact-value rule is HTML5's, so it applies to EPUB 3 only. - Ordinary
META-INF/metadata (Apple'scom.apple.ibooks.display-options.xml,
calibre's bookmark files) no longer drawsPKG-025. - A nav
indexlandmark no longer draws a falseRSC-005. - Genuinely malformed content documents (e.g. an unclosed
<p>) are no longer
silently accepted — they now fail withRSC-016at the exact spot. RSC-005now names what is wrong ("element "p" is not allowed here")
instead of a blanket schema message, reports every offending node rather than
only the first, andRSC-011points at the source<a>rather than the OPF
root.- The engine also stops pulling second copies of
zipandroxmltreeinto the
build: it now shares the versions epublift already uses. - Findings now include epubcheck's
infoandusagelevels, and the JSON
summaryalways carries all five counters, zeros included.
- BREAKING:
check --jsonnow emits the shared veripublica machine envelope
(FORMATS.md)
instead of a bespoke array, so epublift's validation output can be consumed by
anything that already reads epubveri's or epubsana's — no per-tool parsing. One
object per run:{tool, tool_version, convention, status, inputs[]}. Findings
gainrule(a stable semantic sub-code such asopf.spine.duplicate_itemref,
distinguishing the many unrelated violations one ID likeRSC-005can mean),
data.params, anddata.element_path— an XPath-style path to the offending
node, resolvable with the accompanyingnamespacesmap. See
docs/validate.md for the shape and a migration note. - BREAKING: an input that cannot be read now exits
2, not1. Exit1
means every input was graded and at least one book is invalid; exit2means
at least one input could not be processed at all (a missing file, an unreadable
one) — so a typo in a path is no longer indistinguishable from a bad book. Both
are non-zero, so an ordinarycheck … || exit 1gate is unaffected. Every
input is still processed and reported even when an earlier one fails.
Fixed
- A fatally broken book no longer reports
(0 errors, 0 warnings). epubveri
counts fatal-severity findings apart from errors, so a book stopped dead by a
corrupt container — the whole reason it failed — summarised as a failure that
named nothing. Both surfaces now name them only when there are any: the CLI
readsFAIL book.epub (1 fatal, 0 errors, 0 warnings), and the web report's
count line matches. - PDF import: text now comes from whichever extractor reads the page better.
Newer lopdf reads pages itsextract_textused to give up on, but on some
fonts it runs the words together ("Atyearend2022,Berkshirewas…") and on an
unreadable composite font it returns the raw glyph codes as letters
("DXWKRUV" for "authors"). Our own extractor, which places spaces from the
real glyph widths, used to take over only when lopdf found nothing. Now, on a
born-digital page, both run and the text with fewer run-together words wins
(lopdf's on a tie, as before); on a scanned page with an OCR text layer lopdf
still reads first. Measured on 126 real PDFs against the previous release: 13
more documents import (74 vs 61), 27% more text comes through, and
run-together words drop from 0.27% to 0.21%; the reference books intests/
import byte-identically.
Security
- One crafted upload could take the whole web server down; now it is declined.
epublift parses a book'scontainer.xml, package document and NCX with
roxmltree, which recurses per nesting level: an EPUB whose package document
nested 20,000 elements (1.2 MB) overflowed the stack, and in Rust that aborts
the process — theepublift-webcontainer exited, taking every conversion in
flight with it. Found by epubveri, reproduced against the server. Every such
parse (eight sites, in the CLI and the web service alike) now runs epubveri's
xmlguard::checkfirst, which declines nesting deeper than 256 levels, more
than 256 attributes on one element (quadratic to parse), more than a million
elements, or runaway entity expansion — with an ordinary error. No real book
comes near: the deepest document on the 474-book test shelf nests 24 levels,
and convert, kepub,meta showand repair produce identical output on all 474
before and after.epubveriis therefore always linked now (the validator
itself stays behind thevalidatefeature); the binaries did not grow. - Dependencies refreshed to their latest compatible releases (
cargo update,
135 lockfile changes). This clears two advisories —rustls0.23.40
(RUSTSEC-2026-0285, TLS 1.3 handshake messages accepted across encryption
levels) andcrossbeam-epoch0.9.18 (RUSTSEC-2026-0204) — and theanyhow
(unsound) andspin(yanked) warnings.archmage/magetypes/archmage-macrosare held at 0.9.26: 0.9.27+ no longer
compilesjxl-encoder-simd0.3.0 (the JPEG XL encoder) on ARM64. quick-xml0.40 → 0.41, clearing two advisories on the parser that reads
every uploaded book's package, metadata and content: RUSTSEC-2026-0194
(quadratic time on a start tag with many attributes) and RUSTSEC-2026-0195
(unbounded namespace-declaration allocation). No code change was needed;
convert, kepub,meta showand repair produce identical output on all 474
books of the test shelf. 0.42 is a separate decision: it rewrites the API
around&strand rejects non-UTF-8 input outright.lopdf0.34 → 0.45 (PDF import), clearing RUSTSEC-2026-0187 (stack
overflow on deeply nested PDF objects) and picking up fixes for four crashes
on crafted PDFs and a bound on object-graph recursion.- HTTPS client: the pure-Rust crypto provider now comes from the maintained
code line.rustls-rustcrypto's only release (0.0.2-alpha, 2024) pulls
rustls-webpki0.102 with four advisories (RUSTSEC-2026-0049, -0098, -0099,
-0104); its main branch no longer does, so it is now pinned to a reviewed
commit until a release is cut. The HTTPS client stays pure Rust — no C
toolchain — and still runs on every CPU we ship for, Raspberry Pi 4
included. Handshakes to every host we talk to (Open Library, Google Books,
Mistral, the OCR model store) measure the same as before. One advisory
remains accepted, with its reason in.cargo/audit.toml: thersaMarvin
side channel concerns RSA private keys, and a TLS client verifying server
signatures holds none. - Dependencies are now audited continuously. A new
Auditworkflow checks
Cargo.lockagainst the RustSec advisory database whenever the dependencies
change and every Monday; the few accepted advisories are listed, each with
its reason, in.cargo/audit.toml. Dependabot now proposes weekly updates
for the Rust crates, the GitHub Actions and the Docker base image. - CI now builds and tests on ARM64 as well as x86-64, since the SIMD image
codecs compile different code per architecture. ASECURITY.mdnow says how
to report a vulnerability privately and what counts as one, including for
the hosted web service.
Full Changelog: cli-v1.13.0...cli-v2.0.0