chore: rolling uv exclude-newer supply-chain quarantine#2616
chore: rolling uv exclude-newer supply-chain quarantine#2616tonyandrewmeyer wants to merge 6 commits into
Conversation
Add --locked to the project-scoped `uv run` invocations at the top level of .github/workflows/ (zizmor.yaml, integration.yaml). Without --locked, if a PR widens a version range in pyproject.toml without regenerating uv.lock, uv silently re-resolves in CI and the transitive-dependency delta never appears in the diff. --locked makes uv fail instead of re-resolving when uv.lock is out of sync with pyproject.toml, forcing a fresh uv.lock commit and making the resolution change reviewable. Not using --frozen: --frozen skips the freshness check entirely (it just uses the lock file as-is, even if stale relative to pyproject.toml), so it doesn't provide this guarantee. Scope: only bare project-scoped `uv run`/`uv sync` calls against the root uv.lock. The `uv run --script --no-project` and the working-directory=examples/* `uv run` invocations in example-charm-tests.yaml were left untouched -- the latter resolves against each example charm's own uv.lock (each examples/* directory has its own pyproject.toml/uv.lock), a separate resolution story from the root project. `uv tool install`, `uvx`, `uv build`, `uv export`, `uv add`/`uv remove` invocations are out of scope for --locked (they are not `uv run`/`uv sync`) and were left untouched, as were all pip/tox/poetry invocations.
Add [tool.uv].exclude-newer = "7 days" to pyproject.toml. uv will ignore any package release newer than 7 days old when resolving dependencies, across every uv resolution path: manual `uv add`, `uv lock` regenerations, uvx bootstraps, and CI re-resolves. This complements (does not replace) Dependabot's cooldown, which only protects Dependabot-authored PRs. exclude-newer is a rolling window -- "7 days" is evaluated relative to whenever the resolution runs, not a fixed date -- so it continuously quarantines newly published releases from every other route a bad or compromised release could enter the dependency tree. Reference: uv docs (exclude-newer accepts durations like "7 days"); Canonical Security "How-To: Secure a repo" -- Minimum release age.
Add [tool.uv].no-build = true to pyproject.toml. With this set, uv refuses to build or install any package from a source distribution (sdist) and will only install from wheels. Why: installing an sdist (via `pip install <sdist>` or `uv sync` falling back to one) runs that package's setup.py / PEP 517 build hooks at install time -- arbitrary code execution controlled by whatever is in the sdist. Wheels are pre-built and do not run install scripts, so restricting resolution to wheels closes this vector fleet-wide. Fleet verification (2026-07-02): 0 of 571 dependencies across the fleet's uv.lock files are sdist-only (confirmed 0/73 in this repo's own root uv.lock), so this is a zero-cost change today -- no package currently in use requires falling back to an sdist build. Escape hatch: if a future dependency is genuinely sdist-only and must be installed anyway, exempt it individually with [tool.uv].no-build-package = ["specific-pkg"] rather than reverting this setting. Reference: Canonical Security "How-To: Secure a repo" -- Install scripts section.
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.
…exclude-newer 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. tox-uv's uv-venv-lock-runner passes --locked to 'uv sync', so every run the newly-computed floor differs from the stored one, uv 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.
Reverts the tox.ini runner change from uv-venv-runner back to uv-venv-lock-runner: uv-venv-runner uses 'uv pip install' with only the tox-declared dep groups, which skips the workspace install and left runtime deps (e.g. ops-scenario==8.9.0.dev0) unresolvable. Instead, add 'uv lock' before every tox invocation across framework-tests.yaml and example-charm-tests.yaml. That re-locks against the current '[tool.uv].exclude-newer = "7 days"' floor, so tox's uv-venv-lock-runner sees a fresh uv.lock and its internal 'uv sync --locked' succeeds. --locked stays as the security control inside tox; the rolling exclude-newer floor no longer breaks it.
dwilding
left a comment
There was a problem hiding this comment.
I don't understand why the extra locks are needed. Especially the ones in example-charm-tests.yaml, which run in the charm projects where nothing has changed.
Yeah, I have the same question. |
james-garner-canonical
left a comment
There was a problem hiding this comment.
Comment review to allow explicit re-review request once the uv lock issue is hammered out elsewhere.
|
Have you discussed this elsewhere? I've been investigating. For the example charms, the issue seems to be: tox-uv views the charm's I think the right fix might be to run the example charm |
Thanks for investigating this. I posted a suggestion on this similar PR, but I haven't tested anything. |
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.