fix(deps): close 21 Dependabot alerts across five packages - #263
Merged
Conversation
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.
|
|
Tick the box to add this pull request to the merge queue (same as
|
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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
addoptscarrying-n auto, so every pytest call in this repository inherits xdist parallelism. The notebook step never passed an-nof 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
-n0runs the notebooks in one process, which is also faster here, because parallel kernels contend for the same cores.-p no:xdistis not a substitute. Unregistering the plugin also unregisters the optionsaddoptsstill passes, and pytest then exits 4.That
-n0is 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.