Skip to content

ci: add scheduled cargo-deny AI auto-fix with shared root deny.toml - #4899

Open
deniallugo-ml wants to merge 26 commits into
mainfrom
use-common-cargo-deny
Open

ci: add scheduled cargo-deny AI auto-fix with shared root deny.toml#4899
deniallugo-ml wants to merge 26 commits into
mainfrom
use-common-cargo-deny

Conversation

@deniallugo-ml

@deniallugo-ml deniallugo-ml commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

Adds a proactive, scheduled cargo-deny system covering all four Cargo workspaces (core, prover, zkstack_cli, airbender_prover_server) instead of only core on pull_request, and consolidates cargo-deny policy into a single shared deny.toml at the repo root.

  • deny.toml (moved from core/deny.toml): shared config for all workspaces. One policy addition vs. core's config: CDDL-1.0 is now allowed — required by inferno (flamegraph rendering), a mandatory transitive dependency of zksync-airbender's riscv_transpiler in airbender_prover_server. It's weak file-level copyleft, similar in spirit to the already-allowed MPL-2.0. This is the one policy call in this PR — please review it explicitly.
  • .github/workflows/cargo-deny-ai-fix-reusable.yml: reusable detect → ai-fix → publish workflow. detect runs cargo-deny; if red and nothing is already open for that workspace (open fix PR or open diagnosis issue, deduped via cargo-deny-fix / cargo-deny-diagnosis + workspace:<slug> labels), ai-fix hands the failure to Claude. It never merges, and policy decisions (disallowed license with no alternative, advisory with no upstream fix, unapproved source) are deliberately left unresolved and labeled needs-security-review / needs-license-review for a human. Supports dry-run (diagnosis issue instead of PR). Long-term this file moves to matter-labs/zksync-ci-common, referenced by full commit SHA (never @main).
  • .github/workflows/cargo-deny-ai-fix.yml: caller — daily cron + push-to-main + manual dispatch, matrix over the four workspaces, arguments matching cargo-license.yaml exactly so both checks agree on what is red.
  • .github/workflows/cargo-license.yaml: minimal change pointing the existing PR-time gate at the shared root config (--config ../deny.toml — in cargo-deny 0.18.x, bundled by the action, --config is a check argument whose relative path resolves from the workspace root directory).

Security design (after two review rounds)

  • Job-level credential isolation: untrusted code (build scripts / proc macros under cargo build/cargo test) runs only in the read-only ai-fix jobcontents: read GITHUB_TOKEN, no publishing credential anywhere on that runner, persist-credentials: false, no gh in allowedTools. Claude only edits files (no git at all — planted core.fsmonitor/diff.external/hooks would otherwise execute in its credentialed environment); a step running after the Claude process exits captures the tree as a raw binary diff (--no-ext-diff --no-textconv --no-renames) and exports diff + report as an artifact. A separate publish job on a fresh runner (where nothing untrusted ever executed) validates the diff touches only the workspace (or the declared policy-path), applies it, creates a single bot commit, pushes, and opens the PR/issue. Only that job mints the App token / reads the PAT.
  • Credential preference: repository-scoped GitHub App installation token minted in the publish job (GH_APP_ID + GH_APP_PRIVATE_KEY, short-lived), release-please bot PAT (RELEASE_TOKEN) as validated fallback. Bot-authored PRs trigger CI.
  • Fingerprinted dedup, no paid retry loop, no suppression: detect runs the pinned cargo-deny 0.18.6 CLI with --format json and hashes the complete normalized error diagnostics (multiplicity preserved — license rejections share one message in 0.18.x, so a deduplicated summary could hide a newly added disallowed dependency). Both fix PRs and cargo-deny-diagnosis issues embed that fingerprint; the gate skips re-runs only while the fingerprint still matches, so neither a PR awaiting review nor a long-open human decision can mask newly introduced findings — a changed set force-updates the existing PR branch (with a comment) or refreshes the issue. Green checks auto-close stale diagnosis issues.
  • Correct toolchains: all cargo commands run from the workspace directory with its pinned toolchain pre-installed — rustup resolves rust-toolchain files from the cwd, not --manifest-path.
  • Supply chain: all third-party actions pinned to full commit SHAs; the future central copy in matter-labs/zksync-ci-common must be referenced by full SHA, never @main.
  • API-key shielding: a PATH-shadowing cargo wrapper strips ANTHROPIC_API_KEY (and other credential vars) from every cargo invocation, so build scripts / proc macros don't inherit it. Wrapper bypasses are closed: no rustup (skips the wrapper) and git explicitly denied via --disallowedTools "Bash(git:*)" (read-only git is otherwise part of Claude Code's default toolset; deny rules take precedence). Residual same-UID /proc/<pid>/environ exposure is documented — use a spend-capped workspace key; move to workload identity federation when available.
  • Rename-proof patch boundary: the diff is generated with --no-renames and the publish job validates NUL-separated git status paths against the workspace + policy-path, so a rename can't smuggle a deletion outside the boundary.
  • Race avoidance: max-parallel: 1 serializes workspaces within a run, and a caller-level repo-wide concurrency group serializes overlapping schedule/push/manual runs. Default single-pending replacement is intentional: the group spans the whole run, so nothing is lost when a newer pending run supersedes an older one, and busy-main pushes can't queue up dozens of expensive AI runs.
  • Caller token grant: reduced to contents: read, pull-requests: read, issues: write.

