CI and GitHub Actions

Run free catalog adversaries on pull requests and mainline pipelines. Prefer the official run action; the same CLI works in any CI.

CI is the same product as local: install the CLI, authenticate, pull catalog packages, and adversary run against the checkout. Official free-catalog packages are signed, so host execution is trusted without --allow-unsafe-host-execution.

Recommended integration: adversarylabs/actions/run. It installs a release CLI, exchanges CI OIDC when requested, and maps the common adversary run flags. Works on GitHub Actions and Depot CI (runs-on: depot-ubuntu-latest).

Prerequisites

  • Trusted CI identity configured on the Team page for your repository. For GitHub Actions or Depot CI, add id-token: writeand use auth-mode: oidc.
  • Model provider key when packages set permissions.model: true (most catalog reviewers). Pass via the action's model-api-key or the usual env vars (OPENAI_API_KEY, ANTHROPIC_API_KEY, FIREWORKS_API_KEY).
  • Full git history for PRs fetch-depth: 0 so base/head merge bases resolve.

Token creation and scopes: Registry authentication.

Pull request workflow

Pass PR base/head SHAs so the CLI reviews the change set (not the whole tree). Catalog ids use domain/name form.

.github/workflows/adversary.yml
name: Adversary review

on:
  pull_request:

permissions:
  contents: read
  id-token: write

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Run adversaries
        id: review
        uses: adversarylabs/actions/run@v1
        with:
          adversaries: |
            go/security
            security/secrets
          path: .
          base: ${{ github.event.pull_request.base.sha }}
          head: ${{ github.event.pull_request.head.sha }}
          auth-mode: oidc
          registry-namespace: your-team
          # Optional model-backed packages:
          # model-provider: openai
          # model: gpt-4o
          # model-api-key: ${{ secrets.OPENAI_API_KEY }}
          format: json

      - name: Report outcome
        if: always()
        run: |
          echo "outcome=${{ steps.review.outputs.outcome }}"
          echo "findings=${{ steps.review.outputs.findings-count }}"

Pin adversarylabs/actions/run to a release tag or full commit SHA for reproducible CI. Optionally set cli-version to an exact CLI CalVer tag (for example 2026.7.30).

Full-tree scan on main

.github/workflows/adversary-main.yml
name: Adversary full scan

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: adversarylabs/actions/run@v1
        with:
          adversaries: go/security
          path: .
          all-files: true
          auth-mode: oidc
          registry-namespace: your-team

all-files: true cannot be combined with base/head.

Authentication modes

auth-modeWhen to use
oidcRecommended for private CI pulls and publishing. GitHub Actions and native Depot CI exchange the job identity for a ten-minute team credential; no registry secret is stored.
tokenFallback when OIDC is unavailable. Pass token from secrets; the action runs adversary login --token-stdin.
noneDefault of the action. Skips login — only for local path adversaries or registries that need no Adversary Labs session. Free catalog pull requires auth.
oauthInteractive device login (rarely useful in non-interactive CI).
existingRunner already has a CLI profile; the action never logs in or out.

Findings and exit codes

By default the action fails the step when the review reports findings (fail-on-findings: true). Set it to false to keep the job green while still publishing findings-count and result-file (with format: json).

CodeMeaning
0Success
1Findings reported
2Usage / configuration error
3Execution failure
4Network / auth failure

Outputs: exit-code, findings-count, result-file, outcome (success/ findings/ failure).

Generic CI (CLI only)

Any runner with a release CLI can do the same without the composite action. Install from CLI install, then:

bash
# Install a release CLI (or use adversarylabs/actions/run which does this)
# Then authenticate with a pull-scoped service account token:
printf '%s\n' "$ADVERSARY_SERVICE_ACCOUNT_TOKEN" | \
  adversary login --token-stdin --registry-namespace adversarylabs

# Pull-request style scope (set refs from your CI system):
adversary run go/security security/secrets \
  --path . \
  --base "$BASE_SHA" \
  --head "$HEAD_SHA" \
  --format json \
  --output-file review.json

# Full-tree scan (for example on main):
adversary run go/security --path . --all-files --format json

Without explicit --base/--head, the CLI may still pick up PR scope from environment variables such as GitHub GITHUB_BASE_REF / head SHA pairs when present — but passing SHAs from the workflow is more reliable. You can also set ADVERSARY_BASE_REF and ADVERSARY_HEAD_REF.

Auto-select in CI

The run action requires an adversaries input (explicit list). For detection-based selection of every accessible catalog package, use the CLI directly:

bash
printf '%s\n' "$ADVERSARY_SERVICE_ACCOUNT_TOKEN" | \
  adversary login --token-stdin --registry-namespace adversarylabs

adversary run --path . --base "$BASE_SHA" --head "$HEAD_SHA" --format json
# or: adversary run --dry-run --explain --path . --base "$BASE_SHA" --head "$HEAD_SHA"

Security notes

  • OIDC pull bindings cannot publish; publishing must be explicitly enabled and is never issued to pull-request events.
  • The action checksum-verifies the CLI archive before running.
  • Tokens and model keys are passed via secrets / stdin / env — never as plain CLI flags in logs.
  • Unsigned private packages still need allow-unsafe-host-execution: true (or a sandbox) for host execution. Official free-catalog packages do not.

Related: Getting started, CLI, Official signatures, adversarylabs/actions.