Skip to content

chore(deps): batch six renovate lockfile bumps + clear yanked spin - #452

Merged
mpecan merged 2 commits into
mainfrom
chore/renovate-batch-2026-08
Aug 7, 2026
Merged

chore(deps): batch six renovate lockfile bumps + clear yanked spin#452
mpecan merged 2 commits into
mainfrom
chore/renovate-batch-2026-08

Conversation

@mpecan

@mpecan mpecan commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Batches the six open renovate PRs into one branch — one CI run, one review, and no Cargo.lock conflict cascade from merging them sequentially. Same approach as #435. Also clears two yanked crates found while adding supply-chain checks.

Taken — the six renovate PRs

Crate From → To Was
clap 4.6.1 → 4.6.6 #443
clap_complete 4.6.7 → 4.6.9 #447
clap_complete_nushell 4.6.0 → 4.6.1 #444
futures-util 0.3.32 → 0.3.33 #445
http-body-util 0.1.3 → 0.1.4 #446
keyring 4.1.5 → 4.1.6 #448

No Cargo.toml changes — every bump fits the existing constraints. clap_builder, clap_derive and the futures-core/io/macro/sink/task family move with their parents; the diff is exactly the union of the six PRs, no extra crates picked up.

Also taken — two yanked crates (second commit)

Crate From → To Reached via
spin 0.9.8 → 0.9.9 axum 0.8.9 → multer 3.1.0
spin 0.10.0 → 0.10.1 aws-sdk-s3 1.137.0 → aws-smithy-checksums → crc-fast 1.10.0

Both versions had been yanked from crates.io. Neither is reachable from a direct dependency, so nothing surfaced them until cargo deny check advisories went looking (that check is being added in a separate PR). Each has a non-yanked successor inside the same semver range, so this is lockfile-only too.

A yank is the mechanism by which a compromised release gets withdrawn, so sitting on yanked versions is worth clearing regardless of why these two were pulled.

Why batch instead of merging the six

All six touch only Cargo.lock. Merging any one makes the other five conflict, and renovate only rebases inside its schedule window (before 6am on monday) — so merging them one at a time would have spread across several weeks.

Why they were stuck

Renovate had already enabled squash auto-merge on all six on the day it opened them. They sat at mergeStateStatus: BLOCKED / reviewDecision: REVIEW_REQUIRED, because the require-pr-approval ruleset requires one approving review and renovate cannot approve its own PR. Its bypass actor is the admin repository role, which renovate does not hold. The automerge: true rule for cargo patch updates in .github/renovate.json can therefore never fire — every patch bump keeps piling up until someone batches it by hand. Addressed in the follow-up hardening PR.

