# Compliance Evidence

Auditors ask who changed what, who approved it and why; Bumpkit keeps that record and exports it, for security, compliance and audit teams.

Bumpkit records every release check, gate decision, approval and override. This page covers how to hand that record to an auditor and to your security tooling. For how this evidence maps to specific controls, see [Security and Compliance](/docs/security-compliance).

| Feature | Flag |
|---------|------|
| [Evidence export](#evidence-export) | `compliance_export` |
| [Signed evidence](#signed-evidence) | `signed_evidence` |
| [Agent governance evidence](#agent-governance-evidence) | `governance_evidence` |
| [Audit streaming to a SIEM](#audit-streaming) | `siem_streaming` |
| [Auditor access](#auditor-access) | `auditor_access` |
| [Audit log](#audit-log) | none |

Every feature with a flag is Beta, in early access. Bumpkit turns each one on per workspace through early access.

### Who can export

Evidence exports (`GET /v1/evidence`, `/v1/evidence/signed`, `/v1/evidence/governance`) and the audit event export (`GET /v1/audit/events`) need an API key created by an owner, an admin or an auditor. Keys of any other role, and keys not tied to a person such as CI keys, get `403 ROLE_REQUIRED`:

```text
Exporting evidence needs the owner, admin or auditor role, and this API key acts as Viewer. Use a key created by someone with one of those roles.
```

Each refusal is written to the audit log as `evidence.export.denied` or `audit.read.denied`, with the request path as the target. To export from CI, use a key created by someone with the `auditor` role.

## Evidence export

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

```bash
bumpkit evidence export --days 90 -o evidence.json
bumpkit evidence export --from 2026-07-01 --to 2026-09-30 --format csv -o q3.csv
```

| Flag | Meaning |
|------|---------|
| `--days` | The last N days instead of `--from`/`--to` (1 to 730, default 90) |
| `--from`, `--to` | A date range, `YYYY-MM-DD` |
| `--format` | `json` (default) or `csv` |
| `-o`, `--output` | File to write (default: standard output) |

The API is `GET /v1/evidence?days=` or `?from=&to=`, with `&format=csv`. Owners, admins and auditors can also download it from the dashboard's Settings page.

### What's included

The JSON export has these parts:

| Part | Contents |
|------|----------|
| `records` | One per release check in the period: repository, pull request, base, head and head SHA, source, detected `agents`, verdict, version, finding count, proven high findings, summary, and for gated releases the decision ID, time, `outcome`, the `rules` that fired with their outcome, `approvers`, `approvals_source`, a fingerprint of the policy in force (`default` when there was none), and with [`require_regression_evidence`](/docs/bumpkit-yml#require-regression-evidence) on (Beta, flag `regression_evidence`), `regression_evidence`: the expected tests the decision saw, each with its verdict and why it was expected, and the CI run that attested them |
| `emergency_changes` | Every [break-glass override](/docs/hotfixes) in the period: who overrode which commit and why, the review due date, who reviewed it and their note, and its review `status` (including overdue) |
| `release_trains` | Every [release train](/docs/release-trains) cut in the period: name, version, tag, commit, verdict, pull requests, approvers, any break-glass reason, and who cut it and when |
| `provenance` | With AI change provenance on, an in-toto attestation for each change (see below) |
| `closed_loop` | With close the loop on (Beta, flag `close_the_loop`), each item found in the period with whom it was routed to, its fix pull request and the gate decision that verified it |
| `authorship` | In a [signed bundle](#signed-evidence), one entry per record: who wrote the change (`writer`, `tier`, `agents`), the agent `sessions` and the evidence `sources` behind it, the `approvers` and `approvals_source`, and, with the pause hook on (flag `agent_pause`), the `guard_events` agents hit on the change's files. Each entry carries the `record_hash` of its record, so it can't be moved to another change |
| `manifest` | Workspace, period, generation time, counts (records, gated, emergency changes, overdue reviews, release trains, and closed-loop and authorship entries when present), and the values that prove nothing was changed, removed or reordered |

Each gated record has `server_fetched`: `true` when Bumpkit read the diff from your repository, `false` when your CI sent it. Vanta and Drata evidence and change tickets from a `false` decision say it was reported by CI. The CSV export has one row per record with the record fields, except `regression_evidence`, whose outcome shows in `rules`. Emergency changes, release trains and the other parts are only in the JSON.

### Tamper-evident

Every export is tamper-evident: changing, removing or reordering any entry shows up when the export is verified. Use a [signed bundle](#signed-evidence) and `bumpkit evidence verify` to check it.

With AI change provenance on (Beta, flag `change_provenance`), the export also carries an in-toto provenance attestation for each change in the period. See [Agent Hooks](/docs/agent-hooks).

## Signed evidence

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

A signed bundle proves who produced the evidence, not just that it is intact.

```bash
bumpkit evidence export --signed --days 365 -o evidence.zip
```

The API is `GET /v1/evidence/signed` with the same period parameters. The zip contains:

| File | Contents |
|------|----------|
| `evidence.json`, `evidence.csv` | The evidence export |
| `governance.json`, `governance.csv` | [Agent governance evidence](#agent-governance-evidence), when that flag is on |
| `manifest.json` | Period, generation time, `key_id`, `algorithm` (`ed25519`), `public_key`, `key_url`, record counts, and a checksum and size for every file |
| `manifest.sig` | The base64 Ed25519 signature of `manifest.json`'s exact bytes |

Each signed export is written to the audit log as `evidence.export_signed`.

### Verify a bundle

```bash
bumpkit evidence verify evidence.zip --public-key-file bumpkit-key.json
bumpkit evidence verify evidence.zip --public-key PUBLIC_KEY
```

`bumpkit evidence verify` works offline. It checks the signature, every file, that no file is missing or extra, and that no evidence entry was changed, removed or reordered, including each change's authorship entry and that it still belongs to its change. Pin the signer's public key with `--public-key` (the raw 32-byte Ed25519 key, base64, as in the manifest's `public_key`) or `--public-key-file` (a file with the key, or the JSON published at the key URL). Without a pinned key it checks against the key inside the bundle, which proves integrity but not who signed it. `--expected-workspace ID` also fails a bundle from any other workspace, and needs a pinned key. A key file with `retired_at` fails any bundle generated after that time.

Public keys are published at:

```text
https://bumpkit.dev/.well-known/bumpkit-evidence-keys/<key_id>
```

This endpoint needs no API key and allows 60 lookups a minute per IP address.

## Agent governance evidence

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

Governance evidence answers one question for each agent-written change: was it reviewed by a human as policy required?

```bash
curl "https://bumpkit.dev/v1/evidence/governance?days=90" -H "x-api-key: $BUMPKIT_API_KEY"
curl "https://bumpkit.dev/v1/evidence/governance?days=90&format=csv" -H "x-api-key: $BUMPKIT_API_KEY"
```

Each release check with detected agents becomes a change with a `status`:

| Status | Meaning |
|--------|---------|
| `reviewed` | Gated and approved as the policy required |
| `no_review_required` | Gated; the policy didn't require approval |
| `blocked` | The gate failed it |
| `awaiting_approval` | The gate is waiting for approval |
| `not_gated` | No gate decision was recorded |
| `emergency_change` | Shipped through a break-glass override |

And any `gaps`:

| Gap | Meaning |
|-----|---------|
| `not_gated` | The change never went through the gate |
| `approval_not_verified` | Approvals were asserted by the caller, not verified with GitHub |
| `approver_is_agent` | An approver is a bot or agent account |
| `override_review_pending` | The override's post-hoc review is not done yet |
| `override_review_overdue` | The override's review is past due |
| `override_not_reviewed` | The override was never reviewed |

A summary counts agent changes per status and how many have gaps. Each change carries the `evidence_hash` of its evidence record, so you can tie it back to the evidence export.

## Audit streaming

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

Bumpkit streams audit events to your SIEM as they happen. Owners and admins add destinations under **Settings > Audit streaming**.

### Events

| `type` | Source |
|--------|--------|
| `workspace.audit` | Every [audit log](#audit-log) entry |
| `gate.decision` | Every gate decision (`action` is `gate.<outcome>`) |
| `gate.approval` | Each approval on a decision |
| `gate.override` | A break-glass override |
| `gate.override_review` | Its post-hoc review |
| `admin.action` | Actions Bumpkit staff took in your workspace's dashboard |

Every event has `id` (`bkevt_<sequence>`), `sequence`, `workspace_id`, `type`, `source_id`, `occurred_at`, `actor` (`email`, `via`), `action`, `target` and `data`.

### Destinations

| Type | URL | Secret | Delivery |
|------|-----|--------|----------|
| Splunk HTTP Event Collector | The HEC event endpoint | HEC token, sent as `Authorization: Splunk <token>` | Newline-delimited HEC events, `source` `bumpkit`, sourcetype `bumpkit:audit` unless you set one, optional index |
| Datadog Logs | Your site's logs intake, such as `https://http-intake.logs.datadoghq.com/api/v2/logs` | API key, sent as `DD-API-KEY` | A JSON array of logs, `ddsource` and `service` `bumpkit`, optional tags |
| Webhook (HMAC-signed) | Your HTTPS endpoint | A signing secret of at least 16 characters | `{"events": [...]}` |
| S3-compatible storage | The service endpoint, such as `https://s3.us-east-1.amazonaws.com` or a MinIO URL | Secret access key; also set the bucket and access key ID, and optionally region (default `us-east-1`), key prefix and path-style addressing | One JSONL object per batch at `<prefix>/<workspace>/<yyyy>/<mm>/<dd>/<first>-<last>.jsonl`, signed with AWS SigV4 |

URLs must use `https`. Secrets are encrypted with AES-256-GCM before they're stored, and never shown again. For a destination with a self-signed or private certificate, paste a PEM CA bundle (up to 64 KB); only those certificates are then trusted for it.

Webhook deliveries carry:

```text
X-Bumpkit-Timestamp: 1792689437
X-Bumpkit-Signature: t=1792689437,v1=<hex HMAC-SHA256 of "<timestamp>.<body>">
```

Every delivery carries `X-Bumpkit-Delivery: <destination id>:<first event id>-<last event id>` so you can deduplicate retries.

### Delivery and retries

A new destination starts from the next event. Bumpkit sends batches of up to 100 events in order, checking every 5 seconds. A failed batch is retried with backoff starting at 10 seconds and doubling up to an hour. After 8 failed attempts the batch goes to a dead-letter queue and streaming continues; you can redeliver it from the dashboard. You can also send a test event, pause, resume and remove destinations there. Creating and testing destinations is audited.

### Signed exports

For a backfill or an auditor, export a period of audit events:

```bash
curl "https://bumpkit.dev/v1/audit/events?days=30" -H "x-api-key: $BUMPKIT_API_KEY" -o audit.jsonl
curl "https://bumpkit.dev/v1/audit/events?from=2026-07-01&to=2026-09-30&format=csv" -H "x-api-key: $BUMPKIT_API_KEY" -o audit.csv
```

The response is JSONL (default) or CSV, up to 50,000 events per export (`400 EXPORT_TOO_LARGE` beyond that). The headers `X-Bumpkit-Signature` (base64 Ed25519 over the body), `X-Bumpkit-Key-Id`, `X-Bumpkit-Content-Sha256` and `X-Bumpkit-Events` let you verify it. Each export is written to the audit log as `audit.export`.

## Auditor access

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

Give an auditor a read-only link instead of an account. Owners and admins create links under **Settings > Compliance evidence**: name the auditor or audit firm (up to 64 characters) and pick an expiry of 1 to 90 days (default 30). The link is shown once.

The link opens a read-only page scoped to evidence and audit events, where the auditor can download the evidence export, the signed bundle, governance evidence and the audit event export. Every visit and download is written to the audit log under `auditor: <name>`. Revoke a link at any time; creating and revoking links are audited too.

With roles on (Beta), you can also invite an auditor as a workspace member with the read-only `auditor` role, which adds the audit log and evidence export. See [SSO, SCIM & Roles](/docs/sso).

## Audit log

Every change to workspace settings, keys, team, integrations, policies, approvals, overrides, deploys, incidents and rollbacks is recorded with who made it and from where (`dashboard`, `cli`, `api`, `github` or `system`).

Changes Bumpkit makes to your workspace are recorded too. A billing change appears as `billing.plan`, from billing or from Bumpkit staff, and an early-access grant by Bumpkit staff appears as `early_access.grant` with the features it turned on.

When a pull request merges while its release gate still needs a person's approval, Bumpkit records `gate.merged_without_approval` from `system`, with the release check and the pull request (`<check id> owner/repo#123`) as the target. The release page shows it as **Merged without approval**. An approval or override recorded before the merge means no event.

```bash
bumpkit audit --limit 100
```

`--limit` is up to 500 (default 100). The API is `GET /v1/audit?limit=`, returning `events` with `id`, `actor_email`, `via`, `action`, `target` and `created_at`. Workspace owners can read the audit log. With roles on (Beta), admins and members with the `auditor` role can read it too.

See the [CLI Reference](/docs/cli) and [API Reference](/docs/api-reference) for every flag and field.


## Manage audit access and streaming from the CLI

These commands use the active CLI workspace and the role of the person who created its API key. Owners and organization admins can configure destinations and auditor links. Audit and governance exports also allow auditors. The corresponding workspace features must be enabled; CLI access does not bypass permissions or feature availability.

List destinations without revealing stored credentials, configuration or remote error bodies:

```sh
bumpkit audit streaming list
bumpkit audit streaming list --json
```

Read a destination credential from a private file or standard input. Do not put credentials in command arguments or the destination URL. The optional configuration file is a JSON object with provider fields such as `index`, `sourcetype`, `tags`, or S3 `bucket`, `region`, `access_key_id`, `prefix` and `path_style`. S3's secret access key belongs in the credential input. A private CA bundle uses `ca_pem` in the configuration object.

```sh
bumpkit audit streaming create --kind webhook --name Security --url https://audit.example.com/events --secret-file /secure/audit-secret --yes
bumpkit audit streaming create --kind splunk --name Splunk --url https://splunk.example.com:8088/services/collector/event --secret-file - --config-file /secure/splunk.json --yes
bumpkit audit streaming test DESTINATION_ID --yes
bumpkit audit streaming disable DESTINATION_ID --yes
bumpkit audit streaming enable DESTINATION_ID --yes
bumpkit audit streaming remove DESTINATION_ID --yes
bumpkit audit streaming dead-letters list
bumpkit audit streaming dead-letters redeliver BATCH_ID --yes
```

Adding or enabling a destination streams new workspace audit events to it. Testing sends a sample event; redelivery sends the selected failed batch again. Check the destination before confirming these actions. Disabled destinations remain configured. Removing a destination deletes its configuration.

Auditor access links expire after 1 to 90 days. The complete access URL appears only when created. Anyone holding it can use its read-only auditor access until expiry or revocation; keep it private. Listing links returns metadata without access tokens.

```sh
bumpkit auditors list
bumpkit auditors create "Audit firm" --days 30 --yes
bumpkit auditors revoke LINK_ID --yes
```

Export the complete requested audit period, rather than the latest 500 entries shown by `bumpkit audit`. Audit exports support JSON Lines (`jsonl`) or CSV. A period over 50,000 events fails with `400 EXPORT_TOO_LARGE` instead of returning a partial export; split it into smaller periods. Governance exports support JSON or CSV. Use either both dates or `--days` (1 to 730); without a period, the export covers the last 90 days.

```sh
bumpkit audit export --from 2026-01-01 --to 2026-01-31 --format jsonl -o january-audit.jsonl
bumpkit audit export --days 30 --format csv -o audit.csv
bumpkit evidence governance --days 90 --format json -o governance.json
```

File exports are written atomically with owner-only permissions. Without `--output`, exported data goes to standard output. The audit endpoint also returns signature headers; the CLI's audit file contains the event data. Use `bumpkit evidence export --signed` when you need a separately verifiable evidence bundle.

The management API routes are `GET|POST /v1/audit/streaming/sinks`, `POST /v1/audit/streaming/sinks/{id}/test|enable|disable`, `DELETE /v1/audit/streaming/sinks/{id}`, `GET /v1/audit/streaming/dead-letters`, `POST /v1/audit/streaming/dead-letters/{id}/redeliver`, and `GET|POST /v1/auditor-links` / `DELETE /v1/auditor-links/{id}`. Exports use `GET /v1/audit/events` and `GET /v1/evidence/governance` with `from`, `to`, `days` and `format` query parameters. Mutation requests use JSON, and one-time access links are returned with `Cache-Control: no-store`.
