Integrations

CI Integration

Run xgrep in CI and publish results to GitHub Code Scanning or GitLab SAST.

CI Integration

xgrep ci on a simulated GitHub Actions pull request: it detects the CI environment, runs a diff-aware scan against the merge-base, writes a SARIF report, and flags the command injection the change introduced

xgrep emits native report formats for both major platforms. Run it from the repository root (or pass --project-root) so file paths in the report match the repo and findings attach to the right lines. Combine with --baseline-commit to scan only what a pull request changed. See Output formats.

Installing xgrep in CI

The workflows below assume xgrep is on the runner's PATH. The simplest way to get it there is the npm package, which ships prebuilt binaries (Linux, macOS, and Windows, amd64 and arm64) and works on any runner with Node.js:

npm install -g @mondoohq/xgrep   # adds the `xgrep` command to PATH
xgrep version

Or run it without a global install via npx @mondoohq/xgrep … — drop-in for xgrep … in any of the steps below. Pin a version (@mondoohq/xgrep@<version>) for reproducible builds instead of floating on the latest release.

GitHub-hosted and GitLab node:* runners ship Node.js, so no extra setup step is needed; on other images add Node first (actions/setup-node on GitHub, a node:lts image on GitLab — both shown below). See Installation for details.

Exit codes

xgrep follows the Semgrep/Opengrep convention:

CodeMeaning
0Scan completed (with or without findings)
1Findings found — only when --error is passed
2Fatal error (e.g. rules failed to load)

A completed scan exits 0 even when it reports findings, so the upload/report step still runs. Add --error to hard-fail the job on any finding (scope it with --severity, e.g. --severity ERROR --error). Let the platform (GitHub Code Scanning, GitLab) gate via policy when you prefer upload-then-gate over a hard CLI gate.

For gradual rollout, --max-findings N fails the job (exit 1) only when the active finding count exceeds N — so you can adopt a ruleset on a repo with known findings, gate at the current count, and tighten over time. When set it takes precedence over --error and gates on its own (--max-findings 0 is equivalent to --error).

Diff-aware scanning (pull requests)

For pull-request checks, --baseline-commit scopes the scan to the files changed since a baseline and reports only findings on changed lines — so a PR check parses and runs rules over just what changed, not the whole repository. This is the same flag (and behavior) Semgrep/Opengrep use for diff-aware scanning.

xgrep -f rules/ --baseline-commit origin/main .       # changes since origin/main
xgrep -f rules/ --baseline-commit origin/main..HEAD . # an explicit commit range
xgrep -f rules/ --baseline-commit HEAD .              # uncommitted working-tree changes
  • The argument is a single ref (diffed against the working tree) or a base..head / base...head range. In CI, use the merge-base between the PR branch and its target — that is what should count as "new."
  • Only changed files are scanned; findings on unchanged lines of changed files are dropped. With --error, the job fails only when a changed line has a finding.
  • Run from the repository root; the content scanned is the working tree, so the head should be the working tree / HEAD.
  • Because unchanged files are skipped, interfile (cross-file) analysis sees only the changed files. Drop --baseline-commit for a full-context scan (e.g. a scheduled scan of the default branch).

GitHub Actions PR example (combine with the SARIF upload below):

on: pull_request
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # need history so the baseline ref is available
      - run: npm install -g @mondoohq/xgrep
      - name: Diff-aware scan
        run: |
          xgrep -f rules/ \
            --baseline-commit "${{ github.event.pull_request.base.sha }}" \
            --sarif -o results.sarif .

GitLab CI resolves the target branch via CI_MERGE_REQUEST_DIFF_BASE_SHA:

xgrep-mr:
  image: node:lts
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  before_script:
    - npm install -g @mondoohq/xgrep
  script:
    - xgrep -f rules/ --baseline-commit "$CI_MERGE_REQUEST_DIFF_BASE_SHA" --gitlab -o gl-sast-report.json .

GitHub Code Scanning (SARIF)

# .github/workflows/xgrep.yml
name: xgrep
on: [push, pull_request]

permissions:
  contents: read
  security-events: write # required to upload SARIF

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @mondoohq/xgrep
      - name: Run xgrep
        run: xgrep -f rules/ --sarif -o results.sarif .
      - name: Upload SARIF
        if: always() # upload even when findings make xgrep exit non-zero
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif
          category: xgrep # matches automationDetails.id (set via --sarif-category)

xgrep fills in partialFingerprints (stable alert tracking), security-severity (severity ranking), CWE/OWASP tags, automationDetails.id, and versionControlProvenance automatically.

GitLab SAST

# .gitlab-ci.yml
xgrep-sast:
  stage: test
  image: node:lts
  before_script:
    - npm install -g @mondoohq/xgrep
  script:
    - xgrep -f rules/ --gitlab -o gl-sast-report.json .
  artifacts:
    reports:
      sast: gl-sast-report.json

The report appears in the merge request security widget and the Vulnerability Report. scan.primary_identifiers lets GitLab mark findings resolved once they disappear from a later scan.

xgrep ci — one-command CI scan

xgrep ci wraps scan with CI-aware defaults, so most pipelines need a single line. It:

  • Auto-detects the CI provider (GitHub Actions, GitLab CI, or a generic CI environment).
  • Scans diff-aware by default against the pull/merge-request base — on GitHub from GITHUB_BASE_REF, refined to the merge-base with HEAD (the fork point) so changes the base branch picked up after the PR forked aren't blamed on the PR; needs full history, e.g. actions/checkout with fetch-depth: 0 (it falls back to the base ref when the merge-base can't be computed). On GitLab it uses CI_MERGE_REQUEST_DIFF_BASE_SHA, which is already the merge-base. On a push / when no base is available it runs a full scan. --no-baseline forces a full scan; --baseline-commit overrides the auto-derived base.
  • Defaults to SARIF (GitLab SAST under GitLab CI); override with --json/--sarif/--gitlab and -o.
  • Exits per the table above (0 clean/findings, 1 with --error, 2 fatal).
  • Honors SEMGREP_BASELINE_REF / SEMGREP_REPO_URL (and XGREP_* equivalents), so an existing Semgrep/Opengrep CI step can switch by binary name.
  • Stays hermetic — it writes a report file for the platform to ingest and never uploads to a hosted backend.
# GitHub Actions
- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- run: npm install -g @mondoohq/xgrep
- run: xgrep ci -f rules/ -o results.sarif
- if: always()
  uses: github/codeql-action/upload-sarif@v3
  with: { sarif_file: results.sarif }

Baseline precedence: --baseline-commit > SEMGREP_BASELINE_REF > XGREP_BASELINE_REF > provider diff-base. An explicitly-set env var overrides the auto-detected provider base (so a custom ref can override GITHUB_BASE_REF). The auto-derived baseline (env var or provider ref, but not an explicit --baseline-commit) is refined to its merge-base with HEAD; a base..head range is used verbatim.

--max-findings (see Exit codes) gates gradual rollout. Parity status is tracked in Semgrep compatibility.

On this page