Handbook sections

Run Pavois in CI (GitHub Actions)

Last reviewed

Every scan returns an exit code a pipeline can gate on and emits SARIF/JUnit/JSON/CSV/HTML. Scan a disposable container or an SSH target, fail the job under a grade, and publish findings, secrets handled safely.

Pavois is built for CI: every scan returns an exit code a pipeline can act on, and emits SARIF, JUnit, JSON, CSV or HTML. The patterns below scan a target, fail the job under a chosen grade, publish findings to GitHub code scanning, and archive the full report.

Exit codes

The process exit code is the gate:

  • 0: every scored control passed (and the grade is at or above --fail-under).
  • 1: --fail-under was not met, so the job fails. The threshold is the points value (0 to 100), not the letter.
  • 2: a technical error (pavois or the engine could not complete the run).

CINC Auditor's own 100 / 101 are handled INSIDE pavois (a failing control is a normal outcome, not an error) and never reach your shell: a pipeline testing for 100 would never fire. The reference is generated from the binary.

Workflow 1: a disposable container target

name: hardening
on: [push, pull_request]

jobs:
  pavois:
    runs-on: ubuntu-latest
    permissions:
      security-events: write   # upload SARIF to code scanning
    steps:
      - uses: actions/checkout@v4
      - uses: jdx/mise-action@v2   # pinned Go/Node/Python toolchain
      - run: mise run build        # -> go/pavois

      - run: docker run -d --name target --privileged debian:12 sleep infinity
      - run: |
          ./go/pavois scan target \
            --profile profiles/linux/debian12 \
            --format sarif --fail-under 70 > pavois.sarif

      - if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: pavois.sarif
      - if: always()
        uses: actions/upload-artifact@v4
        with:
          name: pavois-report
          path: reports/

--fail-under 70 blocks a merge whose effective posture scores below 70/100. Because the SARIF upload and artifact steps use if: always(), the findings are published even when the gate fails, so a reviewer sees exactly which controls regressed.

Workflow 2: a remote SSH target (with secrets)

To scan a real host over SSH, inject the key and the sudo password from GitHub secrets, never as command-line arguments:

      - run: |
          install -m 600 /dev/null ~/.ssh/id
          printf '%s' "${{ secrets.TARGET_SSH_KEY }}" > ~/.ssh/id
      - env:
          PAVOIS_SUDO_PASSWORD: ${{ secrets.TARGET_SUDO_PASSWORD }}
        run: |
          ./go/pavois scan deploy@server1 --key ~/.ssh/id \
            --sudo-prompt --on-target \
            --profile profiles/linux/ubuntu2404 \
            --format sarif --fail-under 70 > pavois.sarif

--sudo-prompt reads PAVOIS_SUDO_PASSWORD from the environment (no echo, never argv); --on-target runs the scan on the host (far fewer SSH round-trips, and it sidesteps the Debian Defaults use_pty TTY issue with native-SSH sudo).

Secrets, handled safely

  • Never pass --ssh-pass/passwords on the command line: they leak via ps and shell history. Use --ssh-prompt (reads PAVOIS_SSH_PASSWORD) or --sudo-prompt (reads PAVOIS_SUDO_PASSWORD), or a key file written from a secret with mode 0600.
  • Scope the GitHub token to security-events: write; nothing more is needed to upload SARIF.
  • Prefer a key over a password, and a short-lived deploy account over root.

Install: build or released binary

The workflows above build from source with mise run build. On a tagged release you can instead install the published .deb/.rpm from the project's GitHub Releases rather than building, which is faster and pins a signed artifact. Either way the same pavois scan runs.

Other formats

Swap --format for the consumer you need: junit for test reporting, json for a custom gate, html for a human-readable artifact, csv for a spreadsheet. See evidence and exports.

FAQ

How do I fail the build on a bad grade? --fail-under N sets exit code 1 when the grade is below N points (0 to 100). Pair it with if: always() on the SARIF/artifact steps so findings still publish on failure.

How do I pass the SSH key and sudo password safely? Write the key from a secret to a 0600 file and use --key; set PAVOIS_SUDO_PASSWORD from a secret and use --sudo-prompt. Never put a password in argv (it leaks via ps).

Does Pavois install an agent on the target? No. It scans a throwaway container or an SSH target; --on-target runs the checks on the host for speed but leaves nothing behind.

Next steps