The three enforcement layers
Enterprise enforces crypto posture in CI at three escalating layers. Each is independent — adopt them in order, or skip straight to the one you need. Layer 1 needs only the CLI; layers 2 and 3 need a tenant.
| Layer | Command | What it gates on | How it blocks |
|---|---|---|---|
| 1. Scan exit code | relixq scan --exit-on <sev> | Any finding at or above a severity | Process exit code 1 — works on every CI system |
| 2. PR gate | relixq pr-comment --mode block | Findings in a SARIF file, with an optional base-versus-head score delta | GitHub check-run conclusion plus branch protection — not the exit code |
| 3. QAST release gate | relixq pentest gate --mode block | New HNDL exposures, net-new findings, score regression, and the governance policy decision | Process exit code 1 on a fail verdict, plus a check-run conclusion |
All three read the same scanner output and the same severity ladder (info < low < medium < high < critical), so a finding that trips layer 1 locally is the same finding that trips layer 2 on the pull request.
Quick start: GitHub Actions
A minimal, realistic workflow: scan the base ref and the head ref, upload SARIF to code scanning, and post the pull-request comment with a score delta.
name: relixq-pqc-scan
on:
pull_request:
permissions:
contents: read
pull-requests: write # PR comment
checks: write # check run
security-events: write # SARIF upload (optional)
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # needed to check out the base ref for the score delta
- name: Install relixq
run: |
install -m 0755 ./tools/relixq /usr/local/bin/relixq
relixq version
- name: Scan base ref (for the score delta)
run: |
git worktree add /tmp/base "origin/${{ github.base_ref }}"
relixq scan /tmp/base --format sarif --output base.sarif --quiet
- name: Scan head ref
# No --exit-on here: let the scan complete so SARIF upload and the PR
# comment always run. Enforcement happens via the check-run conclusion.
run: relixq scan . --format sarif --output relixq.sarif
- name: Upload SARIF to GitHub code scanning (optional)
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: relixq.sarif
category: relixq
- name: Post PR comment + check run
run: |
relixq pr-comment \
--sarif relixq.sarif \
--base-sarif base.sarif \
--mode block \
--fail-on high
env:
GITHUB_TOKEN: ${{ github.token }}GITHUB_REPOSITORY,GITHUB_SHA, andGITHUB_EVENT_PATHare set automatically; you only wireGITHUB_TOKEN.- Start with
--mode warnfor a rollout period, then flip to--mode blockand add the check to required status checks. - Scanner configuration — paths, rule packs — is read from a
relixq.yamlin the scanned directory. There is no--configflag.
Baselines and base-versus-head diffs
Adopting a scanner on a legacy codebase without drowning in the backlog is a two-mechanism story, and they compose.
The committed baseline file records every finding accepted at adoption time; a later scan with --baseline reports only findings absent from it. Matching uses a content fingerprint — rule plus file plus code snippet — so findings survive line-number drift.
Base-versus-head SARIF comparison decides "new" by comparing two scans instead of consulting a committed file. relixq pr-comment --base-sarif shows the score delta; relixq pentest gate --findings-sarif with --base-findings-sarif computes net-new findings, and only fingerprints absent from the base can trip the gate finding-severity and regression conditions.
The QAST release gate
relixq pentest gate --project <id> --run <id> computes one canonical decision and renders it identically to four surfaces — CLI JSON, a GitHub check run, an idempotent pull-request comment, and a forensic audit event — so the verdict in your CI logs can never disagree with the check run.
Eight named conditions feed the verdict. Each is pass, warn, fail, or not_evaluated — and not_evaluated always fails open: never a silent pass, never a fabricated one.
| Condition | Gates on | Can block? |
|---|---|---|
new-hndl-exposure | New HNDL exposures versus --base-run, at or above --fail-on relevance | Yes |
finding-severity | Net-new static findings from the SARIF diff, at or above --fail-on | Yes |
regression | Any net-new finding regression at all | Warn-level |
score-regression | Local RelixQ Score drop between base and head findings | Warn by default; blocks when --fail-on-score-drop is set and met |
policy-decision | The governance service synchronous policy verdict | Yes |
asset-coverage-regression | Inventory coverage drop | Currently always `not_evaluated` — no real coverage endpoint exists yet, and the gate refuses to fabricate one |
expired-exception | Previously granted exceptions that have lapsed | Warn-level |
ownerless-critical-remediation | Open critical remediation items with no assigned owner | Warn-level |
The last three conditions need backend data, so they are evaluated only when a remote API and credential are available; offline they render not_evaluated.
Degradation, credentials and exit codes
- Credential. Set
RELIXQ_API_TOKENin CI. With no usable credential — including the common "no OS keyring on a CI runner" case — the gate logs it and degrades to findings-only instead of failing: HNDL, policy, and backend conditions becomenot_evaluated, and SARIF-based conditions still run. - Side channels never change the verdict. The governance-exception fetch, the policy-decision fetch, and gate-history persistence are all non-fatal on failure.
- Exit codes.
0for apassorwarnverdict;1only for afailverdict — which requires--mode blockplus at least one failing condition — or an operational error.
relixq pentest gate \
--project "$RELIXQ_PROJECT_ID" \
--run "$HEAD_RUN_ID" \
--base-run "$BASE_RUN_ID" \
--findings-sarif relixq.sarif \
--base-findings-sarif base.sarif \
--mode block --fail-on high \
--fail-on-score-drop 5 \
--audit gate-audit.jsonUse --dry-run first to see the full decision JSON without touching GitHub or disk.
The GitHub App: what it does today
Shipped and working:
- Webhook receiver — HMAC-SHA256 signature verification with a constant-time compare, delivery-id replay protection, and fast-ack queueing.
- Event processing —
pushto the default branch triggers a full platform scan;pull_requesttriggers a scan carrying the PR number;installationdelete or suspend deactivates the installation;installation_repositoriessyncs repository connections into your workspace. - Installation token broker — signs App JWTs with a private key from the secret store, mints installation access tokens, and caches them for 50 minutes.
Designed but not shipped:
- App-posted PR comments, check runs, and SARIF upload. The App does not write anything back to your pull request today. All PR-surface output comes from the CLI running inside your CI workflow. The CLI-based path is the shipped, supported path; results from App-triggered scans land in the dashboard, not on the pull request.
- PR-diff-scoped platform scans. An App-triggered pull-request scan currently covers the whole connected repository rather than just the changed files. Use
relixq scan --diff origin/<base-branch>in your workflow for diff-scoped scanning today. - A published App manifest and Marketplace listing.
Exit codes and modes reference
| Command | Exit `0` | Exit `1` |
|---|---|---|
relixq scan / scan deps / scan tls | Scan completed; no finding at or above --exit-on | A finding met --exit-on, or an operational error |
relixq baseline | Baseline written | Operational error |
relixq pr-comment | Comment and check run posted — regardless of findings or mode | Operational error only |
relixq pentest gate | Verdict pass or warn | Verdict fail (block mode plus a gating condition), or an operational error |
| Mode | Check-run conclusion | Can block a merge? |
|---|---|---|
observe | neutral | Never |
warn (default) | neutral | Never — surfaces risk without blocking |
block | success when clean; failure when a gating condition trips | Yes — via required status checks; pentest gate additionally exits 1 |
Other CI systems
Everything except the GitHub-specific publishing runs anywhere that can execute a static binary. relixq pentest gate --dry-run --json works anywhere too — the decision JSON is computed offline; parse the verdict field yourself if you want QAST-style gating on a non-GitHub system.
The check-run and PR-comment surfaces of pr-comment and pentest gate, without --dry-run or --no-publish, are GitHub-only since they require GITHUB_TOKEN, GITHUB_REPOSITORY, and GITHUB_SHA. On other CI systems use --dry-run with the scan exit code instead.
Related pages
CLI: platform and QAST commands
The commands that talk to your tenant — login, org, use, remote-scan, submit, report, rules — plus the `pentest` group for quantum exposure assessment and the release gate.
Governance, policies and exceptions
The policy engine that renders release-gate decisions, the TTL-bounded exception workflow, and the tamper-evident audit chain that makes both defensible to an auditor.
QAST: Quantum Exposure Assessment
The adversary-validation layer: HNDL exposure classification via the Mosca inequality, attack-path projection, consent-gated read-only probing, remediation lifecycle, PTES/NIST reports, retest, and the PQC compatibility lab.