# Code Index & CVE Exposure

A new CVE drops. Two questions matter right away: which of the releases running right now are on an affected version, and does your code actually call the vulnerable function? Bumpkit answers both. Then it opens the fix pull request for you.

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it.

| Feature | What it does | Flag |
|---------|--------------|------|
| Code index | Blast radius follows exact calls, and every vulnerable dependency says whether your code reaches the affected function, with the call path. | `code_index` |
| CVE exposure | Lists every live release and environment on an affected version of an OSV, CVE or GHSA id. | `code_index` |
| Fix pull request | Opens the smallest upgrade that leaves the affected range as a pull request. | `dependency_autopilot` |

## Upload a code index

A code index is the `index.scip` file a SCIP indexer writes for one commit. Bumpkit keeps only the symbols and references it derives from the index, never your source, and keeps the newest 50 indexes per repository.

Run the indexer for your language in the repository root, on the commit you build, then upload the file:

```bash
bumpkit index upload index.scip
```

`--commit` defaults to `HEAD` and `--repository` to the origin remote. From an AI agent, the `index_upload` MCP tool does the same.

### scip-go

For Go modules. Install scip-go from its release page, then run it in the module root.

```bash
scip-go
bumpkit index upload index.scip
```

### scip-typescript

For TypeScript and JavaScript projects. Install your dependencies first so the indexer can resolve imports.

```bash
npm install -g @sourcegraph/scip-typescript
scip-typescript index
bumpkit index upload index.scip
```

### scip-java

For Java, Kotlin and Scala builds with Maven, Gradle or sbt.

```bash
scip-java index
bumpkit index upload index.scip
```

### scip-python

For Python projects. Install your dependencies into the active environment first.

```bash
npm install -g @sourcegraph/scip-python
scip-python index . --project-name=your-project
bumpkit index upload index.scip
```

### GitHub Actions

Upload an index on every push to your default branch and every pull request, after the step that builds and installs your indexer:

```yaml
- name: Index the code
  run: scip-go
- name: Upload the index
  run: bumpkit index upload index.scip
  env:
    BUMPKIT_API_KEY: ${{ secrets.BUMPKIT_API_KEY }}
```

## What reachability shows

When a release check finds a dependency with a known vulnerability, and the advisory names the affected functions, Bumpkit searches the code index for that commit:

- **Reachable** (`cve_reachable`): your code calls the affected function. The finding shows the call path from the entry point to the vulnerable function, with the file and line of every hop.
- **Not reachable** (`cve_unreachable`): none of the functions in the index reach it. The finding says how many functions and references were searched.
- **Reachability not measured**: either no code index was uploaded for the commit, or the advisory names no affected functions. The finding says which one and what to do.

You see the same result in the release check, the blast radius on the web, `bumpkit blast-radius` and the `blast_radius` MCP tool. Put `cve_reachable` in `gate.block_on` to block high and critical vulnerabilities your code reaches:

```yaml
gate:
  block_on: [cve_reachable]
```

## CVE exposure

Look up any OSV, CVE or GHSA id to see every live release and environment whose dependencies include an affected version:

```bash
bumpkit cve exposure CVE-2024-45337
```

On the web, open **Releases** and pick the **CVE exposure** tab. From an AI agent, use the `cve_exposure` MCP tool.

The result leads with a verdict: **Exposed** with how many releases run an affected package, **Not exposed** with how many live releases were checked, or **Not checked** with the reason. Owners and admins can open the fix pull request from the verdict. The CLI prints the verdict first, and the API and the `cve_exposure` MCP tool return it as `verdict`.

Below the verdict, Bumpkit shows the advisory as OSV publishes it:

- The severity, with the CVSS score, level and vector. Bumpkit scores CVSS 4.0 and 3.x vectors and prefers the newest version OSV lists.
- When the advisory was published and last modified.
- The full description, CWE ids and aliases.
- Every affected package with its affected version ranges and fixed versions, such as `golang.org/x/crypto: fixed in v0.31.0`.
- Every affected repository OSV tracks by commit, such as `go.googlesource.com/crypto: fixed in commit b4f1988`. Each commit links to the exact commit on GitHub, GitLab or googlesource; the CLI prints the commit URL.
- References grouped as Advisory, Fix, Report and Web.

Anything OSV doesn't provide is left out.

A CVE record often lists only the fixing commit. Bumpkit follows its GHSA and Go advisory aliases and uses their package ranges, so looking up a CVE id matches the same releases as its GHSA or GO id. When aliases disagree, the range with an explicit fixed or last affected version wins; between two explicit ranges, the ecosystem's own database wins (the Go vulnerability database for Go, GitHub advisories for everything else). Bumpkit never widens a range beyond what an advisory states.

For each live release, Bumpkit shows:

- The repository, environment, deployed commit and version, from your deploy events.
- The affected package, the installed version and the smallest fixed version.
- Where the dependency list came from: the code index for the deployed commit when you uploaded one, otherwise the repository's lockfiles and manifests at the deployed commit.
- Whether that release's code reaches the vulnerable function, with the call path, when a code index exists for the deployed commit.

A live release Bumpkit can't prove is on an exact version is listed as **Not checked** with the reason and what to upload: a version range with no lockfile, manifests from a different commit than the one deployed, a development-only dependency, or no dependency data at all. It is never hidden, and it never counts as exposed.

