Skip to content

fix(deps): this week's security advisories: pillow 13, certifi and rustls-webpki; Python 3.11 or newer → 3.148.7-alpha - #968

Merged
BBYU26 merged 1 commit into
mainfrom
fix/sidecar-pillow-13
Oct 22, 2026
Merged

BBYU26 merged 1 commit into
mainfrom
fix/sidecar-pillow-13

Conversation

@BBYU26

@BBYU26 BBYU26 commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

This week's security advisories, in one place. The required dependencies check has been red on main and on every open PR since 17-10. This PR clears all of it. 3.148.7-alpha, based on main. The calendar stack (#959 and up) will be rebased on top of it, and #959 drops its own copy of the certificate fix.

What it fixes

  • Pillow 12.3.0 → 13.0.0 (the image library the document helper uses for photos).
    • pip-audit reports CVE-2026-112395, -112397, -112400, -112404, -112895, -112896, -112897 and -112899.
    • GitHub lists about 40 more advisories, published from 17-10. Two are critical, among them heap overflows in the FlashPix and JPEG decoders.
    • Every one is fixed only in 13.0.0. There is no 12.x after 12.3.0.
  • certifi → 2026.10.19 (PYSEC-2026-4320 / GHSA-6q48-6c7w-9gw2), plus rustls-webpki 0.103.16 and webpki-roots / webpki-root-certs 1.0.10 (RUSTSEC-2026-0381/0382/0383). These remove trust in a certificate authority whose signing keys were compromised. Nothing else moves in Cargo.lock.

Two decisions for you

  1. fastembed's cap is overridden. fastembed 0.9.0, the latest release, still declares pillow<13 on every Python, although its code runs on 13. The upstream fix, chore(deps): allow pillow 13 qdrant/fastembed#830, is open and unmerged. So:
    • Pillow 13 is pinned through a new sidecar/requirements-override.txt, passed to uv pip compile --override for the base lock and stamped in its header. just requirements-lock checks the stamp.
    • pip's own resolver refuses a lock resolved past the cap, so every lock install now adds --no-deps beside --require-hashes. That covers the base and optional-component installs, pr.yml's locked-deps step and release.yml's python-smoke. Every line is still pinned and hash-checked, and the lock is complete because uv resolved it.
    • just pip-audit audits the hashed locks with --disable-pip instead of installing them. On the old locks it still reports all eight Pillow advisories.
    • Remove the override once a fastembed release allows Pillow 13.
  2. Python 3.11 is the new minimum (3.10 reaches end of life this month). Pillow 13 needs it.
    • Windows and Linux bundle Python 3.12.
    • On a Mac, PM uses your Python when it is new enough, and otherwise downloads its own 3.12.
    • So the change reaches only a venv an older PM built on 3.10, which is rebuilt once on the next launch, and source builds on a machine with nothing newer than 3.10.
    • The setup messages now take the version from MIN_PYTHON. The locks lose the 13 package versions that existed only for Python below 3.11.

The alternative to both was to ignore the advisories until fastembed releases. I didn't take it: the JPEG and TIFF decoder bugs are reachable from any photo you drop in.

