Skip to content

fix(deps): remediate pillow CVEs by raising the floor to >=13.0.0 - #180

Merged
Aparnap2 merged 1 commit into
mainfrom
fix/pillow-cve-remediation
Oct 21, 2026
Merged

Aparnap2 merged 1 commit into
mainfrom
fix/pillow-cve-remediation

Conversation

@Aparnap2

Copy link
Copy Markdown
Owner

What changed

Two files, dependency-only:

File Change
pyproject.toml pillow>=12.3.0 → pillow>=13.0.0 (1 line)
uv.lock pillow 12.3.0 → 13.0.0

Base main@254c4b92dddf59a758a133c1edbf7618dcc011b2 · commit b0eeff2 · branch fix/pillow-cve-remediation.

No parser behaviour, fixture, business logic, CI workflow, or other dependency changed.

The advisories

pip-audit on the committed lock reported 8 known vulnerabilities in 1 package — all pillow 12.3.0, all fixed in 13.0.0:

CVE-2026-112895  CVE-2026-112400  CVE-2026-112397  CVE-2026-112896
CVE-2026-112897  CVE-2026-112395  CVE-2026-112404  CVE-2026-112899

The job is advisory rather than required, but it is red on main and on every PR, which erodes the signal of a green run.

Why both halves were required

security.yml audits the lock (uv export --frozen), so re-locking alone would turn the scanner green while the declared minimum stayed known-vulnerable for anyone resolving from pyproject.toml without the lock — and would let a future uv lock --upgrade (or a Dependabot PR touching another package) re-select 12.3.0. The floor therefore moves too.

Verification

Dependency graph — pillow 12.3.0 → 13.0.0 is the only version change in the entire lock; no package added, none removed, every other resolved version identical. uv pip check: All installed packages are compatible (29 packages).

Advisory — uvx pip-audit -r <new export> → No known vulnerabilities found, exit code 0.

Compatibility — requires-python = ">=3.12"; Pillow 13 requires >=3.11; CI runs Python 3.12. reportlab 5.0.1 requires pillow>=9.0.0 with no upper bound. liteparse 2.14.4 has no dependencies, so it cannot conflict. Resolution succeeded with no constraint backtracking.

Python suite — uv run pytest tests/unit -q → 67 passed, identical to the pre-change baseline I ran first on pillow 12.3.0 (67 passed). All 14 corpus-generator tests pass, including test_render_is_byte_deterministic. ruff check . → All checks passed. mypy packages scripts/ci → Success: no issues found in 11 source files.

Go — go test ./... -count=1 → 41 packages ok, 0 failures against real PostgreSQL (claimops_app, rolsuper=f, rolbypassrls=f, so RLS is genuinely enforced). go test -race -count=1 ./... → clean, no data races. go vet ./..., go build ./... → clean.

The corpus was NOT regenerated — and it does not need to be

git diff -- fixtures/parser_eval/ is empty, and TestVerifyCorpus_RealFixtures passes.

I additionally measured the risk rather than assuming it. A prior audit warned that Pillow 13's HarfBuzz/-O2 text-rasterization change and a textbbox underflow fix could alter the rasterised cases (CASE-039/042/044) and force corpus requalification. I regenerated all 45 cases with the corpus's own seed (29) into a scratch directory under Pillow 13 and compared against the committed manifest:

committed cases : 45
regenerated     : 45
IDENTICAL sha256: 45/45
DIFFERENT       : 0
case_id order identical: True
transforms identical  : True

The committed corpus is byte-reproducible under Pillow 13.0.0. The feared delta does not materialise, so no corpus requalification is triggered and the frozen artefact stays frozen. The scratch output was not committed and the repo fixtures were never touched.

Not run / unavailable

Mockoon contract tests (gate GET /v1/policies/POL-001 returns 404), localgcp GCS/Pub/Sub, the GCW emulator, and the Groq live-LLM matrix are unavailable in this environment and self-skip — they are not passing coverage and are unaffected by this change.

Status

Dependency-only, isolated from PR #179. Not authorized to merge — opened as a draft for review. No branch-protection change, no deployment. Release status NOT QUALIFIED; this clears a supply-chain advisory and nothing else — docs/KNOWN_LIMITATIONS.md is unchanged and no release gate is closed by it.

pip-audit reported 8 advisories against the locked pillow 12.3.0
(CVE-2026-112895, -112400, -112397, -112896, -112897, -112395, -112404,
-112899), all fixed in 13.0.0. The advisory job is advisory rather than
required, but it is red on every PR and on main.

Both halves are required: pip-audit audits the lock via
'uv export --frozen', so re-locking alone would leave the declared
minimum (pillow>=12.3.0) known-vulnerable for anyone resolving from
pyproject.toml without the lock. The floor moves to >=13.0.0 and the
lock is re-pinned for pillow only.

Verified: pillow 12.3.0 -> 13.0.0 is the ONLY version change in the
lock (no package added or removed); 'uv pip check' reports all 29
packages compatible; pip-audit exits 0 with 'No known vulnerabilities
found'; the Python unit suite passes 67/67, unchanged from the
pillow 12.3.0 baseline; ruff and mypy clean; go test ./... and
go test -race ./... green against real PostgreSQL.

The committed parser corpus is NOT regenerated and is byte-reproducible
under pillow 13: regenerating all 45 cases with seed 29 into a scratch
directory reproduces every committed sha256 (45/45 identical, case order
and transforms unchanged), and TestVerifyCorpus_RealFixtures passes.
'git diff -- fixtures/parser_eval/' is empty. No parser behaviour,
fixture, or business logic changed.

Co-Authored-By: opencode <no-reply@opencode.ai>
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 585f5265-66b5-4ff2-b5bd-5c3c3dd6275c
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Aparnap2
Aparnap2 marked this pull request as ready for review October 21, 2026 06:44
@Aparnap2
Aparnap2 merged commit 1a6a944 into main Oct 21, 2026
7 checks passed
Aparnap2 added a commit that referenced this pull request Oct 21, 2026
The rebase onto main@7ad446c (which merged #180 and #179) made three
statements in this change false as written:

- The ADR, the decision-log entry and KL-18 all said the cross-language
  fixture 'contains nine cases'. After #179 it contains nineteen. None
  of the nineteen covers EXPIRED, so the substantive claim is unchanged;
  only the count was stale.
- The ADR's Base line still named main@254c4b9; it is now written against
  main@7ad446c.
- The Context said 'no document in this repository ever ratified it'.
  This ADR ratifies it, so the claim needs its temporal qualifier. The
  statemachine.go reference is likewise now stated as 'before this ADR',
  since this change corrects that header.

Also fixes KL-18's position: it was inserted between KL-16 and KL-17
instead of after KL-17, so the register's rows were out of order.

Co-Authored-By: opencode <no-reply@opencode.ai>
Aparnap2 added a commit that referenced this pull request Oct 21, 2026
* docs(adr): ratify the claim expiry contract (ADR-011, S5/APA-26)

EXPIRED has been implemented, wired, tested and deployed for a long time,
but no document in this repository ever ratified it. Its semantics were
attested only by code comments citing a Linear id the repo does not
record, while the file calling itself the specification of this lifecycle
did not contain the status at all.

ADR-011 records the approved semantics: EXPIRED is terminal-for-round
(no out-edges; terminality by absence from claims.Transitions), reachable
only as HITL -> EXPIRED or ACTION_PENDING -> EXPIRED (HITL-wait timeout).
Timeout expiry can never approve, reject, close, escalate or otherwise
decide a claim; post-expiry APPROVE/REJECT/ESCALATE/HOLD are illegal and
APPROVE from HITL was already illegal. Re-drive after expiry requires an
explicit new decision/round, never an implicit edge.

Reconciled documentation:
- statemachine.go's header no longer claims the file is 'the Go port of the
  Python specification' - packages/domain/state_machine.py is a demoted
  artifact, not a mirror, and does not define EXPIRED. Comment-only change.
- AGENT_OPERATING_CONTRACT.md item 5 now lists EXPIRED in the chain.
- packages/domain/state_machine.py's docstring states it is superseded for
  lifecycle semantics and is not a mirror. Docstring-only change.
- 09_DECISION_LOG.md and 06_PROGRESS.md carry the decision.
- KNOWN_LIMITATIONS.md gains KL-18 registering the narrower Python graph
  rather than reconciling it silently.

No lifecycle behaviour changed. The implementation already behaved this
way and the existing tests already pin it: claims/expiry_red_test.go,
handlers/decision_expiry_test.go (4 PG-gated),
repository/postgres/hitl_expiry_test.go.

The statemachine.go comment about golden case 08 is also inaccurate but
is deliberately left alone: that correction is a separate focused change.

Co-Authored-By: opencode <no-reply@opencode.ai>

* docs(adr): correct ADR-011 facts after rebasing onto #179

The rebase onto main@7ad446c (which merged #180 and #179) made three
statements in this change false as written:

- The ADR, the decision-log entry and KL-18 all said the cross-language
  fixture 'contains nine cases'. After #179 it contains nineteen. None
  of the nineteen covers EXPIRED, so the substantive claim is unchanged;
  only the count was stale.
- The ADR's Base line still named main@254c4b9; it is now written against
  main@7ad446c.
- The Context said 'no document in this repository ever ratified it'.
  This ADR ratifies it, so the claim needs its temporal qualifier. The
  statemachine.go reference is likewise now stated as 'before this ADR',
  since this change corrects that header.

Also fixes KL-18's position: it was inserted between KL-16 and KL-17
instead of after KL-17, so the register's rows were out of order.

Co-Authored-By: opencode <no-reply@opencode.ai>

---------

Co-authored-by: opencode <no-reply@opencode.ai>
Aparnap2 added a commit that referenced this pull request Oct 24, 2026
…#184)

The PyPI advisory database moved between the last green main run and
the next PR's CI: pypdf 6.21.0 (in the lock via >=6.19.0) now carries
four advisories, all fixed by 6.21.2:

- CVE-2026-113786  (fix: 6.21.1)
- CVE-2026-115572  (fix: 6.21.2)
- CVE-2026-115585  (fix: 6.21.2)
- CVE-2026-115580  (fix: 6.21.2)

Mirrors the pillow remediation (W1 / #180) exactly: raise the
pyproject floor, relock, verify the range, then prove the committed
corpus is byte-identical under the new generator dependency.

pypdf is the generator dependency for fixtures/parser_eval/ (seed 29,
sole reproducer tools/corpus through tools/corpus.generate). Produced
a candidate corpus with pypdf 6.21.2 at master seed 29 and diffed the
whole tree against the committed corpus: 'diff -r' exits clean, all 45
case PDFs byte-identical. No corpus requalification is needed.

Gates: pip-audit 'No known vulnerabilities found' (claimops-ai is
workspace-local and skipped as before); pytest 67 passed;
ruff 'All checks passed!'; go vet/test untouched scope 41 ok.

Co-authored-by: opencode <no-reply@opencode.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant