Four fixes, and in three of them the report named one site while the tree held several. v1.108.287's own notes say we keep fixing the reported call site and leaving the mechanism; this release is what checking first looks like.
install-pack extracted drive-absolute archive members outside the install directory (#447, @elfrost)
The pre-scan rejected a leading separator and .., which is necessary and not sufficient: C:/Windows/Temp/evil.txt contains neither, and base / relative discards base when relative is absolute. mkdir(parents=True) ran before the write, so a hostile member created directories outside the install root before a byte of content existed.
Confinement is by resolution now, not by pattern — a string test can never finish enumerating separator and drive spellings. The pre-scan stays as an early abort with the per-member check as the authority.
@elfrost found this, analysed it, and wrote a correct fix in #443 that could not be merged because the CLA went unsigned through a posted window. What shipped applies our own pre-existing _safe_content_path pattern to the call site that lacked it — an independent path, not a copy of their diff. The finding and the design of the remedy are theirs.
The rule already had three spellings in the tree and the new call site would have been a fourth. There is one definition now (security.resolve_within), both index stores delegate to it, and a test fails on a fifth.
PyPI published the whole LICENSE file where an identifier belonged (#517, @marcelruhf)
license = { file = "LICENSE" } made PyPI put the entire licence text into info.license, so an organisation with a commercial licence could not allowlist us by identifier — there was no identifier. Packaging metadata is PEP 639 as of this release: License-Expression: LicenseRef-jCodeMunch-Dual-Use-1.1. PyPI metadata is immutable per version, so 1.108.288 is the first release that carries it.
He could see the one surface PyPI publishes; we declared the licence on three. The plugin manifest and the mcpb manifest both said LicenseRef-Dual-Use, so an allowlist keyed on the identifier still needed two entries. All three agree now, and a licence version bump that forgets the identifier fails the build.
CONFIGURATION.md gave the wrong default for disabled_tools (#515, @rknighton)
The Tools table read [] against a shipped ["test_summarizer"], so a reader expected all 91 canonical tools in the schema and found 90. Four surfaces describe that default and the three that agree are the interesting part — one of them is a test pinning the value. A value pinned by a test can still be mis-documented; the pin guards the value, not every claim about it. Every Default cell in the document is now checked against DEFAULTS.
A full-root re-walk was treated as a subdir merge (#504, @lsg1103275794)
The v1.96 collision guard assigned _merge_with_existing whenever an existing index recorded the same git_root, with no test for whether the walk was actually a subdirectory — so a full-root re-walk could never reach the incremental branch and every scheduled freshness check rebuilt the whole corpus.
⚠ Disclosed migration: the first full-root index after upgrading may be a rebuild, once per index, to establish a complete source_roots marker. Every later root walk is incremental.
Suite: 8083 passed, 17 skipped, 0 failed — same total and same skip split on 3.10 and on the 3.13 CI environment. ruff check src/ clean. All 9 CI jobs green on the release commit.