> ## Documentation Index
> Fetch the complete documentation index at: https://docs.breachlens.app/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI

> Trigger a BreachLens scan from any terminal or CI pipeline and gate the build on severity — a thin, headless client for your self-hosted BreachLens API.

The `breachlens` CLI is a **thin client**: it scans nothing locally. It drives your
self-hosted BreachLens API — trigger a scan, wait for it, download the SARIF, and
turn the result into an **exit code**. That exit code is the whole point: `0` passes,
`1` fails the build.

<Note>
  The CLI carries no scanners (\~200 KB, Node ≥ 20). Your code is cloned and analysed
  **server-side** on your BreachLens deployment — so the same binary drops into a
  laptop pre-push hook, a GitHub Action, or a GitLab job with nothing but a different
  `--severity-gate`.
</Note>

## Install

<CodeGroup>
  ```bash npx (no install) theme={null}
  npx @breachlens/cli scan --help
  ```

  ```bash global theme={null}
  npm install -g @breachlens/cli
  breachlens scan --help
  ```
</CodeGroup>

## Quickstart

Point it at your deployment, pass a token, name a target, and set a gate:

```bash theme={null}
breachlens scan \
  --api-url https://breachlens.example.com \
  --token   blt_xxxxxxxx \
  --repo    my-org/my-service \
  --severity-gate HIGH
```

Progress prints to **stderr**; **stdout** and the **exit code** stay clean for CI.
Every value can come from the environment instead of a flag — ideal for CI secrets:

```bash theme={null}
export BREACHLENS_API_URL=https://breachlens.example.com
export BREACHLENS_API_TOKEN=blt_xxxxxxxx
breachlens scan --repo my-org/my-service --severity-gate HIGH
```

## How it works

<Steps>
  <Step title="Trigger">
    `POST` to the API. `--repo owner/name` hits `/api/scans/from-github`, which
    **auto-onboards** the repo on first run; explicit IDs hit
    `/api/{repos,containers,domains}/:id/scan`.
  </Step>

  <Step title="Poll">
    Scans are async, so the CLI polls `/api/scans/:id` every `--poll-interval` seconds
    until `COMPLETED` (or `FAILED`), with a hard `--timeout`.
  </Step>

  <Step title="Fetch SARIF">
    Downloads `/api/scans/:id/export.sarif` to a file — the standard format your
    pipeline can upload to its security tab.
  </Step>

  <Step title="Gate">
    Counts findings by severity and compares to `--severity-gate`. Any finding at or
    above the threshold exits `1`; `none` reports without ever failing.
  </Step>
</Steps>

## Authentication

Mint a scoped token in **Settings → API Tokens** with the `scans:trigger` and
`scans:read` scopes. Pass it as `--token` (or `BREACHLENS_API_TOKEN`); it authenticates
as `Authorization: Bearer blt_…`, scoped to your org. See the [API reference](/api) for
token details.

### Cloudflare Access

If your deployment sits behind a Cloudflare Access perimeter (a hosted / edge-fronted
instance), a bare token gets bounced to the SSO login page. Supply a **service token**
so the CLI's requests clear the edge:

```bash theme={null}
breachlens scan \
  --cf-access-client-id     "$CF_ACCESS_CLIENT_ID" \
  --cf-access-client-secret "$CF_ACCESS_CLIENT_SECRET" \
  --repo my-org/my-service --severity-gate HIGH
```

Leave these off for self-hosted deployments not behind Cloudflare Access.

## Targets

Pass **exactly one**:

<CardGroup cols={2}>
  <Card title="--repo owner/name" icon="code-branch">
    Auto-onboards + scans a GitHub repo (needs the BreachLens GitHub App installed on that account).
  </Card>

  <Card title="--repo-id <id>" icon="folder-tree">
    An existing BreachLens Repository — from its detail-page URL.
  </Card>

  <Card title="--container-id <id>" icon="box">
    An existing Container image.
  </Card>

  <Card title="--domain-id <id>" icon="globe">
    An existing Domain (DAST / pentest target).
  </Card>
</CardGroup>

## Options

| Flag (env var)                                             | Default                 | Meaning                                                        |
| ---------------------------------------------------------- | ----------------------- | -------------------------------------------------------------- |
| `--api-url`<br />`BREACHLENS_API_URL`                      | —                       | BreachLens base URL (required)                                 |
| `--token`<br />`BREACHLENS_API_TOKEN`                      | —                       | Scoped `blt_` token (required)                                 |
| `--cf-access-client-id`<br />`CF_ACCESS_CLIENT_ID`         | —                       | Cloudflare Access service-token id (only if CF-Access-fronted) |
| `--cf-access-client-secret`<br />`CF_ACCESS_CLIENT_SECRET` | —                       | Cloudflare Access service-token secret                         |
| `--repo`                                                   | —                       | `owner/name` — auto-onboard + scan                             |
| `--repo-id` / `--container-id` / `--domain-id`             | —                       | Explicit BreachLens resource id                                |
| `--scan-types`                                             | server default          | csv, e.g. `SAST,SCA,SECRET,IAC`                                |
| `--severity-gate`                                          | `none`                  | `CRITICAL` \| `HIGH` \| `MEDIUM` \| `LOW` \| `INFO` \| `none`  |
| `--commit` / `--branch` / `--pr`                           | —                       | CI context passed to auto-discovery                            |
| `--sarif-out`                                              | `breachlens-<id>.sarif` | Where to write the SARIF                                       |
| `--poll-interval`                                          | `15`                    | Status poll interval (seconds)                                 |
| `--timeout`                                                | `45`                    | Hard timeout (minutes)                                         |

## Exit codes

The exit code is the CI signal — the same severity buckets the GitHub Action and
GitLab template use, so verdicts always agree.

| Code | Meaning                                                              |
| ---- | -------------------------------------------------------------------- |
| `0`  | Passed — no finding at or above the gate (or `--severity-gate none`) |
| `1`  | Gate breached, scan `FAILED`, timeout, or a usage / connection error |

## In CI

Because only the exit code matters, the same command works two ways — a bypassable
pre-push hook (advisory) and a required CI check (enforcing):

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  - run: npx @breachlens/cli scan --repo ${{ github.repository }} --severity-gate HIGH
    env:
      BREACHLENS_API_URL: ${{ vars.BREACHLENS_API_URL }}
      BREACHLENS_API_TOKEN: ${{ secrets.BREACHLENS_API_TOKEN }}
  ```

  ```yaml GitLab CI theme={null}
  breachlens_scan:
    image: node:20-alpine
    script:
      - npx @breachlens/cli scan --repo-id "$BREACHLENS_REPO_ID" --severity-gate HIGH
  ```

  ```bash pre-push hook (advisory) theme={null}
  # .git/hooks/pre-push — bypassable with git push --no-verify
  npx @breachlens/cli scan --repo my-org/my-service --severity-gate none
  ```
</CodeGroup>

<Note>
  Applying a license and the full runtime-enforcement behavior live on the
  [Self-hosting & air-gap](/deployment/self-hosting#licensing) page — the scan CLI itself
  needs no license to run.
</Note>
