CVE-2026-45409 idna Bump — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Lift the project’s idna floor to >= 3.15 to remediate CVE-2026-45409, and verify pip-audit reports clean across all environments.
Architecture: Single-commit security PR off main. Add an explicit floor in pyproject.toml (visible, durable across uv lock --upgrade rebuilds), then refresh uv.lock so the resolved version moves from 3.11 → 3.15+. After merge, fold the bump into the open feat/observability-stack branch via a merge from main (not rebase — preserves PR #156’s review state).
Tech Stack: uv (lock & resolver), pip-audit (vulnerability scan), pytest (regression check).
Note on TDD: This is a pure dependency-version change with no behavioral surface in our code. The “failing test” is pip-audit reporting the CVE; the “passing test” is pip-audit clean. Standard TDD (write-test → implement → green) does not apply; we use verification-driven development instead — every task ends with a concrete command and an expected output.
Working branch: fix/cve-2026-45409-idna (already created off main; design doc already committed in a057849).
Task 1: Confirm the vulnerability is reproducible
Files: none (read-only).
Step 1: Run pip-audit and capture the current finding
Run: uv run pip-audit --strict --desc 2>&1 | grep -A2 idna
Expected: a line reporting idna 3.11 CVE-2026-45409 3.15 (matching the report that motivated this work). If the finding is missing, stop and reassess — either pip-audit has been updated and the advisory rolled forward, or the local lockfile already differs from what was scanned. Do not proceed.
Task 2: Add the explicit floor in pyproject.toml
Files:
- Modify:
pyproject.toml(around line 17, end of[project].dependencieslist)
Step 1: Edit pyproject.toml
Replace the closing of the dependencies list:
"django-json-widget>=2.0.1",
]
with:
"django-json-widget>=2.0.1",
# CVE-2026-45409 floor — idna is transitive (requests/httpx/anyio);
# pinned explicitly so future `uv lock --upgrade` cannot regress.
"idna>=3.15",
]
Step 2: Verify the edit parses
Run: uv run python -c "import tomllib; tomllib.loads(open('pyproject.toml').read())"
Expected: exit 0, no output. (Confirms the TOML is still valid.)
Task 3: Refresh the lockfile
Files:
- Modify:
uv.lock(auto-generated; do not hand-edit)
Step 1: Upgrade only idna in the lock
Run: uv lock --upgrade-package idna
Expected: stdout reports Updated idna v3.11 -> vX.Y.Z where X.Y.Z >= 3.15. No other package versions should change. If other packages are also updated, stop and investigate — --upgrade-package is supposed to be scoped.
Step 2: Confirm the lockfile records >= 3.15
Run: grep -nE '^name = "idna"|^version = ' uv.lock | grep -A1 '"idna"' | head -2
Expected: the version line under name = "idna" shows version = "3.15" or higher.
Task 4: Verify pip-audit is clean
Files: none.
Step 1: Re-run the audit
Run: uv run pip-audit --strict --desc
Expected: No known vulnerabilities found (exit 0). If any finding remains — including a new transitive — stop and surface it before proceeding.
Task 5: Run the full test suite
Files: none.
Step 1: Sync the environment from the new lock
Run: uv sync --extra dev
Expected: idna 3.15+ is installed; no other version churn.
Step 2: Run pytest
Run: uv run pytest
Expected: same passing count as before the bump (currently 2206 passed). Any new failure is treated as a regression caused by the idna version change — investigate before committing. (Network-touching tests using requests/httpx are the most plausible regression surface, though none is expected from a parsing-only library.)
Task 6: Commit and push
Files:
- Add:
pyproject.toml,uv.lock
Step 1: Stage and commit
Run:
git add pyproject.toml uv.lock
git commit -m "$(cat <<'EOF'
fix(deps): bump idna 3.11 → >=3.15 for CVE-2026-45409
idna < 3.14 is vulnerable to DoS via crafted long inputs to
idna.encode() (CVE-2026-45409 — incomplete 2024 patch for the same
issue tracked as CVE-2024-3651). idna is purely transitive here
(requests/httpx/anyio), so we add an explicit >=3.15 floor in
pyproject.toml to make the security pin visible and durable across
future lock rebuilds.
Verified: uv run pip-audit --strict --desc reports clean.
Verified: uv run pytest — full suite green.
Refs: docs/plans/2026-05-24-cve-2026-45409-idna-bump-design.md
EOF
)"
Expected: pre-commit hooks (black, ruff, pytest, mypy) all pass.
Step 2: Push and set upstream
Run: git push -u origin fix/cve-2026-45409-idna
Expected: new remote branch created; GitHub returns a PR-creation URL.
Task 7: Open the PR
Files: none (uses gh).
Step 1: Open PR against main
Run:
gh pr create --base main --head fix/cve-2026-45409-idna \
--title "fix(deps): bump idna 3.11 → >=3.15 (CVE-2026-45409)" \
--body "$(cat <<'EOF'
## Summary
Adds an explicit `idna>=3.15` floor in `pyproject.toml` and refreshes `uv.lock` to remediate **CVE-2026-45409** (DoS via crafted long inputs to `idna.encode()`; incomplete remediation of the 2024 CVE-2024-3651 advisory).
`idna` is transitive here (pulled in by `requests`, `httpx`, `anyio`). Pinning the floor in the manifest rather than only in the lock makes the security constraint visible to anyone reading the deps and durable across future `uv lock --upgrade` rebuilds.
See `docs/plans/2026-05-24-cve-2026-45409-idna-bump-design.md` for the rationale and rejected alternatives.
## Test plan
- [ ] `uv run pip-audit --strict --desc` — clean
- [ ] `uv run pytest` — full suite green (no regressions from the version bump)
- [ ] `uv sync --extra dev` — installs idna 3.15+ with no other version churn
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
Expected: PR URL printed. Note the number for Task 8.
Task 8 (deferred — runs only after Task 7’s PR is merged): Update feat/observability-stack
Files: updates pyproject.toml + uv.lock on the observability branch via a merge commit.
Step 1: Fetch and check out the observability branch
git fetch origin main
git checkout feat/observability-stack
git pull --ff-only origin feat/observability-stack
Step 2: Merge main (not rebase)
git merge origin/main
Expected: a merge commit. No conflicts expected — the idna fix only touches pyproject.toml (a clean append) and uv.lock (auto-regenerated). If a conflict appears in uv.lock, resolve by re-running uv lock (do not hand-merge lockfile diffs).
Step 3: Re-verify on the merged branch
uv sync --extra dev
uv run pip-audit --strict --desc # clean
uv run pytest # 2206 passing (or higher if main grew tests)
Step 4: Push
git push origin feat/observability-stack
Expected: PR #156 picks up the merge commit automatically.
Done criteria
- PR for
fix/cve-2026-45409-idnais open againstmainwith pip-audit clean and the full pytest suite green. feat/observability-stackis updated (after the security PR merges) such that PR #156 also shows pip-audit clean.
Skills referenced
- @superpowers:verification-before-completion — every task ends with a concrete verification command and expected output.
- @superpowers:executing-plans — execution harness for this plan.