Validation

Ran all four workspaces locally with the exact cargo-deny 0.18.6 binary the action bundles and CI's exact flags:

workspace result
core ✅ fully green (confirmed in this PR's CI)
prover licenses/bans/sources ✅, 8 advisory errors (patched versions available upstream)
zkstack_cli licenses/bans/sources ✅, 14 advisory errors (patched versions available upstream)
airbender_prover_server licenses/bans/sources ✅, 3 advisory errors (patched versions available upstream)

The remaining advisory errors are real, cargo update-fixable findings — fixing them is exactly the new workflow's job: on its first scheduled run it should open one fix PR per red workspace. Workflows pass actionlint and prettier.

Setup needed before the first run

  • Anthropic credential: exactly one of ANTHROPIC_API_KEY (ideally a dedicated spend-capped workspace key) or CLAUDE_CODE_OAUTH_TOKEN — validated up front. WIF is documented as future hardening.
  • Publishing credential: ideally provision a repo-scoped GitHub App and add GH_APP_ID/GH_APP_PRIVATE_KEY; until then the caller passes RELEASE_TOKEN as GH_PAT.
  • After merge, trigger once via workflow_dispatch and sanity-check the detect gate output.

🤖 Generated with Claude Code

Adds a proactive, scheduled cargo-deny system covering all four Cargo
workspaces (core, prover, zkstack_cli, airbender_prover_server) instead
of only core on pull_request:

- Move core/deny.toml to the repo root as a single shared config for all
  workspaces (plus CDDL-1.0, required by `inferno` via zksync-airbender's
  riscv_transpiler in airbender_prover_server). cargo-deny does not
  auto-discover a root config from nested workspaces, so every invocation
  passes `--config deny.toml` explicitly (before the `check` subcommand,
  i.e. via cargo-deny-action's `arguments` input).
- cargo-deny-ai-fix-reusable.yml: reusable detect -> ai-fix workflow that
  runs cargo-deny on a schedule and, when red with no open fix PR for that
  workspace, hands the failure to Claude to open a fix PR (never merges;
  policy decisions are left for humans and labeled needs-*-review).
- cargo-deny-ai-fix.yml: caller matrix over the four workspaces, with
  arguments matching cargo-license.yaml so both checks agree on what is red.
- cargo-license.yaml: point the existing PR-time gate at the shared root
  config, keeping the action's --all-features default explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread .github/workflows/cargo-deny-ai-fix.yml Fixed
PRs opened with the default GITHUB_TOKEN don't trigger on:pull_request
workflows, so the ai-fix job now pushes branches and opens PRs/issues
with the same bot PAT release-please uses. The GH_PAT secret is optional
in the reusable workflow and falls back to GITHUB_TOKEN, so repos
without a PAT still work (their fix PRs just need a manual CI kick).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread .github/workflows/cargo-deny-ai-fix.yml Fixed
deniallugo-ml and others added 8 commits July 16, 2026 14:27
Drop the GITHUB_TOKEN fallback - callers must always pass a bot PAT so
fix PRs reliably trigger CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cargo-deny-action v2.0.11 bundles cargo-deny 0.18.6, where --config is a
`check` argument (it only became a top-level argument in later releases)
and a relative path resolves from the workspace root directory, not the
cwd. Pass `--config ../deny.toml` via command-arguments instead of the
action's `arguments` input, and pin the interactively-installed CLI in
the ai-fix job to the same 0.18.6 so Claude's re-runs accept the same
syntax and produce identical results.

Validated locally with the actual 0.18.6 binary against all four
workspaces: core green; prover/zkstack_cli/airbender red only on real
fixable advisories (8/14/3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the CodeQL actions/missing-workflow-permissions alert. The caller
grants the maximum the reusable workflow's ai-fix job needs; the
reusable workflow downscopes its detect job to read-only itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Isolate the publishing credential from untrusted code: Claude now works
  offline (no gh in allowedTools, checkout with persist-credentials:
  false, no token in its environment - cargo build scripts and proc
  macros execute arbitrary code and inherit the job env). It commits
  locally and writes report files; a deterministic publish step pushes
  and opens the PR/issue.
- Prefer a short-lived repository-scoped GitHub App installation token
  (GH_APP_ID + GH_APP_PRIVATE_KEY, minted in-workflow) over the bot PAT;
  GH_PAT remains as fallback, validated up front.
- Run all cargo commands from the workspace directory and pre-install
  its pinned toolchain: rustup resolves rust-toolchain files from the
  cwd, not --manifest-path (zkstack_cli and airbender_prover_server pin
  different nightlies than the repo root).
- Break the paid retry loop for human-only findings: when nothing is
  safely fixable there are no commits, so the publish step opens or
  updates a cargo-deny-diagnosis issue instead of a PR, and the detect
  gate now dedups against open diagnosis issues as well as fix PRs.
- Pin all third-party actions to full commit SHAs; document that the
  future central copy (matter-labs/zksync-ci-common) must be referenced
  by full SHA, never @main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second security-review round:

- Same-job credential separation is not isolation: code that ran on the
  runner can plant git hooks or persist processes that observe a later
  step's token. The ai-fix job is now fully read-only (contents: read,
  no publishing credential anywhere on the runner) and hands its results
  (format-patch series + report) to a publish job on a fresh runner via
  an artifact. The publish job validates that patches stay inside the
  workspace (or its deny.toml), applies them with git hooks disabled,
  and is the only place the App token is minted / the PAT is read.
- Diagnosis-issue dedup is now fingerprint-based: detect runs the pinned
  cargo-deny 0.18.6 CLI with --format json and hashes the sorted set of
  (code, advisory id, message) error tuples; an open issue only
  suppresses re-runs while its embedded fingerprint matches, so a
  months-open human decision cannot mask newly introduced fixable
  findings. Green checks close stale diagnosis issues.
- Caller GITHUB_TOKEN grant reduced to contents/PR read + issues write.
- Matrix serialized with max-parallel: 1 - all four workspaces share the
  root deny.toml and concurrent AI edits to it could conflict. (A shared
  concurrency group would silently drop queued runs instead.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third security-review round:

- Fingerprint hashes the complete normalized error diagnostics (jq -cS
  on .fields) with multiplicity preserved instead of deduplicated
  (code, advisory, message) tuples: license rejections all share one
  message in cargo-deny 0.18.x, so a newly added disallowed dependency
  could previously hide behind an open diagnosis issue.
- Shield ANTHROPIC_API_KEY (and other credential env vars) from cargo
  subprocesses via a PATH-shadowing wrapper that execs the real cargo
  through `env -u ...` - build scripts and proc macros no longer inherit
  the key. Documented residual /proc/<pid>/environ exposure with the
  recommendation to use a spend-capped workspace key and workload
  identity federation when available.
- Patch path boundary uses git diff --no-renames: rename detection
  reported only the destination path, letting a rename smuggle a
  deletion outside the workspace boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fourth security-review round:

- Remove rustup from allowedTools (`rustup run <tc> cargo` would skip
  the credential-scrubbing PATH wrapper; the workspace toolchain is
  pre-installed so Claude never needs rustup) and scope git to explicit
  subcommands (`git -c alias.x='!cmd' x` would exec arbitrary commands
  with the original environment).
- Neutralize core.hooksPath in the ai-fix job: a build script could
  plant .git/hooks/* during cargo build and have it fire on Claude's
  own `git commit` with the API key present.
- Diagnosis-issue updates now remove both needs-*-review labels before
  re-adding what the new report warrants, so an issue whose finding set
  changed category no longer sits in the wrong review queue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rface

Fifth security-review round:

- Claude can no longer invoke git at all: a build script can plant
  executable git config (core.fsmonitor, diff.external, gpg.program,
  hooks) that would run inside Claude's environment - where the
  Anthropic credential lives - on its next git command. Claude now only
  edits files; a workflow step AFTER the Claude process exits (key gone
  from the runner) captures the tree as a raw binary diff with
  --no-ext-diff --no-textconv --no-renames and -c overrides. The publish
  job applies the diff on a fresh runner, validates the path boundary
  from NUL-separated git status, and creates a single bot commit.
- Fix PRs are now fingerprint-gated like diagnosis issues: an open PR
  suppresses re-runs only while its embedded fingerprint matches; a new
  finding set force-updates the existing PR branch, replaces its body
  and review labels, and leaves a comment - so a PR awaiting review
  cannot mask a newly introduced advisory.
- Caller gets workflow-level concurrency with queue: max, serializing
  overlapping schedule/push/manual runs (max-parallel only covers one
  run). Note: actionlint's schema doesn't know `queue` yet; GitHub docs
  confirm it (queues up to 100 runs vs the replace-pending default).
- Interface genericized for zksync-ci-common: ANTHROPIC_API_KEY or
  CLAUDE_CODE_OAUTH_TOKEN accepted (validated up front), and a
  policy-path input replaces the hardcoded root deny.toml in the publish
  path boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread .github/workflows/cargo-deny-ai-fix-reusable.yml Fixed
deniallugo-ml and others added 9 commits July 16, 2026 18:09
Sixth review round (merge-ready checklist):

- Explicitly deny Bash(git:*) to Claude: read-only git commands like
  `git status`/`git diff` are part of Claude Code's default read-only
  toolset even when git is absent from allowedTools, and they invoke
  attacker-plantable core.fsmonitor / diff.external inside Claude's
  credentialed environment. Deny rules take precedence.
- Route gate expressions through step env vars to clear the CodeQL
  command-injection alert (false positive - fingerprints are 64 hex
  chars - but red CI is red CI).
- Capture the diff against HEAD so staged changes can't vanish from the
  artifact if anything touches the index.
- Parse publish-side git status with --no-renames explicitly.
- Enforce exactly one of ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN;
  header now scopes WIF to future hardening instead of implying support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post-approval operational tweak: queue: max could accumulate up to 100
queued runs of four AI jobs each on a busy main, and stale queued runs
would regenerate the bot PR against old commits. Since the concurrency
group covers the entire caller run, the default single-pending
replacement loses no matrix entries - the newest pending run supersedes
everything an older one would have done, and the detect gate re-arms on
the next tick regardless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment-only pass plus three leftovers with no behavior impact:

- Drop the unused concurrency-key input (callers serialize shared-policy
  workspaces with workflow-level concurrency + max-parallel instead);
  the reusable group is keyed by manifest path.
- Drop the unused pr_number step output.
- Rename the fingerprint-extraction step to say what it does.

Comments now state the current design instead of narrating review
iterations, and the header is restructured into placement / pipeline /
credential isolation / design notes / setup sections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Trigger the caller on pushes to this branch (TEMPORARY, remove before
  merge) so the pipeline can be exercised without merging.
- Fix PRs target the branch the run executed on (GITHUB_REF_NAME) - the
  default branch for scheduled runs, unchanged behavior there.
- Remove the RUSTSEC-2026-0097 (rand 0.8, no upstream fix) ignore from
  deny.toml to turn core red with a deliberately unfixable finding: the
  expected outcome is a diagnosis issue with needs-security-review, NOT
  a deny.toml edit, verifying the policy guardrail. Restored after the
  test. The other three workspaces are genuinely red and should produce
  fix PRs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ai-fix job fails with "Unsupported event type: push" on push-driven
runs (claude-code-action supports entity events, workflow_dispatch,
repository_dispatch, schedule and workflow_run - not push). Rely on the
daily schedule plus workflow_dispatch, which can target any ref the
workflow file exists on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First live run burned 11 of 26 turns on permission denials and hit the
max-turns ceiling in 104s: the allowlist lacked the read-only utilities
Claude habitually uses (ls/cat/grep/...), and each denied command
silently costs a turn. Allow read-only utilities that expose nothing
beyond the Read tool, raise the default max-turns to 50, and enable
show_full_output so the session transcript is reviewable in the run log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second live run: Claude fixed all core findings (including rand via the
0.8.6 backport) and cargo deny went green, but then started
`cargo build --workspace --all-features`, backgrounded it, and ended its
turn "waiting" - the session terminates on end of turn, so the report
files were never written and packaging failed.

Full workspace builds don't fit inside an agent session on a repo this
size and duplicate what the fix PR's own CI does. The prompt now forbids
cargo build/test and background/long-running commands, and requires the
report files to be written before the session ends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-workspace PRs don't make sense to review and merge separately,
especially with a shared root deny.toml. The reusable workflow now takes
a whitespace-separated `manifest-paths` list: detect checks every
workspace and fingerprints the combined failing set, one Claude session
fixes all red workspaces, and publish opens (or force-refreshes) a
single PR - or a single diagnosis issue when nothing is safely fixable.

This drops the matrix, the per-workspace workspace:<slug> labels, and
the max-parallel serialization (a single job cannot race itself);
caller-level concurrency still serializes overlapping runs. Default
max-turns raised to 80 for the larger combined session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ines

${{ inputs.manifest-paths }} is textual substitution, so the multiline
list injected literal newlines into the for-statement (bash syntax
error). Iterate over an env var instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
deniallugo-ml and others added 7 commits July 17, 2026 12:37
Seventh review round:

- No transcript in logs and no shell read utilities for Claude: the
  credential-bearing process could print /proc-derived secrets, and
  partial output bypasses GitHub's exact-value masking. File exploration
  goes through Read/Glob/Grep tools only.
- The cargo wrapper now enforces the subcommand policy in tooling
  (deny/update/tree/metadata only) instead of prompt-only - none of
  these compile build scripts or proc macros, so no untrusted code runs
  in the ai-fix job at all.
- Path policy tightened to Cargo.toml/Cargo.lock inside failing
  workspaces + policy-path + caller-declared extra-allowed-paths; a
  repo-root workspace no longer grants the whole repository (workflow,
  source and script changes are rejected even for dir == ".").
- PR/issue dedup and refresh are scoped per base branch: PR queries use
  --base, diagnosis issues carry a cargo-deny-base marker, branch names
  embed the ref - a fix PR for one branch can no longer suppress or be
  hijacked by runs on another.
- New credential-free validate job applies the diff and runs
  caller-provided bounded validation commands (era: cargo check of
  zkstack_cli, whose compile break in the previous generated fix
  motivated this); on failure the PR is published as a DRAFT with an
  explicit unverified warning. The prompt also steers toward minimal
  --precise bumps to reduce breakage risk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generated fixes kept resolving cargo-deny while breaking
compilation (alloy-dyn-abi 1.4.1 pulls winnow 1.0.4, 139 E0277s caught
by the validate job and again by PR CI). Builds stay out of the
credentialed Claude job - instead, real compiler errors from the
credential-free validate job feed a second bounded Claude session:

- validate captures per-command error tails as an artifact on failure;
- repair applies the previous diff, reads the error log, and adjusts
  versions using cargo tree/update reasoning (same restricted wrapper,
  no compilation, no credentials beyond Anthropic);
- revalidate re-runs the caller's validation on the repaired set;
- publish picks the repaired artifact when available and only marks the
  PR draft if the final validation still failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Final review round:

- File tools scoped to the checkout (Read/Edit/Write/Glob/Grep with
  ./** specifiers): the model must not be able to read outside the
  repository (e.g. /proc) and surface secrets into published output.
- Repair input sanitized against prompt injection: only filtered
  compiler diagnostics (structured cargo JSON messages, falling back to
  rustc error/location lines) reach the repair session - never raw
  build-script stdout. Both prompts instruct treating all read content
  strictly as data.
- State markers hardened: publish strips any marker lines the untrusted
  AI report may contain before appending the trusted ones, and detect
  reads the LAST marker.
- Validation state made consistent: anything except a clean pass
  (including skipped - no validation configured) publishes as a draft,
  and regenerated PRs have their draft/ready state synchronized with
  the current validation outcome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# cargo-deny fix report

Ran `cargo deny --all-features check --config ../deny.toml --allow
unmaintained --hide-inclusion-graph` from
each workspace directory (the exact CI invocation) and fixed what was
safely fixable via `cargo update`.
Only `Cargo.lock` files were touched — no `Cargo.toml` or `deny.toml`
changes were needed anywhere.

All four workspaces failed on their first run (findings below), so this
is not a transient/already-green case.

## core

**Before:** `advisories FAILED` — 1 finding.

- `RUSTSEC-2026-0097` (unsound): `rand 0.8.5` — `ThreadRng` aliasing UB
under a custom logger.
Fixed: `cargo update -p rand@0.8.5 --precise 0.8.6` (patch bump,
smallest fix per the advisory's
  own solution range `>=0.8.6`).

**After:** `cargo deny check` passes (advisories/bans/licenses/sources
all `ok`). Remaining output is only
pre-existing warnings (yanked `core2`/`spin`, a couple of
`license-not-encountered` notices for allow-list
entries not hit in this workspace) — not check failures.

## prover

**Before:** `advisories FAILED` — 8 findings.

- `RUSTSEC-2026-0190` (unsound): `anyhow 1.0.98` — UB in
`Error::downcast_mut()`.
Fixed: `cargo update -p anyhow --precise 1.0.103` (solution range
`>=1.0.103`).
- `RUSTSEC-2026-0045`, `-0046`, `-0047`, `-0048` (vulnerabilities):
`aws-lc-sys 0.28.2` — AES-CCM timing
side-channel, two PKCS7 validation bypasses, and a CRL scope-check logic
error in AWS-LC.
Fixed: a plain `cargo update -p aws-lc-rs` (no `--precise`) picked up
the latest compatible `aws-lc-rs`
(`1.13.0 -> 1.17.1`), which pulled `aws-lc-sys` to `0.42.0` — well past
the `>=0.39.0` the strictest of
  these advisories required.
- `RUSTSEC-2026-0204` (vulnerability): `crossbeam-epoch 0.9.18` —
invalid pointer deref in `fmt::Pointer`.
  Fixed: `cargo update -p crossbeam-epoch --precise 0.9.20`.
- `RUSTSEC-2026-0012` (unsound): `keccak 0.1.5` — yanked, ARMv8 asm
operand-type UB.
  Fixed: `cargo update -p keccak --precise 0.1.6`.
- `RUSTSEC-2026-0097` (unsound): `rand 0.8.5` and `rand 0.9.1` both
present.
Fixed: `cargo update -p rand@0.8.5 --precise 0.8.6` and `cargo update -p
rand@0.9.1 --precise 0.9.3`.
- `RUSTSEC-2025-0055` (vulnerability): `tracing-subscriber 0.3.19` —
ANSI escape injection into logs.
  Fixed: `cargo update -p tracing-subscriber --precise 0.3.20`.

**After:** `cargo deny check` passes (advisories/bans/licenses/sources
all `ok`).

## zkstack_cli

**Before:** `advisories FAILED` — 14 findings.

- `RUSTSEC-2025-0073` (vulnerability): `alloy-dyn-abi 1.3.1` — panic/DoS
in `TypedData` hashing.
Fixed: `cargo update -p alloy-dyn-abi --precise 1.4.1` (solution range
`>=1.4.1` for the 1.x line). This
pulled a real transitive shift (bumped
`alloy-sol-types`/`alloy-primitives` family to 1.6.1 and swapped in
`sha3`/`secp256k1`/`winnow`/etc. in place of older crates) — flagging
this as a larger-than-usual
transitive move for reviewers to double check in CI, even though it's a
minor-version bump within semver.
- `RUSTSEC-2026-0190` (unsound): `anyhow 1.0.93` — same as prover.
  Fixed: `cargo update -p anyhow --precise 1.0.103`.
- `RUSTSEC-2026-0045`..`-0048` (vulnerabilities): `aws-lc-sys 0.24.0`.
Fixed: `cargo update -p aws-lc-rs` (`1.12.0 -> 1.17.1`), bringing
`aws-lc-sys` to `0.42.0`.
- `RUSTSEC-2026-0007` (vulnerability): `bytes 1.8.0` — integer overflow
in `BytesMut::reserve`.
  Fixed: `cargo update -p bytes --precise 1.11.1`.
- `RUSTSEC-2026-0204` (vulnerability): `crossbeam-epoch 0.9.18`.
  Fixed: `cargo update -p crossbeam-epoch --precise 0.9.20`.
- `RUSTSEC-2026-0012` (unsound): `keccak 0.1.5`.
  Fixed: `cargo update -p keccak --precise 0.1.6`.
- `RUSTSEC-2026-0097` (unsound): `rand 0.8.5` and `rand 0.9.1`.
  Fixed: precise updates to `0.8.6` and `0.9.3` respectively.
- `RUSTSEC-2025-0137` (vulnerability): `ruint 1.15.0` — unsound safe
`reciprocal_mg10` relying on
`debug_assert!`. Fixed: `cargo update -p ruint --precise 1.17.1`. Note:
this added an *unreachable*
`ark-ff 0.5.0` (and friends) resolution candidate to `Cargo.lock` —
confirmed with
`cargo tree -i ark-ff@0.5.0` that nothing in the graph actually
activates it, so it doesn't affect the
  build.
- `RUSTSEC-2025-0023` (unsound): `tokio 1.42.0` — broadcast channel
`Sync` unsoundness.
Fixed: `cargo update -p tokio --precise 1.42.1` (solution range
`>=1.42.1, <1.43.0`).
- `RUSTSEC-2025-0055` (vulnerability): `tracing-subscriber 0.3.18`.
  Fixed: `cargo update -p tracing-subscriber --precise 0.3.20`.
- `RUSTSEC-2025-0009` (vulnerability): `ring 0.17.8` (a second, separate
`ring` instance in the graph, via
`rustls 0.21.12`). Fixed: `cargo update -p ring@0.17.8 --precise
0.17.14`.

**After:** `cargo deny check` still has **1 unresolved finding** — see
"Needs human review" below.

## airbender_prover_server

**Before:** `advisories FAILED` — 3 findings.

- `RUSTSEC-2026-0190` (unsound): `anyhow 1.0.102`. Fixed: `cargo update
-p anyhow --precise 1.0.103`.
- `RUSTSEC-2026-0204` (vulnerability): `crossbeam-epoch 0.9.18`.
  Fixed: `cargo update -p crossbeam-epoch --precise 0.9.20`.
- `RUSTSEC-2026-0186` (unsound): `memmap2 0.9.10` — unchecked pointer
offset in `advise_range`/`flush_range`.
  Fixed: `cargo update -p memmap2 --precise 0.9.11`.

**After:** `cargo deny check` passes (advisories/bans/licenses/sources
all `ok`).

## Needs human review

- **`zkstack_cli`: `RUSTSEC-2025-0009`** — AES functions in `ring
0.16.20` may panic when overflow checking
is enabled (`ring::aead::quic::HeaderProtectionKey::new_mask()`, or
`AES_128_GCM`/`AES_256_GCM` after
~64GB in one chunk). The advisory's solution is "Upgrade to >=0.17.12",
but this specific `ring 0.16.20`
is pulled in by `jsonwebtoken 8.3.0` (`ring = "^0.16"`), which is pulled
in by `ethers-providers 2.0.14`
(part of the `ethers 2.0.14` crate family used throughout
`zkstack_cli`). `jsonwebtoken` 8.x has no
release compatible with `ring >=0.17` — that requires `jsonwebtoken`
9.x, which `ethers-providers`
doesn't accept (`cargo update -p jsonwebtoken` / `-p ethers-providers` /
`-p ethers` all reported "Locking
0 packages to latest compatible versions", i.e. no compatible bump
exists in the current dependency
graph). Actually fixing this needs either a semver-major `ethers`
upgrade (risky, unverifiable here,
and `ethers-rs` is a largely unmaintained project people are migrating
off of in favor of `alloy`) or
vendoring/patching around `ethers-providers`'s JWT usage — both are
policy/architecture decisions, not
something to force through an autonomous `cargo update`. Left
unresolved; `cargo deny check` in
  `zkstack_cli` will still report this one finding.


<!-- cargo-deny-base: use-common-cargo-deny -->
<!-- cargo-deny-fingerprint:
08566c1acc885d8d1605ecf8a8faf71c4eeb457b86a9ef3182792cb433a96b75 -->

Co-authored-by: cargo-deny-bot <cargo-deny-bot@users.noreply.github.com>
The merged AI fix (#4910) shipped a non-compiling zkstack_cli lockfile:
alloy-sol-type-parser 1.6.1 is source-incompatible with its own winnow
1.0.4 dependency (139 E0277s), breaking every CI job at zkstack build.
It was published as validated because the validate step's `| tee` runs
under the default `bash -e` shell WITHOUT pipefail - tee's exit code
masked the failing cargo check.

- zkstack_cli/Cargo.lock: pin the alloy sol-family coherently to 1.4.1
  (alloy-dyn-abi stays 1.4.1, satisfying RUSTSEC-2025-0073; winnow 1.0.x
  drops out of the graph entirely). Verified locally: zero dependency
  compile errors and cargo-deny reports only the known ring finding.
  All other #4910 fixes (core/prover/airbender lockfiles) are unaffected.
- Reusable workflow: set -o pipefail in the validate step so a failing
  validation can never again report success.

Co-Authored-By: Claude Fable 5 <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.

3 participants