Guide

Run Sonda in GitHub Actions

Scan every push and pull request with Sonda in GitHub Actions — install the CLI, gate on new code, upload SARIF to code scanning or results to a Sonda server.

Updated 3 min read

This guide adds Sonda to a repository's GitHub Actions: every push and pull request is scanned, new code is judged by the quality gate, and a failing gate fails the job. There are two ways to see the results:

  • CLI only, with code scanning: findings appear in GitHub's Security tab and as annotations on pull requests. No server needed.
  • With a Sonda server: analyses are kept with their history, and the server posts each pull request's result on it.

Prerequisites

  • A repository in JavaScript/TypeScript, Python, Java, C# or C/C++.
  • For code scanning on a private repository, GitHub Code Security (part of GitHub Advanced Security). Public repositories have it.
  • For the server option, a running Sonda server that the runner can reach.

1. Add a quality gate

Run sonda init locally and commit the sonda.toml it writes. It holds the default gate: no new bugs or vulnerabilities, at least 80% coverage and at most 3% duplication on new code. Quality gates explains each condition and how to change them.

2. Install Sonda in the job

On GitHub's Linux runners, download the release binary and check it against the release's checksums. It takes seconds:

yaml
- name: Install sonda
  run: |
    v=0.1.0
    curl -fsSLO https://github.com/lvaoliveira/sonda/releases/download/v$v/sonda-$v-x86_64-linux
    curl -fsSL https://github.com/lvaoliveira/sonda/releases/download/v$v/SHA256SUMS | sha256sum -c --ignore-missing
    install -D -m 755 sonda-$v-x86_64-linux "$HOME/.local/bin/sonda"
    echo "$HOME/.local/bin" >> "$GITHUB_PATH"

On an arm64 runner, use aarch64 instead of x86_64. On macOS or Windows runners there's no release binary yet: build it with cargo install --locked --git https://github.com/lvaoliveira/sonda --tag v0.1.0 sonda, which takes a few minutes.

3a. CLI only: upload SARIF to code scanning

Create .github/workflows/sonda.yml:

yaml
name: sonda
on:
  push:
    branches: [main]
  pull_request:
permissions:
  contents: read
  security-events: write # upload the SARIF report to code scanning
jobs:
  sonda:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0 # new code is judged against the base branch
      - name: Install sonda
        run: |
          v=0.1.0
          curl -fsSLO https://github.com/lvaoliveira/sonda/releases/download/v$v/sonda-$v-x86_64-linux
          curl -fsSL https://github.com/lvaoliveira/sonda/releases/download/v$v/SHA256SUMS | sha256sum -c --ignore-missing
          install -D -m 755 sonda-$v-x86_64-linux "$HOME/.local/bin/sonda"
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: sonda scan
        run: sonda scan . --format sarif -o sonda.sarif
      - uses: github/codeql-action/upload-sarif@v4
        if: always() # upload the findings even when the gate fails the scan
        with:
          sarif_file: sonda.sarif

There's no --new-code here on purpose. In a pull request, Sonda compares with the pull request's base branch; on a push, with the pushed commits. fetch-depth: 0 gives it the history to do that.

3b. With a Sonda server: upload every analysis

The server's web app writes this workflow for you: on an empty server, Connect a repository asks for the repository's URL, creates the project and an analysis token, and opens GitHub's editor with the file filled in. Here it is for GitHub's runners, with one change: Connect's version builds the CLI with cargo install, and this one installs the release binary from step 2, which is faster.

yaml
name: sonda
on:
  push:
    branches: [main]
  pull_request:
jobs:
  sonda:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0 # new code is judged against the base branch
      - name: Install sonda
        run: |
          v=0.1.0
          curl -fsSLO https://github.com/lvaoliveira/sonda/releases/download/v$v/sonda-$v-x86_64-linux
          curl -fsSL https://github.com/lvaoliveira/sonda/releases/download/v$v/SHA256SUMS | sha256sum -c --ignore-missing
          install -D -m 755 sonda-$v-x86_64-linux "$HOME/.local/bin/sonda"
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"
      - name: sonda scan
        env:
          SONDA_TOKEN: ${{ secrets.SONDA_TOKEN }}
          # Through env, not inline: a branch name is untrusted input.
          BRANCH: ${{ github.head_ref || github.ref_name }}
        run: >
          sonda scan . --upload
          --server https://sonda.example.com --project my-project
          --branch "$BRANCH" --commit "$GITHUB_SHA"
  • Add the token as the repository secret SONDA_TOKEN.
  • The server must be reachable over HTTPS from GitHub's runners: the CLI sends the token over plain HTTP only to localhost. For a server on your own network, run the job on a self-hosted runner on the server's machine and upload to http://localhost:9000.
  • With an upload, the server's gate decides the exit code, so issues accepted on the server stop failing the build.
  • To see results on the pull request itself, as a check run, annotations and one summary comment, configure the server with a GitHub App (scripts/setup-server.sh --github-app ID KEY.pem).

4. Read the results

  • In the job log: the scan prints the issues on new code and each gate condition with its actual value.
  • In code scanning (3a): the Security tab lists the findings, and pull requests show them as annotations. Security rules carry their CWE ids and OWASP Top 10 categories.
  • On the server (3b): the analysis page shows what the change introduced and resolved, and the project's history.

To report without blocking merges, add continue-on-error: true to the scan step, or pass --no-gate.

Troubleshooting

  • "New code" is the whole project, or nothing. The checkout is shallow: set fetch-depth: 0.
  • The SARIF upload fails with a permissions error. The job needs security-events: write, and private repositories need GitHub Code Security.
  • The job fails even though the findings uploaded. That's the gate: the scan exits 1 when it fails. The upload step runs anyway because of if: always().
  • A pull request from a fork isn't uploaded. Its workflow gets no secrets, so there's no token. The scan still runs and says so.
  • A pull request could weaken its own gate by editing sonda.toml. Load the config from the base branch: git show origin/main:sonda.toml > /tmp/sonda.toml and sonda scan . --config /tmp/sonda.toml.
  • The log warns that workers aren't fully sandboxed. The scan continues. To refuse to scan without the sandbox, pass --sandbox require.

Pin what you run

GitHub recommends pinning third-party actions to a full commit SHA rather than a tag. The release binary is already pinned to v0.1.0 and checked against its checksum; bump v when you upgrade.