The two red PRs (#443, #447) were not real failures — both were GitHub Actions infrastructure flakes (Failed to resolve action download info. Error: Service Unavailable). I re-ran them and they went green.

Verification

  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace — 2468 passed, 0 failed, 180 ignored
  • cargo deny check advisories bans sources — passes with the spin fix; failed on both yanked versions without it

Superseded PRs

Supersedes #443, #444, #445, #446, #447, #448. GitHub's closing keywords only auto-close issues, not PRs, so those six need closing by hand after this merges — or renovate will autoclose them itself on its next scheduled run (Monday before 6am).

🤖 Generated with Claude Code

Consolidates the six open renovate PRs into one branch, following the
precedent set by #435: one CI run and one review instead of six, and no
Cargo.lock conflict cascade from merging them one at a time.

All six are lockfile-only patch bumps within the existing Cargo.toml
constraints — no manifest changes:

| Crate                 | From → To       | Was  |
|-----------------------|-----------------|------|
| clap                  | 4.6.1 → 4.6.6   | #443 |
| clap_complete         | 4.6.7 → 4.6.9   | #447 |
| clap_complete_nushell | 4.6.0 → 4.6.1   | #444 |
| futures-util          | 0.3.32 → 0.3.33 | #445 |
| http-body-util        | 0.1.3 → 0.1.4   | #446 |
| keyring               | 4.1.5 → 4.1.6   | #448 |

clap_builder, clap_derive and the futures-core/io/macro/sink/task
family move alongside their parents; the resulting diff is exactly the
union of the six PRs with no extra crates picked up.

Supersedes #443, #444, #445, #446, #447, #448 — those PRs need closing
by hand, since GitHub's closing keywords apply to issues, not PRs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mpecan
mpecan force-pushed the chore/renovate-batch-2026-08 branch from 9b12269 to 1f57a4f Compare August 7, 2026 14:58
@repository-butler

Copy link
Copy Markdown
Contributor

Filter Verification Report

Changed Filters

No filter files changed in this PR.

All Filters Summary

✅ 151/151 test cases passed across 51 filters


Generated by tokf verify

Both versions in the lockfile were yanked from crates.io. Neither is
reachable from a direct dependency — they arrive via axum -> multer and
aws-sdk-s3 -> aws-smithy-checksums -> crc-fast — so nothing surfaced
them until a supply-chain check went looking.

Each has a non-yanked successor inside the same semver range, so this is
a lockfile-only move with no manifest change:

  spin 0.9.8  -> 0.9.9   (via axum 0.8.9 -> multer 3.1.0)
  spin 0.10.0 -> 0.10.1  (via aws-sdk-s3 1.137.0 -> crc-fast 1.10.0)

A yank is the mechanism by which a compromised release is withdrawn from
crates.io, so sitting on yanked versions is worth clearing regardless of
why these two were pulled.

Found by `cargo deny check advisories`, which is being added to CI
separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mpecan mpecan changed the title chore(deps): batch six renovate lockfile bumps chore(deps): batch six renovate lockfile bumps + clear yanked spin Aug 7, 2026
@mpecan
mpecan merged commit 221c471 into main Aug 7, 2026
5 checks passed
@mpecan
mpecan deleted the chore/renovate-batch-2026-08 branch August 7, 2026 15:32
mpecan added a commit that referenced this pull request Aug 7, 2026
Two asks, one branch: make renovate open a single batched PR, and harden
dependency upgrades against compromised releases.

> **Depends on #452.** The new `cargo deny` job fails on this branch
alone, because `main` currently pins two **yanked** versions of `spin`.
#452 clears them. Merge that first and this goes green; the failure is
the new check doing its job.

---

## 1. Renovate: one PR instead of six

| | Before | After |
|---|---|---|
| Cargo patch updates | one PR each | batched into a single `cargo
dependencies` PR |
| Cargo minor updates | grouped separately | same batch |
| Major updates | individual | individual (unchanged) |
| New release age | none — proposed within hours | **quarantined 7
days** |
| Security fixes | same queue as everything | exempt from quarantine
*and* schedule |
| Automerge | configured, never fired | removed |

**Batching.** The old config grouped only *minor* updates, so patch
bumps — the overwhelming majority — each got a PR. Six were open when I
started, all touching only `Cargo.lock`, so merging any one put the
other five into conflict.

**Quarantine.** `minimumReleaseAge: "7 days"` holds a new version back
before proposing it. A compromised release is usually caught and yanked
within a day or two; against the existing Monday schedule this costs no
real latency. `vulnerabilityAlerts` overrides both the age and the
schedule so security fixes come through immediately — a known-exploited
hole is worse than an unvetted release. `osvVulnerabilityAlerts` adds
the OSV database as a source for those.

**Automerge removed.** It could never fire — see the analysis in #452.
Rather than grant the bot a ruleset bypass, I dropped the dead config:
auto-merging dependency bumps with nobody looking is precisely what the
quarantine exists to prevent. If you'd rather have working automerge,
say so and I'll add renovate as a bypass actor instead.

Validated with `renovate-config-validator`.

## 2. cargo-deny gate

New `supply-chain` CI job running `cargo deny check advisories bans
sources`, wired into the aggregate `Check` job so it actually blocks.
Policy in `deny.toml`:

- **advisories** — RustSec reports, plus **yanked** crates. A yank is
the mechanism by which a compromised release is withdrawn from
crates.io.
- **bans** — wildcard version requirements, which accept any future
release sight-unseen.
- **sources** — crates.io only. A dependency that suddenly resolves to a
git repository is the shape a takeover attack takes, and would otherwise
pass unnoticed in a lockfile diff.

It found something on its first run: two yanked `spin` versions, reached
indirectly via `axum → multer` and `aws-sdk-s3 → crc-fast`. Fixed in
#452.

One caveat, documented in `deny.toml`: dev-dependencies are excluded
from the graph. `crates/tokf-server` dev-depends on **itself** under an
alias to enable its `test-helpers` feature, and `krates` (inside
cargo-deny 0.20.2) hits an `unreachable!()` on that node. Worth
revisiting when that's fixed upstream.

## 3. Actions pinned to commit SHAs

All 17 actions now use a full SHA with the version in a trailing
comment. **Three were on `@master`** — `dtolnay/rust-toolchain` (×5) and
`superfly/flyctl-actions` — where whoever controls that repository can
repoint the ref at new code, which then runs here with our token and
cache. This is the `tj-actions/changed-files` attack class.

Two of these derive their behaviour from the *ref name*:
`dtolnay/rust-toolchain@stable` and `taiki-e/install-action@nextest`
read the ref to pick a default. The defaults turn out to be baked into
`action.yml` at each pinned SHA (verified), so pinning alone doesn't
break them — but I've made `toolchain:` and `tool:` explicit anyway,
since relying on that would break silently on a future re-pin.

Renovate keeps the digests current via `helpers:pinGitHubActionDigests`.

## 4. Checkout credentials and permissions

**`persist-credentials: false`** on all 13 checkouts that don't push.
Without it the token is left in `.git/config`, where a `build.rs` or
proc macro compiled later in the same job can read it — a real concern
for a Rust repo, where every build executes third-party code. The two
Homebrew tap checkouts in `release.yml` are exempt because they push
with those credentials, and now carry a comment saying so.

**Explicit `permissions:`** on the three workflows that had none. The
repo default is already read-only, so no behaviour changes today — this
stops a future change to that default from silently widening them.
`verify-comment.yml` gets the tightest treatment as the one genuinely
privileged workflow: it runs via `workflow_run` in the base repo with
secrets even for fork PRs, so GITHUB_TOKEN gets only `contents: read` +
`actions: read`, and the comment is posted with a scoped app token.

**`--locked`** on every cargo invocation, so CI fails rather than
silently resolving versions differing from `Cargo.lock`.

## Verification

- All nine workflows parse; **no duplicate keys** (checked with a strict
YAML loader, not just `safe_load`)
- No mutable action refs remain
- Every cargo invocation carries `--locked`
- `renovate-config-validator` — passes
- `cargo deny check advisories bans sources` — `bans ok, sources ok`;
advisories pass once #452 lands
- `cargo clippy --workspace --all-targets -- -D warnings` and `cargo
test --workspace` (2468 passed) — unaffected, no Rust changes

## Not done

- **`sha_pinning_required`** is available as a repo setting (currently
off) and would enforce rule 3 for good. Not flipped — it's a repo
setting, not a file, and it's your call.
- **`cargo vet` / `cargo crev`** — crate-level review attestations.
Materially more process; deferred.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant