Skip to content

Connect the CLI to a server

Two environment variables point the CLI at a server. Nothing else about how you run Bravos changes: scans still run locally, the ledger is still written locally, and the gate still evaluates locally.

What you gain is one command, bravos push, that files a completed run so the rest of your team can read it.

  • A server URL and a bearer token, from whoever stood the deployment up. The token needs the member role to push.
  • A run on this machine with a ledger. bravos resume lists what is available.
  1. Set both variables.

    Terminal window
    export BRAVOS_SERVER_URL=http://127.0.0.1:8123
    export BRAVOS_TOKEN=bugb_NCyQ7A6ELltEtf_qLrREDMhSHiYpf4_O4alfsMSQFCc

    No trailing slash is needed on the URL. The token still carries a bugb_ prefix: it is minted by the server, and the server kept its name.

  2. Give the repository a name to push under.

    A run is filed against a repository key. Bravos takes it from remote.origin.url, and a checkout with no remote has to be told:

    bravos: no git remote 'origin' in /Users/you/demo and no bravos.projectId set for this repository, so it has no name to push under. Set one: git config --local bravos.projectId <host/org/repo> (codebase='/Users/you/demo')
    Terminal window
    git config --local bravos.projectId github.com/acme/demo-shop
  3. Push a run.

    Terminal window
    bravos push
    pushed 20260910-004305-demo → github.com/acme/demo-shop
    http://127.0.0.1:8123/v1/events
    sent 5
    appended 5
    duplicates 0
    summary yes master
    dashboard no no threat-dashboard.html in the codebase
    advisories no no confirmed findings in this run
    templates no no confirmed findings in this run

    Each line is an artifact the run had already written to disk. The three no rows carry their reason rather than being silent: this run was model tier, so it proved nothing and had nothing to promote.

    --run <id> names a run; without it, the latest for this repository.

  4. Push it again.

    sent 5
    appended 0
    duplicates 5 already on the server

    Ingest is content-addressed, so a repeated push is a no-op. That is the designed answer to a retry, which means a pipeline can push as often as it likes and a failed step can be re-run without a second thought.

Exporting BRAVOS_SERVER_URL follows every repository on the machine. A team usually wants one checkout reporting to one server:

Terminal window
git config bravos.server.url https://bravos.example.com

$BRAVOS_SERVER_URL wins over the git config when both are set, so CI can override without editing anything in the checkout.

None of these touch your local run. The ledger, the advisories, and the dashboard on this machine are already written and are unaffected.

Neither variable is set. The error names both, before any request is built, and exits 2, because the request could not be carried out as asked:

bravos: no bravos server configured. Set BRAVOS_SERVER_URL=https://… or: git config bravos.server.url https://…; no bravos token. Set BRAVOS_TOKEN=<token>

The server is unreachable. Exit 1:

bravos: could not reach http://127.0.0.1:9/v1/events: ConnectError (url='http://127.0.0.1:9/v1/events')

The server’s licence has lapsed. This is the one worth reading in full, because the fix is not yours. Exit 1:

bravos: this bugb-server deployment declined to record the run: its licence is
missing or lapsed (HTTP 402): license expired: acme's team plan expired at
2026-06-03T00:00:00+00:00 and the grace window ended at
2026-06-17T00:00:00+00:00. This is the server's licence, not your token or your
checkout, so there is nothing to fix locally. Everything that runs on this
machine is unaffected — scans, the ledger, advisories, and `bravos ci` against a
committed registry set all keep working offline. What pauses is the
server-backed channel above. Whoever operates this deployment can restore it by
renewing or installing its licence. (status=402)

Line breaks added for reading; the CLI prints it as one paragraph.

The token’s role is too weak. viewer can read but not push, and the server answers 403:

{"detail":"role 'viewer' cannot push events; member or above is required"}
HTTP 403

Ask for a member token.

bravos ci reads a ledger and a policy from your repository and exits with a code. It contacts no server, and it needs neither variable:

Terminal window
env -u BRAVOS_SERVER_URL -u BRAVOS_TOKEN bravos ci
PASS 20260910-004305-demo
policy built-in default
fail_on_severity high · max_confirmed none · fail_on confirmed
4 threats tracked · 0 counted · 0 breaches

That separation is the point. A build gate that depended on a server would be a build gate that a server outage turns red. See Gate a build on a run for the policy and the exit codes.

Any valid token reads, and viewer is enough:

Terminal window
curl -s $BRAVOS_SERVER_URL/v1/repos -H "Authorization: Bearer $BRAVOS_TOKEN"

The token also says who it thinks you are:

Terminal window
curl -s $BRAVOS_SERVER_URL/v1/me -H "Authorization: Bearer $BRAVOS_TOKEN"
{
"tenant": "acme",
"identity": "bob",
"role": "member"
}

Every query is filtered by the token’s tenant before it touches a name, so naming another tenant’s repository returns 404:

{"detail":"no such repository: 'github.com/other/secret'"}
HTTP 404

That is the same answer as a typo, deliberately, so the read routes cannot be used to enumerate what other tenants exist.