Repository navigation
build(worker): pypdf floor tracks only advisories; pin lags two SEC fixes #187
Description
Summary
The Worker's pypdf pin is set by the advisory stream, and the advisory stream
cannot see the two newest upstream security fixes.
worker/pyproject.toml:31 is pypdf>=4.0 and worker/uv.lock pins 6.15.0,
so that is what a Worker build installs today. Three public advisories are
first-patched at 6.16.0 / 6.16.1 and #156 (Dependabot) moves the lock there.
Upstream has since shipped two more releases whose only Security (SEC) entry
is the same class of fix — bound an unbounded parser input — and neither has
a GHSA allocated:
- 6.17.0 (2026-09-04) —
Limit value for Roman numerals (#4047) - 6.18.0 (2026-09-07) —
Limit allowed length of indirect object tokens (#4055)
Measured just now against the advisory database:
GET /advisories?affects=pypdf returns 43 entries, all type: reviewed,
and the newest first_patched_version anywhere in that set is 6.16.1. The
most recently published one (GHSA-fc8x-2rww-xw9m, 2026-09-02) already patches
at 6.15.0.
So an advisory-driven floor — >=4.0 today, >=6.16.1 after #156 — covers
everything the database knows about, and no alert will ever schedule 6.17.0 or
6.18.0. The surface those two harden is live here and it is an untrusted one:
worker/mem_worker/processors/pdf.py:79 runs PdfReader(BytesIO(file.data)) on
user-uploaded files.
All references are main @ 2986fe38175f54d99f15dd38a498708c6ecd88cd.
Reproduction (deterministic; run by the author of this issue)
With the tool CI pins — uv 0.9.27 (ci.yml:185-187), sanitized env, no
mirror/proxy variables — on a tarball of that exact main commit whose tree was
verified equal to commits/2986fe38175f (.commit.tree.sha
f447ca554377e85ba26090d59882b8d4f2b78731, obtained with
git init && git add -A && git write-tree):
uv lockon the untouched tree → 0 diff lines against the committed
worker/uv.lock. The pin is reproducible, so the numbers below are about the
resolve, not about a stale tool.- Edit only the constraint and run
uv lockagain →Resolved 90 packages,
Updated pypdf v6.15.0 -> v6.18.0. uv resolves the newest release
satisfying a floor, not the floor, which is why the manifest text is what
carries the intent. uv lock --lockedon the result →rc=0, souv sync --frozen
(ci.yml:193, withUV_FROZEN: "1"at:170) accepts it.- Name/version pairs across both locks (85 packages each) → exactly one
difference:pypdf 6.15.0 → 6.18.0. The remaining 68 of the 76 changed lock
lines are uv's own marker renormalisation (python_full_version >= '3.15' and sys_platform == 'darwin'moving betweentorch 2.13.0and2.13.0+cpu,
likewisetorchvision 0.28.0/+cpu,python_full_versionnarrowing
dropping offnumpy/scipy/tifffile, and theresolution-markers
list reordering). - Behaviour on the repository's own fixture — the single-page PDF built by
worker/tests/test_processor_logic.py:419, extracted under Python 3.11 (the
version the Worker leg uses,ci.yml:182) at 6.15.0 / 6.16.1 / 6.18.0:
identicalpage_count, identicalsha256(extracted_text)
(46e7c072b2684841…, 214 chars),"1800 RMB" in texttrue on all three, and
the malformed input raising the samePdfStreamErroron all three. - The one behavioural change 6.18.0 makes (
DEP: Rework configuration value handling (#4044)) has no consumer here:grepover the whole tree for
overwrite_configuration/apply_configuration/
disable_legacy_handling/pypdf.constants→ 0 hits;pypdfappears in
worker/pyproject.toml,worker/mem_worker/processors/pdf.py, and
scripts/seed_demo_data.shonly.
Per docs/maintainers/triage.md, E3 is for reproduction by someone other than
the author, so this issue carries evidence:e2-source even though the steps
above were executed. A non-author re-running them is the fastest way to raise
it.
Acceptance criteria
- AC-001
worker/pyproject.tomldeclares a floor at or above the newest
upstream release carrying aSecurity (SEC)entry —>=6.18.0as of today —
so a from-scratch resolve cannot land below it. - AC-002
worker/uv.lockpins that version anduv lock --lockedpasses
with the CI-pinned uv, i.e. the Worker leg'suv sync --frozenaccepts it. - AC-003 No other package's locked version moves. Measurable: the
name/version pair set of both locks differs only forpypdf. - AC-004 The Worker test leg passes on the exact head, which is what
executes the 6.18.0 extraction path (test_processor_logic.py:457). - AC-005 The dist hashes in the new lock match what the registry publishes
for 6.18.0 (sdistae58b7d93c22c169ffb02c3b06321c45c4f223b4916536568adb57d789d95d01,
wheel05b762b77bcb9dcb4a7c91fcf5dded585b25bee7269ab3d3001d7c55fa1b324b).
Non-goals
- Not a vulnerability report. The three patched-in-6.16 advisories are public
already and build(deps): bump pypdf from 6.15.0 to 6.16.1 in /worker #156 is the record for them; the two 6.17.0 / 6.18.0 entries have
no CVE and no GHSA, so there is nothing to disclose privately. - Not a change to build(deps): bump pypdf from 6.15.0 to 6.16.1 in /worker #156. Merging this makes build(deps): bump pypdf from 6.15.0 to 6.16.1 in /worker #156's lock effect moot (its diff is
a strict subset), but deciding that is the release cadence owner's call, not
this issue's. - Not the
darwin+ Python ≥ 3.15 unsatisfiableclipextra marker geometry.
It is pre-existing on both sides of any pypdf bump and has no CI coverage —
that belongs on its own issue if anyone wants it.
Activity
PeterGuy326 commentedon Sep 10, 2026
Triage: readiness gate passed — applying
status:readyChecked against every bullet of the readiness gate in
docs/maintainers/triage.md:SECreleases no advisory will ever schedule. Non-goal 2 states the separation explicitly.SECURITY.md.type:maintenance,area:worker,dependenciesare right.evidence:e2-sourceis the strongest honest level and is being left as-is — the author executed the seven steps, but the policy reserves E3 for reproduction by someone other than the author. Confidence is not a substitute for that.type:maintenance; the policy does not normally apply severity to maintenance.uvresolves to the newest release satisfying a floor rather than to the floor, so the lockfile alone does not carry the intent.uv lock --lockedexit code, a name/version pair-set diff, the Worker test leg on the exact head, and two literal SHA-256 dist hashes.darwin+ Python >= 3.15clipextra marker geometry, which is correctly excluded rather than silently absorbed.DEP: Rework configuration value handling (#4044)— 6.18.0's only behavioural change — is traced to zero consumers in this tree.0.9.27atci.yml:185-187), a sanitized env, and a tree equality proof againstmain @ 2986fe38.Nothing here needed
status:waiting-feedback; no bullet is missing information.Note on the attack surface, since it is what makes this more than housekeeping:
worker/mem_worker/processors/pdf.py:79runsPdfReader(BytesIO(file.data))on user-uploaded files, and both 6.17.0 (Limit value for Roman numerals) and 6.18.0 (Limit allowed length of indirect object tokens) bound an unbounded parser input on exactly that path.Effect on #188
#188 named this label as the only thing standing between it and being review-ready, and asked that undrafting follow the label rather than precede it. That ordering is now satisfied, so #188 is being marked ready for review.
Leaving #188's
Refs #187as written rather than rewriting it toCloses— the author chose that keyword and it is their PR body. The consequence is only that this issue will not auto-close on merge, so it will be closed by hand against AC-001…AC-005 once #188 lands.Evidence stays at E2. Raising it to E3 needs a non-author to re-run the seven steps, and to E4 needs a non-author to verify AC-001…AC-005 on the delivered outcome.
build(worker): raise the pypdf floor and pin to 6.18.0 (#188)
PeterGuy326 commentedon Sep 10, 2026
Closing as completed. Delivered by #188, merged as
55d09b203ba9(ordinary squash, no admin bypass).Every acceptance criterion re-checked against
mainafter the merge rather than taken from the PR body:mainworker/pyproject.tomldeclares a floor at or above the newest upstreamSECreleaseworker/pyproject.toml:31="pypdf>=6.18.0". The floor is in the manifest, not only the lock, so a from-scratch resolve cannot land below it.worker/uv.lockpins that version anduv lock --lockedpasses with the CI-pinned uvworker/uv.lock:1646-1648=name = "pypdf"/version = "6.18.0"/ registrypypi.org/simple. The Worker leg runsuv sync --frozenwithUV_FROZEN: "1", so a self-inconsistent lock would have failed it.Workercheck conclusionsuccesson merge SHA55d09b203ba9. This is the leg that actually executes the 6.18.0 extraction path attest_processor_logic.py:457.worker/uv.lock:1649sdistsha256:ae58b7d93c22c169ffb02c3b06321c45c4f223b4916536568adb57d789d95d01and:1651wheelsha256:05b762b77bcb9dcb4a7c91fcf5dded585b25bee7269ab3d3001d7c55fa1b324b— both byte-identical to the values recorded in this issue.Outcome on the alert stream, which is the thing this issue existed to get right:
fixedat 2026-09-10T16:25:49-50Z. Zeropypdfalerts remain open in this repository.Limit value for Roman numerals) and 6.18.0 (Limit allowed length of indirect object tokens), both hardening the unbounded-parser-input surface atworker/mem_worker/processors/pdf.py:79against user-uploaded files.#188 used
Refsrather thanCloses, so this did not auto-close; that is why it is being closed by hand.On the evidence label: this stays at
evidence:e2-sourceand is not being raised. AC-001, AC-002, AC-004 and AC-005 above were verified by a non-author against the merged default branch, which is verification of the delivered outcome, but the seven reproduction steps in this issue were never independently re-run by a non-author, anddocs/maintainers/triage.mdreservese3-reproducedfor exactly that. Closing a delivered issue does not retroactively manufacture reproduction evidence.Non-goal 3 remains unaddressed and correctly so: the
darwin+ Python >= 3.15 unsatisfiableclipextra marker geometry is pre-existing on both sides of this bump and has no CI coverage. It needs its own issue if anyone wants it.