# Fix Vulnerable Dependencies in a Rust Project

Yes. An agent runs cargo audit on your Rust workspace, applies the smallest lockfile change per advisory, reruns the CI matrix and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/fix-vulnerable-dependencies/rust/), in a Project that has your repository.

## What the agent does

The agent reads the workspace before it touches the lockfile. In a project shaped like rust-lang/mdBook, `Cargo.toml` is a workspace with the root `mdbook` crate, eight crates under `crates/`, an example preprocessor and the guide helper, with every external dependency's version set once in `[workspace.dependencies]` and inherited with `workspace = true`; `rust-version` is 1.88.0 and `edition` 2024. `Cargo.lock` is lockfile version 4 with 273 packages, and `.github/workflows/main.yml` already has an `audit` job that installs `cargo-audit` with `--locked` and runs `cargo audit`, feeding the `success` gate that branch protection requires. `CONTRIBUTING.md` tells contributors to run `cargo audit` from the root, and `ci/update-dependencies.sh`, run monthly by `update-dependencies.yml`, does `cargo upgrade` and `cargo update` on an `update-dependencies` branch, so the agent respects that convention and keeps its own change to what the advisories need.

It runs `cargo audit --json` and reads each advisory: the vulnerable crate and version, the patched versions, and the dependency path back to a workspace crate, which `cargo tree -i <crate>` shows. For a direct dependency with a patched version inside the current requirement, `cargo update -p <crate>` moves only that entry in `Cargo.lock`; when the fix needs a new minor or major, the agent edits the version in `[workspace.dependencies]` so every crate inherits it, and leaves `pulldown-cmark` alone, which the manifest marks "Do not update, part of the public api". For a transitive dependency it updates the parent that pulls it in, and only when no release of any parent fixes it does it add an `[patch]` or an ignore entry in a `.cargo/audit.toml` with the advisory id and a comment, which the pull request calls out.

Then it proves the change on the matrix `main.yml` runs: `cargo test --workspace --locked` on stable, the no-default-features build the test job also runs, `cargo test --locked --test gui` where `browser-ui-test` is available, `cargo clippy --workspace --all-targets --no-deps -- -D warnings`, `cargo doc --workspace --document-private-items --no-deps` with `RUSTDOCFLAGS=-D warnings`, `cargo fmt --check` under the `style_edition = "2024"` in `rustfmt.toml`, and a build on the 1.88.0 toolchain, because a dependency bump that raises the minimum supported Rust version fails the `msrv` job. `cargo semver-checks --workspace`, which the `check-version-bump` job runs, is the last check, since a public type from a bumped crate can change the published API of `mdbook-core` or `mdbook-renderer`.

The pull request changes `Cargo.lock` and, where a requirement moved, `Cargo.toml`. Its description quotes the `cargo audit` output before and after, lists one line per upgrade with the advisory it fixes and the crates that depend on it, names any advisory left open with the reason, and quotes the test summary. Nothing under `src/` or `crates/` changes unless a bumped crate's API required it, and that change gets its own paragraph.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Audit this Rust workspace's dependencies and fix the advisories. Read Cargo.toml, Cargo.lock, rust-toolchain files if any, .github/workflows/main.yml, ci/update-dependencies.sh and CONTRIBUTING.md first. Run cargo audit --json and save the output. For each advisory use cargo tree -i to find the path from a workspace crate, then apply the smallest fix: cargo update -p for a patched version inside the current requirement, a bump in [workspace.dependencies] when a new minor or major is needed, the parent crate when the vulnerable crate is transitive, and never a bump of a dependency the manifest marks as part of the public API. Add an ignore in .cargo/audit.toml only when no fix exists, with the advisory id and the reason. Run cargo test --workspace --locked, the no-default-features build, cargo clippy --workspace --all-targets --no-deps -- -D warnings, cargo doc with RUSTDOCFLAGS=-D warnings, cargo fmt --check, a build on the rust-version toolchain, and cargo semver-checks --workspace, then cargo audit again. Open a pull request with the audit output before and after, one line per upgrade with its advisory, every advisory left open with the reason, and the test summary. Change no source file unless a bumped crate's API forces it, and explain any such change.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on a new branch whose diff is `Cargo.lock` and, when a requirement had to move, `Cargo.toml`. The description has the first `cargo audit` output, a table of upgrades with crate, old and new version, advisory id and the workspace crates that depend on it, the second `cargo audit` output, any advisory left open with why, and the summaries from `cargo test`, `clippy`, `doc`, the MSRV build and `semver-checks`.

The session read names the repository and the pull request URL, and the transcript keeps each `cargo tree -i` and each test run, so a reviewer can see why the agent bumped a parent crate instead of the vulnerable one. When the pull request's own `audit` job runs it should pass, because the same command passed in the sandbox on the same lockfile.

Before merging, a reviewer checks any `Cargo.toml` change against the public API rule, reads the `semver-checks` output for a bumped crate that leaks into `mdbook-core`'s types, and confirms that an ignored advisory, if there is one, really has no fix rather than one that needs a major bump.

## Run it on every failed audit job on main

Create an automation in the Project with this prompt and a `github` trigger on `workflow_run.failure`, with `branch_patterns` matching your default branch; a new RustSec advisory makes the `audit` job in `main.yml` fail on the next run, and that failure starts the fix. The trigger adds the workflow event after the prompt, so the agent reads which job failed from it. Only failures of runs by an owner, member or collaborator fire, never runs from forks.

A `schedule` trigger with `every: 1d` covers advisories published between runs: each fire starts a run that audits and, when there is nothing to fix, opens no pull request and says so in its result. From your own server, the same run starts with `POST /o/{org}/sessions` and this body, with a `request_id`, one UUID kept per advisory, so one advisory means one run.

## Why run it on Rush?

Fixing a Rust advisory properly means a lockfile change checked on stable, on the 1.88.0 toolchain, with clippy and doc warnings as errors, with the GUI tests that need `browser-ui-test` from npm, and with a semver check across nine crates. On Rush that runs in a sandbox with the toolchains installed for the run and every command's output kept, so the pull request quotes results a reviewer can open rather than a note that says the tests passed.

Advisories arrive at their own pace, and each one gets its own run, its own sandbox and its own pull request, several at a time if several land. The monthly `update-dependencies` branch keeps doing the broad update; this run does the narrow one the day the advisory appears.

## Frequently asked questions

### Will it run cargo update on everything?

No. A full `cargo update` is what `ci/update-dependencies.sh` does once a month on its own branch. This run moves only the entries each advisory needs, with `cargo update -p`, so the diff is reviewable and the two processes do not fight.

### What about pulldown-cmark, which must not be updated?

The manifest comment marks it as part of the public API, and the prompt forbids bumping any crate so marked. If an advisory lands in it, the pull request reports the advisory as open with that reason instead of changing the requirement.

### Does it respect the minimum supported Rust version?

Yes. It builds on the 1.88.0 toolchain the `msrv` job uses, and a dependency whose patched version needs a newer compiler is reported rather than applied, because that bump is a project decision that also changes `installation.md`.

### What if no patched version exists?

The agent adds an ignore entry in `.cargo/audit.toml` with the advisory id and a comment, lists it in the pull request as left open, and the `audit` job passes with that entry visible in the diff for the reviewer to accept or reject.
