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].dependencies list)

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

  1. PR for fix/cve-2026-45409-idna is open against main with pip-audit clean and the full pytest suite green.
  2. feat/observability-stack is 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.

This site uses Just the Docs, a documentation theme for Jekyll.