Bumpkit learns what's live from deploy events. Until your workspace has recorded a deploy, exposure says so and links to [Record deploys](/docs/release-health#record-deploys).

## Open the fix pull request

Owners and admins can open the fix from the exposure view, or with:

```bash
bumpkit cve fix CVE-2024-45337 --repository acme/api
```

The `cve_fix` MCP tool does the same. Bumpkit picks the smallest version that leaves every affected range, edits your manifest, lists anything it can't change mechanically as a TODO, and opens the pull request through the GitHub App. Bumpkit's release check runs on the pull request as soon as it opens, like on any other. Bumpkit never merges it: review it, finish the TODOs and merge it yourself. Running the fix again returns the pull request that is already open.

## Vulnerability alerts

You don't have to look up a CVE to find out you're exposed. Bumpkit watches newly published and updated advisories and checks each one against every live release in your workspace, every 10 minutes. When one affects a release you ship, you hear about it within minutes.

### What triggers an alert

An alert fires only when Bumpkit can prove a live release ships an affected version:

- **Exact package.** The ecosystem and name match exactly: the Go module path including any `/v2` suffix, the npm name including its scope, the normalized PyPI name. The same name in another ecosystem never matches.
- **Exact version.** The installed version comes from the code index for the deployed commit, a lockfile, or a pinned version in the manifests at the deployed commit. Ranges with no lockfile, manifests from another commit, and development or optional dependencies show as **Not checked** on the exposure tab and never alert.
- **Inside the affected range.** Versions are compared with the ecosystem's own rules, semver for Go, npm and crates.io and PEP 440 for PyPI, including pre-releases and the advisory's exact bounds.
- **Used.** When a Go advisory names the affected packages and the code index shows the release never imports them, the release shows as **Present but not used** and doesn't alert.

Bumpkit reads exact versions from these lockfiles: `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` and `bun.lock` for npm; `poetry.lock`, `uv.lock`, `Pipfile.lock` and `pdm.lock` for Python; `gradle.lockfile` for Gradle; `packages.lock.json` for .NET; `Cargo.lock`, `Gemfile.lock`, `composer.lock`, `mix.lock`, `pubspec.lock` and `Podfile.lock`. `poetry.lock`, `uv.lock`, `pdm.lock` and `Pipfile.lock` can list packages that only install on some platforms, so a match found only in one of them shows on the exposure tab as **Not checked** with the matched version and never alerts. Upload a code index for the deployed commit to check those releases.

Severity comes from the advisory and reachability from your code index, never guessed. A withdrawn advisory never alerts, and an alert already sent is retracted with a follow-up message.

### Where alerts go

Each match is reported once per release, with the same wording everywhere: in the inbox, in Slack (your Slack App channel, or your Slack webhook) and by email to owners and admins. The message gives the severity, the advisory id, each affected release and environment with the installed and fixed versions, whether your code reaches the affected function with the call path, and a link to the exposure tab. A reachable critical or high advisory also opens an incident. You're alerted again only when the severity rises or another release becomes affected.

Newly published advisories apply to release checks right away, so a pull request that adds an affected version is flagged on its next check. Put `cve_reachable` in `gate.block_on`, or set `vulnerabilities` in your policy, to block on them.

### Settings

| Setting | Values | Default |
|---------|--------|---------|
| Vulnerability alerts | `all`, `reachable` (your code calls the affected function), `critical_high`, `off` | `all` |
| Open fix pull requests automatically for | `off`, `critical`, `critical_high` | `off` |

With automatic fixes on, Bumpkit opens the smallest upgrade out of the affected range as a pull request, exactly like `bumpkit cve fix`. The release check runs on it, and Bumpkit never merges it.

Owners and admins change these on the web under **Settings**, or with:

```bash
bumpkit settings set --vulnerability-alerts reachable --auto-fix-prs critical_high
```

The `settings_set` MCP tool and `PATCH /v1/settings` take `vulnerability_alerts` and `auto_fix_prs`. Every change is recorded in your workspace audit log.

### Recent alerts

```bash
bumpkit cve alerts
```

On the web, the **CVE exposure** tab lists recent alerts. From an AI agent, use the `cve_alerts` MCP tool.

## API

| Method | Path | What it does |
|--------|------|--------------|
| `POST` | `/v1/code-index?repository=OWNER/NAME&commit=SHA` | Upload an `index.scip` file as the request body. |
| `GET` | `/v1/code-index/edges?repository=OWNER/NAME&commit=SHA` | The stored index for a commit: `found`, `commit`, `edges` (each with `from`, `to` and `line`) and `vulnerabilities`. `bumpkit blast-radius` and the `blast_radius` MCP tool read it. |
| `GET` | `/v1/cve/{id}/exposure` | Live releases on an affected version, with reachability per release. |
| `POST` | `/v1/cve/{id}/fix` | Open the fix pull request. Body: `{"repository": "owner/name"}`. Owners and admins only. |
| `GET` | `/v1/cve/alerts?limit=50` | Recent vulnerability alerts, newest first, up to 200, with the workspace's alert settings. |

Each endpoint is rate limited per API key, and every upload and fix is recorded in your workspace audit log.
