chore: rolling uv exclude-newer supply-chain quarantine - #69
Merged
tonyandrewmeyer merged 6 commits intoJul 7, 2026
Conversation
Add --locked to every project-scoped `uv run` invocation in CI-invoked workflows and Makefile targets (security.yaml's pip-audit step, and the Makefile lint target used by lint.yaml). Without --locked, if a PR widens a version range in pyproject.toml without regenerating uv.lock, uv silently re-resolves at CI-run time and the transitive-dependency delta never appears in the diff for review. --locked makes uv fail instead, forcing a fresh uv.lock commit so the resolution change is reviewable. --locked is used instead of --frozen: --frozen skips the freshness check against pyproject.toml entirely and just uses whatever lock file is present, so it would not catch this case. Out of scope, left unchanged: - .github/workflows/zizmor.yaml's `uvx zizmor@v1.23.1 ...` -- uvx tool invocations are explicitly excluded from this sweep. - .github/workflows/security.yaml's `uv export --frozen ...` -- this is `uv export`, not `uv run`/`uv sync`, so it's out of scope for this change; --frozen there is also the correct flag since export legitimately should not trigger a re-resolve. - Makefile's `format` target -- local-dev-only, not invoked by CI.
Add [tool.uv].exclude-newer = "7 days" so every uv resolution ignores package releases published in the last 7 days. Dependabot's cooldown setting only protects Dependabot-authored PRs. It does nothing for every other uv resolution path in this repo: manual `uv add`, ad-hoc `uv lock` regenerations, `uvx` bootstraps, and any CI re-resolve. exclude-newer covers all of those with a rolling 7-day quarantine on newly published versions/files, giving a short window for a bad or compromised release to be caught and yanked before this project can pick it up. "7 days" uses uv's friendly duration syntax and is evaluated relative to the current time at resolution, so the window rolls forward automatically -- no maintenance required. Reference: Canonical Security "How-To: Secure a repo" -- Minimum release age.
Add [tool.uv].no-build = true so uv refuses to build or install from any source distribution (sdist) -- for this project's own dependencies and any transitive dependency -- and will only accept prebuilt wheels. Installing an sdist runs its setup.py / PEP 517 build hooks at install time, which is arbitrary code execution under the control of whoever published that package. Wheels carry no such hook and cannot execute code during install. no-build closes this vector fleet-wide by making uv error out instead of silently falling back to a source build. Verified 2026-07-02 that 0/571 dependencies across the fleet's uv.lock files are sdist-only, so this is a zero-cost adoption today. If a future dependency genuinely has no wheel available, the narrower escape hatch is [tool.uv].no-build-package = ["specific-pkg"] rather than reverting this setting. Reference: Canonical Security "How-To: Secure a repo" -- Install scripts.
Replace the `[tool.uv]` block with the canonical fleet comment (rolling quarantine rationale), drop `no-build = true`, and drop `--locked` from CI workflow `uv run` / `uv sync` invocations. See canonical/pytest-jubilant#98 for the exemplar PR and canonical/charm-tech#22 (references/decisions.md) for the recorded deferral of `no-build` and `--locked`: uv has no allow-list to exempt the workspace project from `no-build`, and rolling `exclude-newer` is fundamentally incompatible with `--locked` (uv records the resolved-at-lock-time timestamp in `uv.lock`, so every day CI recomputes a different value and `--locked` errors). Rolling `exclude-newer` is the surviving pattern.
The rolling '[tool.uv].exclude-newer = "7 days"' resolves to now() - 7 days at each invocation and uv records the absolute timestamp in uv.lock. Every run the newly-computed floor differs from the stored one, so 'uv sync --locked' re-resolves, sees a config change, and errors. Rolling exclude-newer and --locked are fundamentally incompatible; exclude-newer is the security control with unique coverage (paths Dependabot cooldown doesn't reach), so --locked has to go.
tonyandrewmeyer
marked this pull request as ready for review
July 4, 2026 04:26
dwilding
approved these changes
Jul 5, 2026
james-garner-canonical
approved these changes
Jul 7, 2026
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.
Adds a rolling package-level cooldown in
[tool.uv]:uv refuses to resolve against any package published in the last 7 days, so a compromised release has a window to be caught / yanked upstream before it can enter this project. Complements (does not replace) the Dependabot cooldown, which only protects Dependabot-authored PRs —
exclude-neweralso covers manualuv add,uv lockregens,uvxbootstraps, and CI re-resolves, which Dependabot cooldown alone doesn't reach.