Guides
Run a scan in CI
The GitHub Actions template for fourgate scan: what it does, what to edit, and its limits.
On this page
The repository ships a copy-paste GitHub Actions workflow for your own repository. It installs Fourgate from a release tag, runs fourgate scan on manual dispatch, uploads the JSON and HTML reports as a workflow artifact, and fails the job on any FAIL or UNKNOWN, because both give a non-zero exit. Customer CI is unvalidated. This template is not a production-reliability claim, and there is no hosted control plane.
What to edit#
Copy the file to .github/workflows/fourgate-scan.yml and change the values marked EDIT:
FOURGATE_REF: the Fourgate release tag to install.FOURGATE_CONTRACT: the path to your scan contract in the repository.FOURGATE_TEST_ACCOUNT: the--confirm-test-accountlabel. It must equalserver.test_accountin the contract.- The environment variable names, to match what your MCP server reads and what
readback.token_envsays. - The step that installs or builds the MCP server named in
server.command.
The MCP server's write credential and the read-back token come from two separate repository secrets, FOURGATE_MCP_WRITE_TOKEN and FOURGATE_READBACK_TOKEN, mapped to separate environment variables. Fourgate removes the read-back variable from the MCP server's environment.
Triggers#
The template runs on workflow_dispatch. It includes a commented-out schedule trigger as an option. It advises against pull_request triggers, because every run performs real writes and fork pull requests do not receive secrets. A concurrency group stops two scans from running against the same test account at once.
The workflow#
# Fourgate scan for a customer repository.## Copy to .github/workflows/fourgate-scan.yml in your repository, then edit# the values marked EDIT. The scan performs the real, contracted writes listed# in your scan contract, so point it only at a disposable test account.## Required repository secrets (Settings -> Secrets and variables -> Actions):# FOURGATE_MCP_WRITE_TOKEN credential the MCP server under test uses to write# FOURGATE_READBACK_TOKEN separate, preferably read-only, credential used# for independent read-back## Exit codes: 0 = every case PASS; 1 = any FAIL or UNKNOWN; 2 = invalid# configuration. Any non-zero exit fails this job.## Reports are uploaded as a workflow artifact. Artifacts are visible to anyone# with read access to this repository (on a public repository, that is any# signed-in GitHub user). Run this workflow only in a private repository, and# review reports before sharing them: redaction is best effort and reports can# contain record metadata.name: Fourgate scanon: workflow_dispatch: # Optional: scheduled runs. Avoid pull_request triggers; every run performs # real writes and fork pull requests do not receive secrets. # schedule: # - cron: "0 6 * * 1"permissions: contents: read# Never run two scans against the same test account at once.concurrency: group: fourgate-scan cancel-in-progress: falsejobs: scan: runs-on: ubuntu-latest timeout-minutes: 15 env: # EDIT: reviewed Fourgate release tag to install. This tag is a pre-release. FOURGATE_REF: v0.4.0b1 # EDIT: path to your scan contract in this repository. FOURGATE_CONTRACT: fourgate/scan-contract.json # EDIT: must equal server.test_account in the contract. Fourgate refuses # to run writes unless this label matches. FOURGATE_TEST_ACCOUNT: disposable-test-account FOURGATE_REPORT_DIR: fourgate-output steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install Fourgate run: python -m pip install "git+https://github.com/hifzabuildsai/fourgate@${FOURGATE_REF}" # EDIT: install or build the MCP server named in server.command here. - name: Run Fourgate scan env: # EDIT: rename to the environment variable your MCP server reads. MCP_SERVER_TOKEN: ${{ secrets.FOURGATE_MCP_WRITE_TOKEN }} # EDIT: rename to match readback.token_env in the contract. Fourgate # removes this variable from the MCP server's environment. FOURGATE_READBACK_TOKEN: ${{ secrets.FOURGATE_READBACK_TOKEN }} # The redacted report is also printed to stdout; it is discarded here so # record data stays out of the job log. The step still fails on exit 1/2. run: > fourgate scan "$FOURGATE_CONTRACT" --confirm-test-account "$FOURGATE_TEST_ACCOUNT" --report-dir "$FOURGATE_REPORT_DIR" > /dev/null - name: Summarize verdicts if: always() run: | python - <<'EOF' import json, os, pathlib path = pathlib.Path(os.environ["FOURGATE_REPORT_DIR"], "fourgate-report.json") if not path.exists(): raise SystemExit("no Fourgate report was written") for row in json.loads(path.read_text(encoding="utf-8"))["cases"]: print(f"{row['tool']}: {row['status']} / {row.get('reason_code')}") EOF - name: Upload Fourgate reports if: always() uses: actions/upload-artifact@v4 with: name: fourgate-reports path: ${{ env.FOURGATE_REPORT_DIR }} if-no-files-found: ignore retention-days: 7What has and has not been run#
Fourgate's own CI runs the template's steps against the bundled demo fixture: the broken fixture exits 1 and the healthy one exits 0. Customer CI validation remains pending.
Exit codes are 0 when every case is PASS, 1 for any FAIL or UNKNOWN, and 2 for invalid configuration. Any non-zero exit fails the job.