fix(deps): bump multidict 6.7.1 -> 6.9.1 for CVE-2026-104874 [PILOT-8488] - #218
Conversation
pip-audit in the Quality Gate fails on every run since the PyPA advisory GHSA-54p9-h82j-f925 (CVE-2026-104874) was published on 2026-10-05 23:40 UTC: a reference leak in the items-view union and subtraction operators of multidict 6.7.0 to 6.9.0, fixed in 6.9.1. multidict is transitive only (aiohttp via litellm and uipath; yarl via harbor -> supabase). `uv lock --upgrade-package multidict` changes no other package. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Claude finished @Mihaiii's task in 1m 22s —— View job Reviewing PR #218: Fix CVE-2026-104874 in multidict dependency
|
uipreliga
left a comment
There was a problem hiding this comment.
Review: coder_eval — pr:218 — fix(deps): bump multidict 6.7.1 -> 6.9.1 for CVE-2026-104874 [PILOT-8488] · PR #218 by @Mihaiii · fix/multidict-cve-2026-104874 → main · OPEN
Scope: pr:218 — fix(deps): bump multidict 6.7.1 -> 6.9.1 for CVE-2026-104874 [PILOT-8488] · PR #218 by @Mihaiii · fix/multidict-cve-2026-104874 → main · OPEN · branch fix/multidict-cve-2026-104874 · 9b1da89 · 2026-10-06T10:51Z · workflow variant
Change class: trivial — lockfile-only dependency bump (transitive multidict 6.7.1 → 6.9.1); no code path changes
The codebase is healthy on all eight axes (10/10 overall), and no finding can change a task's score or final_status; the only risk is one low-severity lockfile churn (uv.lock:2 revision 5 -> 3 from an older local uv), so the bottom line is to merge after you revert that one line.
Summary
| Axis | Score | 🔴 | 🟠 | 🟡 | 🔵 | Top Issue |
|---|---|---|---|---|---|---|
| 1. Code Quality & Style | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 2. Type Safety | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 3. Test Health | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 4. Security | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 5. Architecture & Design | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 6. Error Handling & Resilience | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 7. API Surface & Maintainability | 9.9 / 10 | 0 | 0 | 0 | 1 | Lockfile header revision regressed 5 -> 3 (out-of-scope churn from an older local uv; contradicts PR body) |
| 8. Evaluation Harness Quality | 10 / 10 | 0 | 0 | 0 | 0 | — |
Overall Score: 10 / 10 · Weakest Axis: API Surface & Maintainability at 9.9 / 10
Totals: 🔴 0 · 🟠 0 · 🟡 0 · 🔵 1 across 8 axes.
Blockers
None.
Non-blocking, but please consider before merge
None.
Nits
- [Axis 7] Lockfile header revision regressed 5 -> 3 (out-of-scope churn from an older local uv; contradicts PR body) (
uv.lock:2) — The diff changes-revision = 5to+revision = 3at uv.lock:2. This line has nothing to do with the multidict 6.7.1 -> 6.9.1 bump. It is an artifact of relocking with an older local uv than the one release.yml uses (setup-uv v4.2.0, which installs the latest uv). It also contradicts the PR body's claim that 'all 193 changed lines are multidict's version and wheel list'. Nothing breaks, becauseuv sync --frozenaccepts revision 3. But the header will flip back: release.yml:238 runsuv lockfollowed bygit add uv.lockon the next semantic-release, which rewrites it torevision = 5. That creates lockfile churn in a bot commit, and the header will keep going back and forth between contributors on different uv versions. Fix: restorerevision = 5(relock with the same uv version CI's release job uses, or hand-revert line 2), so that the diff contains only the multidict stanza. Optional: pin a minimum uv version (e.g.[tool.uv] required-versionin pyproject.toml), which stops older local uv from relocking and downgrading the format revision.
What's Missing
Daily/nightly:
- 🔵 The PR does not say what happens to the nightly run. The multidict 6.7.1 -> 6.9.1 bump does not stay in the dev venv. Both container images install from this lockfile: docker/Dockerfile:97 and docker/Dockerfile.runtime:63 run
uv export --frozen ... | uv pip install. So the next image rebuild ships the new multidict (an aiohttp/yarl transitive) into everydriver: dockertask in the nightly. Add one line to the PR that names the image-rebuild blast radius. Also record that the bump was checked against the docker path, not only againstuv sync --frozenin pr-checks. (trigger: uv.lock)
Parallel paths:
- 🔵 The uv version is not pinned on any path that writes or reads this lockfile. Local contributors use whatever uv they have installed. release.yml:195/238 runs
uv lockunder setup-uv v4.2.0 with no version input, and docker/Dockerfile:45 and Dockerfile.runtime:38 runcurl astral.sh/uv/install.shat latest. The revision 5 -> 3 regression came from the contributor path. Nothing aligns the other three paths, so the same flip can come back from any of them. A[tool.uv] required-versionin pyproject.toml covers all four in one place. (trigger: uv.lock) (restates: Axis 7: Lockfile header revision regressed 5 -> 3)
Harness & Lint Improvements
Static checks (lint / type):
- [ce-lint] CE069 (next free number; CE067 is retired per .claude/harness-candidates.md:958), 'uv toolchain pin parity'. This is a whole-tree rule, so it goes in a
@pytest.mark.lintclass in tests/test_custom_lint.py, not in tests/lint/rules/ + runner.py. It parses pyproject.toml, uv.lock and .github/workflows/*.yml and fails in three cases. (a) pyproject.toml[tool.uv](line 214) has norequired-version. (b) Anyastral-sh/setup-uvstep has nowith: version:input, or that input is outsiderequired-version. Today release.yml:112, pr-checks.yml:265, publish-testpypi.yml:58 and verify-published-action.yml:261 all install the latest uv. (c) Therevision = Nheader on uv.lock line 2 is lower than the revision that the pinned uv version writes. Store that number once beside the rule, for exampleEXPECTED_LOCK_REVISION = 5, and update it when the uv pin moves. Add therequired-versionsetting in the same change. Then an older local uv refuses to relock (do not delete before guarding), and the rule keeps the pin and the CI installs from drifting apart. Prevents: Axis 7 (low): the uv.lock:2 header went fromrevision = 5torevision = 3because the relock used an older local uv than CI. Check (c) fails on that diff directly. Checks (a) and (b) remove the cause: withrequired-version, the local uv refuses to runuv lock. With setup-uv pinned to the same version, theuv lock+git add uv.lockat release.yml:238 cannot flip the header back in a bot commit.
Harness improvements (not statically reachable):
- Add a 'lockfile is canonical' job to pr-checks.yml, run only when a PR touches uv.lock or pyproject.toml. It installs the pinned uv version (the same one CE069 enforces), runs
uv lock, and fails ongit diff --exit-code uv.lock. Also add a matchingmake lock-checktarget so contributors can run the same check before they push. Why not static: To show that a lockfile is exactly what the pinned resolver produces, uv must resolve against the package index (network, index state). CE069 can see only the header revision and the version pins. It cannot see the other churn that a different uv version or resolver setting puts in the package stanzas. Prevents: Axis 7 (low): the out-of-scope uv.lock churn (the revision header, and any other relock artifacts) gets caught at PR time, before the release.yml:238uv lockrewrites the file in a semantic-release bot commit. - Add a review-checklist item (or a light PR-template prompt) for dependency-bump PRs: list the package stanzas changed in
git diff uv.lockand compare them with the packages the PR body names. A one-package bump that changes the file header or other packages needs a stated reason. Why not static: The mismatch is between the diff and a prose claim in the PR description ('all 193 changed lines are multidict's version and wheel list'). The PR body is not in the tree, and a judgment call is needed to say if extra churn is intended. Prevents: Axis 7 (low): this catches a PR body that does not match its uv.lock diff, here a revision 5 -> 3 header change in a PR described as touching only multidict.
Top 5 Priority Actions
- No confirmed finding can change a task's score or final_status for identical agent output, so grading needs no action in this change.
- Restore
revision = 5at uv.lock:2 (hand-revert the line or relock with the uv version that CI's release job uses), so that the diff contains only the multidict 6.7.1 -> 6.9.1 stanza. - Add
[tool.uv] required-versionin pyproject.toml, so that an older local uv cannot relock and downgrade the lockfile format revision. - Pin the uv version in the release job's setup-uv step (release.yml, before the
uv lock+git add uv.lockat release.yml:238) to the same minimum, so that the semantic-release bot commit does not rewrite the uv.lock header. - Correct the PR body claim that 'all 193 changed lines are multidict's version and wheel list', or make it true by reverting uv.lock:2, so that the PR description agrees with the diff.
Stats: 0 🔴 · 0 🟠 · 0 🟡 · 1 🔵 across 8 axes reviewed.
uipreliga
left a comment
There was a problem hiding this comment.
Review: coder_eval — pr:218 — fix(deps): bump multidict 6.7.1 -> 6.9.1 for CVE-2026-104874 [PILOT-8488] · PR #218 by @Mihaiii · fix/multidict-cve-2026-104874 → main · OPEN
Scope: pr:218 — fix(deps): bump multidict 6.7.1 -> 6.9.1 for CVE-2026-104874 [PILOT-8488] · PR #218 by @Mihaiii · fix/multidict-cve-2026-104874 → main · OPEN · branch fix/multidict-cve-2026-104874 · 9b1da89 · 2026-10-06T10:51Z · workflow variant
Change class: trivial — lockfile-only dependency bump (transitive multidict 6.7.1 → 6.9.1); no code path changes
The codebase is healthy on all eight axes (10/10 overall), and no finding can change a task's score or final_status; the only risk is one low-severity lockfile churn (uv.lock:2 revision 5 -> 3 from an older local uv), so the bottom line is to merge after you revert that one line.
Summary
| Axis | Score | 🔴 | 🟠 | 🟡 | 🔵 | Top Issue |
|---|---|---|---|---|---|---|
| 1. Code Quality & Style | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 2. Type Safety | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 3. Test Health | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 4. Security | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 5. Architecture & Design | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 6. Error Handling & Resilience | 10 / 10 | 0 | 0 | 0 | 0 | — |
| 7. API Surface & Maintainability | 9.9 / 10 | 0 | 0 | 0 | 1 | Lockfile header revision regressed 5 -> 3 (out-of-scope churn from an older local uv; contradicts PR body) |
| 8. Evaluation Harness Quality | 10 / 10 | 0 | 0 | 0 | 0 | — |
Overall Score: 10 / 10 · Weakest Axis: API Surface & Maintainability at 9.9 / 10
Totals: 🔴 0 · 🟠 0 · 🟡 0 · 🔵 1 across 8 axes.
Blockers
None.
Non-blocking, but please consider before merge
None.
Nits
- [Axis 7] Lockfile header revision regressed 5 -> 3 (out-of-scope churn from an older local uv; contradicts PR body) (
uv.lock:2) — The diff changes-revision = 5to+revision = 3at uv.lock:2. This line has nothing to do with the multidict 6.7.1 -> 6.9.1 bump. It is an artifact of relocking with an older local uv than the one release.yml uses (setup-uv v4.2.0, which installs the latest uv). It also contradicts the PR body's claim that 'all 193 changed lines are multidict's version and wheel list'. Nothing breaks, becauseuv sync --frozenaccepts revision 3. But the header will flip back: release.yml:238 runsuv lockfollowed bygit add uv.lockon the next semantic-release, which rewrites it torevision = 5. That creates lockfile churn in a bot commit, and the header will keep going back and forth between contributors on different uv versions. Fix: restorerevision = 5(relock with the same uv version CI's release job uses, or hand-revert line 2), so that the diff contains only the multidict stanza. Optional: pin a minimum uv version (e.g.[tool.uv] required-versionin pyproject.toml), which stops older local uv from relocking and downgrading the format revision.
What's Missing
Daily/nightly:
- 🔵 The PR does not say what happens to the nightly run. The multidict 6.7.1 -> 6.9.1 bump does not stay in the dev venv. Both container images install from this lockfile: docker/Dockerfile:97 and docker/Dockerfile.runtime:63 run
uv export --frozen ... | uv pip install. So the next image rebuild ships the new multidict (an aiohttp/yarl transitive) into everydriver: dockertask in the nightly. Add one line to the PR that names the image-rebuild blast radius. Also record that the bump was checked against the docker path, not only againstuv sync --frozenin pr-checks. (trigger: uv.lock)
Parallel paths:
- 🔵 The uv version is not pinned on any path that writes or reads this lockfile. Local contributors use whatever uv they have installed. release.yml:195/238 runs
uv lockunder setup-uv v4.2.0 with no version input, and docker/Dockerfile:45 and Dockerfile.runtime:38 runcurl astral.sh/uv/install.shat latest. The revision 5 -> 3 regression came from the contributor path. Nothing aligns the other three paths, so the same flip can come back from any of them. A[tool.uv] required-versionin pyproject.toml covers all four in one place. (trigger: uv.lock) (restates: Axis 7: Lockfile header revision regressed 5 -> 3)
Harness & Lint Improvements
Static checks (lint / type):
- [ce-lint] CE069 (next free number; CE067 is retired per .claude/harness-candidates.md:958), 'uv toolchain pin parity'. This is a whole-tree rule, so it goes in a
@pytest.mark.lintclass in tests/test_custom_lint.py, not in tests/lint/rules/ + runner.py. It parses pyproject.toml, uv.lock and .github/workflows/*.yml and fails in three cases. (a) pyproject.toml[tool.uv](line 214) has norequired-version. (b) Anyastral-sh/setup-uvstep has nowith: version:input, or that input is outsiderequired-version. Today release.yml:112, pr-checks.yml:265, publish-testpypi.yml:58 and verify-published-action.yml:261 all install the latest uv. (c) Therevision = Nheader on uv.lock line 2 is lower than the revision that the pinned uv version writes. Store that number once beside the rule, for exampleEXPECTED_LOCK_REVISION = 5, and update it when the uv pin moves. Add therequired-versionsetting in the same change. Then an older local uv refuses to relock (do not delete before guarding), and the rule keeps the pin and the CI installs from drifting apart. Prevents: Axis 7 (low): the uv.lock:2 header went fromrevision = 5torevision = 3because the relock used an older local uv than CI. Check (c) fails on that diff directly. Checks (a) and (b) remove the cause: withrequired-version, the local uv refuses to runuv lock. With setup-uv pinned to the same version, theuv lock+git add uv.lockat release.yml:238 cannot flip the header back in a bot commit.
Harness improvements (not statically reachable):
- Add a 'lockfile is canonical' job to pr-checks.yml, run only when a PR touches uv.lock or pyproject.toml. It installs the pinned uv version (the same one CE069 enforces), runs
uv lock, and fails ongit diff --exit-code uv.lock. Also add a matchingmake lock-checktarget so contributors can run the same check before they push. Why not static: To show that a lockfile is exactly what the pinned resolver produces, uv must resolve against the package index (network, index state). CE069 can see only the header revision and the version pins. It cannot see the other churn that a different uv version or resolver setting puts in the package stanzas. Prevents: Axis 7 (low): the out-of-scope uv.lock churn (the revision header, and any other relock artifacts) gets caught at PR time, before the release.yml:238uv lockrewrites the file in a semantic-release bot commit. - Add a review-checklist item (or a light PR-template prompt) for dependency-bump PRs: list the package stanzas changed in
git diff uv.lockand compare them with the packages the PR body names. A one-package bump that changes the file header or other packages needs a stated reason. Why not static: The mismatch is between the diff and a prose claim in the PR description ('all 193 changed lines are multidict's version and wheel list'). The PR body is not in the tree, and a judgment call is needed to say if extra churn is intended. Prevents: Axis 7 (low): this catches a PR body that does not match its uv.lock diff, here a revision 5 -> 3 header change in a PR described as touching only multidict.
Top 5 Priority Actions
- No confirmed finding can change a task's score or final_status for identical agent output, so grading needs no action in this change.
- Restore
revision = 5at uv.lock:2 (hand-revert the line or relock with the uv version that CI's release job uses), so that the diff contains only the multidict 6.7.1 -> 6.9.1 stanza. - Add
[tool.uv] required-versionin pyproject.toml, so that an older local uv cannot relock and downgrade the lockfile format revision. - Pin the uv version in the release job's setup-uv step (release.yml, before the
uv lock+git add uv.lockat release.yml:238) to the same minimum, so that the semantic-release bot commit does not rewrite the uv.lock header. - Correct the PR body claim that 'all 193 changed lines are multidict's version and wheel list', or make it true by reverting uv.lock:2, so that the PR description agrees with the diff.
Stats: 0 🔴 · 0 🟠 · 0 🟡 · 1 🔵 across 8 axes reviewed.

Issue
PILOT-8488
Summary
multidict6.7.1 → 6.9.1 inuv.lock. 6.9.1 fixes CVE-2026-104874 / GHSA-54p9-h82j-f925 (a reference leak in the items-view union and subtraction operators of 6.7.0 to 6.9.0).uv lock --upgrade-package multidictonly. The lock changes no other package: all 193 changed lines aremultidict's version and wheel list.pyproject.tomlchange.multidictcomes only throughaiohttp(fromlitellmanduipath) andyarl(fromharbor→supabase).Why the date is 2026-10-05 23:40 UTC
pip-audit reads the PyPA advisory database. OSV records GHSA-54p9-h82j-f925 as published at 2026-10-05 23:40:58 UTC.
The last green Quality Gate was on
mainat 2026-10-05 17:16 UTC (run 37347251438), before the advisory.Every Quality Gate after it failed with the same finding:
Runs that failed (every Quality Gate run since the advisory)
chore/move-delegate-sdk-2No other branch has run the workflow since the advisory. These open PRs into
mainlast ran before it, so their next run will fail the same way: #211, #210, #209, #208, #181, #173, #160, #149, #117, #67, #47. So will the next push tomain, and #53, which has no recorded run yet. #183 and #184 are stacked on feature branches, so this workflow does not run on them until they targetmain.Testing
uv sync --frozen --extra dev --extra uipath --extra codex --extra litellm --extra harbor(the Quality Gate's install line) installsmultidict 6.9.1.--ignore-vulnlist aspr-checks.yml:126) reportsNo known vulnerabilities found, 1 ignored.-m "not live and not lint", Windows): 5985 passed, 10 failed. All 10 failures come from the local machine, not from this change: 8 need symlink rights (WinError 1314), and 2 find anode_modulesfolder in a parent directory. CI runs the suite on Linux.🤖 Generated with Claude Code