URL: /guides/ci

---
title: "Continuous Integration"
description: "Gate your CI builds on squirrelscan audits — GitHub Actions, GitLab, and generic runners"
---

Run squirrelscan in CI to catch audit regressions — performance, security,
content, and more — before they ship. The audit produces a non-zero exit code
when results regress, so your pipeline fails like any other check.

## Exit codes

`squirrel audit` uses distinct exit codes so CI can tell a *regression* apart
from a *broken run*:

| Code | Meaning |
|------|---------|
| `0` | Audit ran and every `--fail-on` threshold passed |
| `2` | Audit ran but a `--fail-on` threshold tripped |
| `1` | Operational error (bad flags, network failure, etc.) |

Gate your build with [`--fail-on`](/cli/audit) — quote each expression, since
shells treat bare `<`/`>` as redirection:

```bash
squirrel audit https://example.com --fail-on 'score<90' --fail-on 'severity>=error'
```

<Note>Deterministic audits run with **no login**. A `SQUIRREL_API_TOKEN` is only
needed for [cloud features](/cloud) (browser rendering, AI summary, tech
detection). Set it as a CI secret — see [Authentication](#authentication).</Note>

## GitHub Actions

The quickest path is the official action:

```yaml
name: Audit
on: [pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: squirrelscan/audit-action@v1
        with:
          url: https://staging.example.com
          fail-on: "score<90,severity>=error"
          token: ${{ secrets.SQUIRREL_API_TOKEN }}   # optional
          github-token: ${{ secrets.GITHUB_TOKEN }}   # optional PR comment
```

Prefer no action? Install the CLI directly:

```yaml
      - run: |
          curl -fsSL https://squirrelscan.com/install | bash
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"  # resolve squirrel in later steps (self-hosted)
      - run: squirrel audit https://staging.example.com --fail-on 'score<90'
        env:
          SQUIRREL_API_TOKEN: ${{ secrets.SQUIRREL_API_TOKEN }}
```

## GitLab CI

```yaml
audit:
  image: ubuntu:latest
  script:
    - apt-get update && apt-get install -y curl
    - curl -fsSL https://squirrelscan.com/install | bash
    - export PATH="$HOME/.local/bin:$PATH"
    - squirrel audit "$AUDIT_URL" --fail-on 'score<90,severity>=error'
  variables:
    AUDIT_URL: "https://staging.example.com"
    SQUIRREL_API_TOKEN: $SQUIRREL_API_TOKEN   # optional, set in CI/CD variables
```

## Generic runners

Any runner that can run a shell works:

```bash
curl -fsSL https://squirrelscan.com/install | bash
export PATH="$HOME/.local/bin:$PATH"

# JSON report on stdout stays clean — the gate summary goes to stderr.
squirrel audit https://example.com \
  --format json --output report.json \
  --fail-on 'score<90,score:perf<80'
```

The command exits `2` if a threshold trips, so the script stops on a regression.

## Authentication

Cloud features require a token. The recommended approach is an
[API key](/cli/auth) stored as a CI secret and exposed as `SQUIRREL_API_TOKEN`:

- **GitHub Actions** — repo/org secret → `env: SQUIRREL_API_TOKEN: ${{ secrets.SQUIRREL_API_TOKEN }}`
- **GitLab** — masked CI/CD variable `SQUIRREL_API_TOKEN`
- **Other** — export `SQUIRREL_API_TOKEN` in the job environment

When `SQUIRREL_API_TOKEN` is set, the CLI uses it automatically. Without it, the
audit still runs deterministically — it just skips cloud enrichment.