Hardening, whatever the Pillow version

  • A photo is opened only as a photo format (JPEG, PNG, WebP, and HEIC once the OCR component's pi-heif is there), the one its extension names tried first.
    • A TIFF, FlashPix, EPS or any other format renamed .jpg no longer reaches its decoder.
    • A real photo with the wrong ending still reads, such as a PNG saved as .jpg or a JPEG saved as .heic.
  • Text recognition only ever gets pixels PM decoded itself. The fallback that handed rapidocr the raw path, which it opens unrestricted, is gone.
  • A damaged or half-downloaded photo is now blamed on the file, not on text recognition. Its row says so, and the date and place from its header are kept.

Reviews

  • A researcher and a builder each established, independently, that this couldn't be a plain bump: fastembed's cap and Pillow's Python floor block it. A third agent checked both claims against PyPI.
  • Review 1 (two lenses):
    • the install and lock path was clean;
    • the hardening kept one medium (a PNG saved as .jpg was refused) and two lows (a cut-off photo blamed on text recognition; What's New said every installer bundles Python).
  • Review 2 kept two lows: a JPEG saved as .heic without pi-heif was refused, and the photo note overstated what was lost. Both are fixed. The HEIC fix was mutation-checked.
  • Every lock change was compared with PyPI's hashes, and every licence was re-entered by hand after reading the wheel:
    • Pillow: MIT-CMU plus permissive bundled natives, byte-identical to 12.3.0's on Linux and macOS;
    • certifi: MPL-2.0, unchanged.

Checks

The full just check passes end to end, deny and pip-audit included. Fresh Python 3.11, 3.12 and 3.14 venvs install the base lock with the app's flags plus --no-deps and pass the sidecar tests with fastembed required. In those venvs fastembed embeds with the default model, and markitdown converts a PNG and a PDF. The OCR lock installs on top, and with pi-heif installed a real HEIC opens.

Live test for you

On the next launch of a dev or release build, PM should rebuild its document helper's environment once, because the locks changed. Then:

  • drop a JPEG, a PNG, an iPhone HEIC (with photo text recognition on) and a PDF;
  • check that each reads as before;
  • check that a TIFF renamed .jpg shows "PM could not read this image…".

🤖 Generated with Claude Code

…stls-webpki; Python 3.11 or newer → 3.148.7-alpha

pip-audit and cargo-deny, and with them the required `dependencies`
check, have been red on main and on every PR since this week's
advisories. This takes all of them in one place.

pillow 12.3.0 carries CVE-2026-112895, -112400, -112397, -112896,
-112897, -112395, -112404 and -112899, and GitHub lists about forty more
advisories published from 2026-10-17, two of them critical (among them
heap overflows in the FlashPix and JPEG decoders). All are fixed only in
pillow 13.0.0; there is no 12.x after 12.3.0.

certifi before 2026.10.19 trusts the emSign ECC Root CA - G3, under
which eight eMudhra intermediates have compromised keys (PYSEC-2026-4320,
GHSA-6q48-6c7w-9gw2). The same distrust reaches Rust as
RUSTSEC-2026-0381 (webpki-roots), RUSTSEC-2026-0382 (rustls-webpki) and
RUSTSEC-2026-0383 (webpki-root-certs), all CVE-2026-111943.
`cargo update -p` moves rustls-webpki 0.103.15 to 0.103.16 and
webpki-roots and webpki-root-certs 1.0.7 to 1.0.10, and nothing else in
Cargo.lock. In the sidecar locks certifi moves to 2026.10.19 with every
other pin held. Its licence was re-entered by hand after reading the
wheel: MPL-2.0, with a LICENSE byte-identical to 2026.7.22's.

fastembed 0.9.0, the latest release, still declares pillow<13 on every
Python although its code runs on 13 (qdrant/fastembed#830 lifts the cap;
open and unmerged). So pillow==13.0.0 also goes in a new
sidecar/requirements-override.txt, which `just lock-regen` passes to
`uv pip compile --override` for the base lock only and stamps in its
header. check-requirements-lock verifies the stamp, and that the file
only overrides packages requirements.txt pins at the same version.
Remove the override line once a fastembed release allows pillow 13.

Because the lock is resolved past that cap, pip's own resolver refuses
it, so every install of a lock now passes --no-deps beside
--require-hashes: the base and optional-component installs in
sidecar.rs, pr.yml's locked-dependency test step and release.yml's
python-smoke. Every line is still pinned and hash-checked, and the lock
is complete because uv resolved it. `just pip-audit` now audits the
hashed locks with --disable-pip instead of installing them; on the old
locks it still reports all eight pillow advisories.

pillow 13 needs Python 3.11, so MIN_PYTHON goes from 3.10 to 3.11 (3.10
reaches end of life this month). The Windows and Linux installers bundle
3.12, and on a Mac PM uses the machine's Python when it is new enough
and downloads its own 3.12 otherwise. So the new floor reaches only a
venv an older PM built on a 3.10, which is rebuilt once on the next
launch, and from-source development on a machine with nothing newer
than 3.10. The setup messages now take the version from MIN_PYTHON. The
locks lose the 13 package versions that existed only for Python below
3.11, and tzdata's 3.10 marker. pillow 13.0.0's wheel LICENSE is
MIT-CMU plus the permissive licences of its bundled natives,
byte-identical to 12.3.0's on Linux and macOS.

Hardening, on any pillow: the sidecar opens a photo only as a photo
format (JPEG, PNG, WebP, and HEIF once pi-heif has registered it), the
one its extension names tried first. A TIFF, FlashPix, EPS or any other
format renamed .jpg no longer reaches its decoder, while a PNG or WebP
saved as .jpg, or a JPEG saved as .png, still reads. Without pi-heif a
.heic is tried as the other three, so a real HEIC is refused and a JPEG
saved as .heic still reads; any other extension is refused.
The fallback that handed rapidocr the raw path when PM's own open
failed is gone, since rapidocr opens a path with no such restriction.
With OCR on, the pixels are decoded before the engine loads, so a file
cut off halfway comes back with no size (its EXIF kept) and its row
blames the file, not text recognition; with OCR off nothing decodes the
pixels, as before. The photo's file is closed once it has been read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 15:14
@BBYU26 BBYU26 added bug Something isn't working dependencies Pull requests that update a dependency file rust Pull requests that update rust code python Pull requests that update python code labels Oct 21, 2026
@BBYU26 BBYU26 self-assigned this Oct 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

BBYU26 added a commit that referenced this pull request Oct 21, 2026
Renumbered above #968's 3.148.7-alpha now that this PR sits on it; the dependency commit it carried is in #968.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BBYU26 added a commit that referenced this pull request Oct 21, 2026
Renumbered above #959's new 3.148.8-alpha now that the stack sits on #968.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BBYU26
BBYU26 added this pull request to stack #975 October 22, 2026 02:51
@BBYU26
BBYU26 merged commit 9c77b31 into main Oct 22, 2026
17 checks passed
@BBYU26
BBYU26 deleted the fix/sidecar-pillow-13 branch October 22, 2026 03:23
BBYU26 added a commit that referenced this pull request Oct 22, 2026
…an earlier split still overlaps → 3.148.8-alpha (C9a-3b-2 of #884) (#959)

* chore(calendar): remember a split while it runs, and refuse a series an earlier split still overlaps → 3.148.7-alpha (C9a-3b-2 of #884)

A "this and following" change is now recorded in the settings store before
its first write (calendar_split_journal: Google's ids, the plan digest, the
original's rule before and after the cut, its start) and forgotten once it
lands or settles. A record no held split stands for (PM restarted) is read
against Google's copies by meaning: a proven duplicate refuses what it reaches
but lets "All events" delete on the new part through; a landed or changed one
is said once and forgotten; one PM can't read refuses until it can.

Every series read now also lists the parts PM split off it
(privateExtendedProperty=pmSplitOf); one that still overlaps the original
refuses every scope past "this event" (scope-M4), as does a listing PM
couldn't read.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): after a restart PM offers no delete; twice needs the original's occurrences; one claim doesn't hide a record

Review round 1 of C9a-3b-2, and the decided Q4 (DECISIONS 18-10): a proven
duplicate only refuses, its words pointing to Google Calendar; no "All
events" delete is let through. A Twice reading also needs the original
still repeating from the split on, read off its occurrences as the backstop
reads them (the planned series' parts, or a window listing of the original);
otherwise it is said once as changed. A record is skipped as a running
change's only while both its series are claimed.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): read the original whole before calling a record twice; mark running splits explicitly

Review round 2 of C9a-3b-2. Reading the original from its new part now lists
its exceptions and cross-checks them as gather does, so an occurrence changed
on its own and left on it never counts as the rule repeating. A record is a
running change's only while that send or settle has marked it in
CalendarEditState.splitting; two unrelated writes holding both series no
longer hide it.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): a split is marked running before its record exists; twice needs whole exceptions on both sides

Review round 3 of C9a-3b-2. The running mark now goes on before the send
writes its record and before a settle takes its split out of the held ones,
so no look meets a record with neither. Read from the original itself, a
twice reading also needs its exceptions listed whole, as from the new part.
Tests pin both marks and their removal, and both unread cases.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): the overlap backstop compares instants, not days

Review round 4 of C9a-3b-2: an extra date earlier on the split day stays on a
cut original, and comparing days read it as overlapping the new part,
refusing every change past this occurrence on a split that finished cleanly.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): the overlap backstop counts only the rule's own occurrences, by day

Review round 5 of C9a-3b-2: comparing instants missed an uncut original's
occurrence on the split day when the new part was retimed later that day,
and still read an extra date as overlapping when the part moved earlier.
Back to days, with the original's extra dates (RDATE) left out beside its
stored exceptions; parts_overlap takes the master.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): the overlap backstop measures from the occurrence a part was split at

Review round 6 of C9a-3b-2: measured from the new part's start, a part moved
to a later day hid an uncut original whose last occurrence in the window was
the split occurrence itself (a yearly event, the last of a COUNT). A new part
now carries a private pmSplitAt note (with PM's other notes, ignored in every
comparison), and an original overlaps while it has a live occurrence at or
after that point. A cut ends it before, wherever the part moved; an extra date
kept earlier that day is before it. Parts without the note fall back to their
start. The RDATE exclusion is no longer needed and goes.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: 3.148.8-alpha, after the advisories PR #968

Renumbered above #968's 3.148.7-alpha now that this PR sits on it; the dependency commit it carried is in #968.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
BBYU26 added a commit that referenced this pull request Oct 22, 2026
…3.148.9-alpha (C9b-1a of #884) (#960)

* chore(calendar): the repeating-event dialog's parts, not yet shown → 3.148.8-alpha (C9b-1a of #884)

The "which occurrences?" dialog for a repeating event is built and
tested but not mounted: the pure rules and words (saveChoices.ts), the
SaveChoicesDialog, the store's choices, scoped writes and settling in
useEventWrites, and settle buttons on WriteNotices. Repeating events
stay read-only and release builds still refuse scoped writes.

Backend: a one-off Google has turned into a series is read-only with
its own reason (BecameRepeating) on every path; a delete's choices that
meet a changed occurrence land Google's copy; every orphan's words are
said; a settle that leaves the change held answers PartialSplit with a
note, so its buttons stay; one release gate for a missing choice,
pinned by a source test; no jargon in what rows show.

The one visible change: the became-repeating reason's wording.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): a save always names its event; refusals in the dialog are announced; notices wrap only with settle buttons

Review round 1 of C9b-1a: saveEdit's title is required, so a partial
notice can't read "(no title)"; the choices dialog's refusal, which arrives
after it opened, is a live region whatever its tone; only a notice with settle
buttons wraps them under its words, so other notices look as before; a settle
with no answer keeps its notice's own offers (now tested); and the What's New
line no longer says the event was made in PM.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(calendar): a settle Google turned away says the change was put back

Review round 2 of C9b-1a: PM's own claim now reaches a settle as a held change
with a note, so a plain busy answer only comes from Google refusing Finish's
next step, after which PM took the new part back. Its words said PM was still
changing the event; they now say it was put back as it was.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: 3.148.9-alpha, after the advisories PR #968

Renumbered above #959's new 3.148.8-alpha now that the stack sits on #968.

Part of #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dependencies Pull requests that update a dependency file python Pull requests that update python code rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants