Skip to content

fix(deps): close 21 Dependabot alerts across five packages - #263

Merged
astrogilda merged 7 commits into
mainfrom
deps/dependabot-triage-2026-08
Aug 27, 2026
Merged

fix(deps): close 21 Dependabot alerts across five packages#263
astrogilda merged 7 commits into
mainfrom
deps/dependabot-triage-2026-08

Conversation

@astrogilda

@astrogilda astrogilda commented Aug 27, 2026

Copy link
Copy Markdown
Owner

This branch closes all 21 open Dependabot alerts. Two of them are bounds in pyproject.toml, for mcp and setuptools. The other three packages, cryptography, pillow and jupyterlab, arrive transitively and move in the lockfile alone. Each package gets its own commit, so any single pin can be reverted without disturbing the rest.

mcp gets a floor of 1.28.1. The WebSocket server transport below that does no Host or Origin validation, so a web page can drive a locally bound server. Our own server speaks stdio, but mcp is the only flagged package that src imports. The floor keeps the vulnerable release out of a shipped extra. setuptools gets a build-system floor of 83, which is the bound that governs our own sdist. Below 83 a Unicode NFC/NFD collision on a case-folding filesystem lets a MANIFEST.in exclusion be bypassed.

The other three reach the install closure only through extras, and no shipped import path touches them. cryptography arrives by way of pyjwt in the mcp extra, carrying a Bleichenbacher oracle on the PKCS#7 decrypt path. pillow comes in through matplotlib in the examples extra with thirteen open advisories at once, mostly heap out-of-bounds writes and reads in the image parsers. jupyterlab comes in through jupyter with five, including stored cross-site scripting in the image viewer. Every replacement publishes a cp314 wheel or is pure Python, so nothing needs a source build on the newest supported interpreter.

The branch also replaces a test that could not fail. The determinism test in tests/unit/test_pwsd.py called optimal_block_length twice with the same argument and asserted the two results matched. That holds for every possible implementation, including one that ignores its input and returns a constant.

Three assertions replace it. A pinned selection fixes the chosen length for one fixed AR(1) draw under both kinds. A global-RNG perturbation reseeds the legacy stream between two calls. A subprocess check reruns the selection under a different hash seed. Each was mutation-checked against an implementation it is supposed to catch, and the deleted test stayed green against all three.

The last commit fixes the flaky notebook job. Our pytest configuration sets a project-wide addopts carrying -n auto, so every pytest call in this repository inherits xdist parallelism. The notebook step never passed an -n of its own. It therefore started one Jupyter kernel per core, and the kernels raced to bind their ZMQ ports. A kernel that lost the race died with "Address already in use", and nbmake reported it as a kernel that died before replying.

Passing -n0 runs the notebooks in one process, which is also faster here, because parallel kernels contend for the same cores. -p no:xdist is not a substitute. Unregistering the plugin also unregisters the options addopts still passes, and pytest then exits 4.

That -n0 is a fix at the call site, and the deeper problem is that our pytest configuration carries parallelism at all. Every caller that does not want it has to remember to undo it, and three callers in this repository already do. Moving those flags onto the callers that want parallelism is a separate change, and it is the one that makes this workaround unnecessary.

Locally, ruff check, ruff format, mypy and pyright are all clean. The suite runs 844 tests, and 843 of them pass every time.

One property test is a pre-existing flake and this branch does not touch it. The AgACI expert-hull invariant in tests/property/test_conformal_rng_invariants.py sometimes trips a Hypothesis filter_too_much health check. Its assume clause rejects too many generated arrays on an unlucky seed. It failed once in five isolated repeats here. That file is byte-identical to main, the only lockfile versions this branch moves are the five security packages, and hypothesis is not one of them. It is worth a separate fix, and it is not this one.

The mcp extra allowed mcp 1.28.0, whose WebSocket server transport performs
no Host or Origin validation (GHSA-vj7q-gjh5-988w, CVE-2026-59950). Any web
page could therefore drive a locally bound server belonging to a consumer
that imports the SDK for its own transport. tsbootstrap-mcp itself speaks
stdio, but the floor keeps the vulnerable release out of the install closure
of a shipped extra, and mcp is the only flagged package that src/ imports
(src/tsbootstrap/mcp.py).

The direct bound in pyproject.toml is what controls this pin, so the bound
moves rather than the lockfile alone. Resolution picks 1.29.0, a pure-python
wheel that covers every supported interpreter including 3.14, and it is well
past the seven-day supply-chain cooldown.

Locked: mcp 1.28.0 -> 1.29.0.
cryptography 49.0.0 exposes a Bleichenbacher oracle when decrypting PKCS#7
EnvelopedData: distinguishable errors and timing let an attacker recover a
plaintext (GHSA-g6cj-pr64-35w5, CVE-2026-69247). It reaches the install
closure transitively, through the mcp extra by way of pyjwt[crypto], and no
bound of ours controls it, so the lockfile entry is what moves.

50.0.0 publishes both cp314 and abi3 wheels, so it needs no source build on
the interpreter this project has just started supporting.

Locked: cryptography 49.0.0 -> 50.0.0.
pillow 12.2.0 carries thirteen open advisories at once: heap out-of-bounds
writes in ImageFilter.RankFilter, Image.paste/crop and ImageCmsTransform.apply;
an out-of-bounds read on the McIdas mmap path; decompression-bomb checks
missing from the PCF, BDF, GD and generic FontFile loaders; denial of service
through the PDF stream decoder, the EPS BeginBinary loop and the JPEG2000
tiled decoder; a TGA RLE encoder that leaks adjacent heap into output; and a
Windows viewer command injection. 12.3.0 fixes all thirteen.

It is a transitive dependency of matplotlib, which only the examples extra
pulls in for the tutorial notebooks, so no shipped import path reaches it and
no bound of ours controls it. The lockfile entry is what moves. 12.3.0 ships
cp314 wheels, so it needs no source build on the newly supported interpreter.

Locked: pillow 12.2.0 -> 12.3.0.
jupyterlab 4.6.0 has five open advisories: stored cross-site scripting when
the image viewer opens a malicious image in a new tab and when a crafted
overrides.json settings file is loaded, a PluginManager lock-rule enforcement
bypass, a PyPI blocklist bypass through package-name canonicalization, and an
allowlist check that a missing await left unenforced for direct callers of
PyPIExtensionManager.install. All five are fixed in 4.6.2; resolution takes
4.6.3, a pure-python wheel that covers every supported interpreter.

It arrives transitively through jupyter, which the docs and examples extras
pull in to build the documentation and execute the tutorial notebooks. No
shipped import path reaches it and no bound of ours controls it, so the
lockfile entry is what moves.

Locked: jupyterlab 4.6.0 -> 4.6.3.
setuptools below 83.0.0 lets a MANIFEST.in exclusion be bypassed in an sdist
through a Unicode NFC/NFD normalization collision on the case-folding macOS
filesystems (GHSA-h35f-9h28-mq5c, CVE-2026-59890), so a file the manifest
excludes can still ship in the source distribution.

Two bounds carry it and both move. The build-system floor is the one that
governs our own sdist, and it goes from 61 to 83; 83.0.0 requires Python
>= 3.10, which is already requires-python, so no supported interpreter is
lost. The locked copy arrives separately and transitively, through pip-tools
in the dev extra, and follows in the lockfile. A source build and a wheel
build both succeed under the new floor.

Locked: setuptools 82.0.1 -> 84.0.0.
test_deterministic called optimal_block_length twice with the same argument
and asserted the two results were equal. That holds for every possible
implementation, including one that ignores its input and returns a constant,
so the test could not fail and did not test the property its name claimed.

Three assertions replace it, each of which can go red.

A pinned selection fixes the chosen length for one fixed AR(1) draw, under
both kinds. The two kinds resolve to different lengths, 12 and 14, so a
constant return fails whichever constant it picks; the pre-ceil values are
11.54 and 13.21, far enough from a rounding boundary that the pins are not
fragile.

A global-RNG perturbation reseeds and burns the legacy stream between two
calls. Politis-White is a closed-form plug-in rule, so any sampling added to
the estimator makes those calls diverge.

A fresh-interpreter check runs the selection in a subprocess under a
different hash seed. Repetition inside one process cannot observe state that
is fixed for a process lifetime, which is the only place determinism is
interesting.

Each was mutation-checked. Against a constant return, against a result that
varies with the global RNG state, and against one that differs only in a new
interpreter, the replacements go red and the deleted test stayed green in all
ten repeats.
The pytest configuration in pyproject.toml sets a project-wide
addopts of "-n auto --dist loadscope", so every pytest invocation in
this repository inherits xdist parallelism unless it opts out. The
notebook execution step never passed an -n of its own, so it started
one Jupyter kernel per core and the kernels raced to bind their ZMQ
ports. A kernel that lost the race died with "Address already in use"
and nbmake reported "Kernel died before replying to kernel_info",
failing the job. The failure was intermittent and disappeared on a
re-run with no code change.

Pass -n0 so the notebooks execute in one process. There is nothing to
lose: the fourteen notebooks are quick serially, and --dist loadscope
buys nothing when each notebook is already its own scope. Measured
locally, serial execution is also faster than the parallel path,
because the kernels contend for the same cores.

-p no:xdist is not a substitute. Unregistering the plugin also
unregisters the -n, --dist and --max-worker-restart options that
addopts still passes, so pytest exits 4 on unrecognized arguments.
@sonarqubecloud

Copy link
Copy Markdown

@mergify

mergify Bot commented Aug 27, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@astrogilda
astrogilda merged commit 5b81cd5 into main Aug 27, 2026
30 checks passed
astrogilda added a commit that referenced this pull request Aug 29, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.7.2](tsbootstrap-v0.7.1...tsbootstrap-v0.7.2)
(2026-08-27)


### Bug Fixes

* **deps:** close 21 Dependabot alerts across five packages
([#263](#263))
([5b81cd5](5b81cd5))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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