Back to home
CI/CD Guide GitHub Actions · GitLab CI · Azure DevOps

The XYZ gate
in your pipeline

One job, three platforms. xyz depalert scan reads your lockfile, checks every package against the same engine that answers the install proxy, and fails the build on a bad verdict. Every run shows up in the dashboard under the runner that produced it.

CyberXYZ Security Team Product Documentation
8 min read
On this page
  1. What the gate does
  2. Verdicts and exit codes
  3. GitHub Actions
  4. GitLab CI
  5. Azure DevOps
  6. Choosing the threshold
  7. What you see in the dashboard
  8. Live blocking on runners
  9. What it does not do
  10. What is coming

The gate is a one-shot scan of the dependency manifests in the repository. It parses package-lock.json, requirements*.txt, Pipfile.lock, poetry.lock and go.sum, sends the resolved package list to /api/v1/proxy/check/batch, and gets back one verdict per package: allow, alert, quarantine or block. The verdict comes from the same six detection signals the install-time proxy uses, so a package that would be refused on a developer laptop is refused in CI too.

It runs on every push and pull request that touches a manifest, in about the time it takes to install the CLI. On GitHub it is one uses: line. Dependabot pull requests run in advisory mode because they cannot read secrets. Or let the CLI write the file for whichever provider your repository uses:

xyz ci init · shellbash
pip install --upgrade cyberxyz-scanner   # 1.4.56 or later
cd your-repo
xyz ci init            # detects GitHub, GitLab or Azure from origin and writes the pipeline file
xyz ci init --fail-on quarantine
xyz depalert scan · shellbash
pip install --quiet cyberxyz-scanner

xyz depalert scan --package-lock package-lock.json --fail-on block
xyz depalert scan --requirements requirements.txt  --fail-on block
xyz depalert scan --go-sum go.sum                   --fail-on block

# ad hoc, no lockfile
xyz depalert scan -p axios@1.14.1 -p lodash@4.17.21 --fail-on block

Verdicts and exit codes

The build fails on any exit code above zero. The gate fails closed: if the backend cannot be reached, the scan exits 4 rather than letting an unchecked package through.

exit codestext
0   all packages allowed
1   BLOCK       confirmed malicious version in the manifest
2   QUARANTINE  day-zero anomaly, held for review
3   ALERT       advisory match, suspicious but not confirmed
4   ERROR       backend unreachable, refusing to fail open

Add XYZ_API_KEY under Settings, Secrets and variables, Actions, then commit this workflow as .github/workflows/xyz-depalert.yml. The CyberXYZ DepAlert action from the GitHub Marketplace installs the CLI, finds your manifests and fails the build on a bad verdict.

.github/workflows/xyz-depalert.ymlyaml
name: CyberXYZ gate
on: [push, pull_request]

jobs:
  depalert:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: CyberXYZSecurity/depalert-action@v1
        with:
          api-key: ${{ secrets.XYZ_API_KEY }}
          fail-on: block        # block | quarantine | alert

The action exposes verdict and exit-code outputs and sets XYZ_VERDICT for later steps. Pin to @v1 for the moving major, @v1.0.0 for the exact release, or the commit SHA if your policy requires it. Without the action, the same gate as plain steps:

.github/workflows/xyz-depalert.ymlyaml
name: XYZ Dependency Security Gate
on:
  push:
    branches: ["main", "master", "develop"]
    paths: ["package-lock.json", "yarn.lock", "requirements*.txt", "Pipfile.lock", "poetry.lock", "go.sum"]
  pull_request:
    branches: ["main", "master", "develop"]
    paths: ["package-lock.json", "yarn.lock", "requirements*.txt", "Pipfile.lock", "poetry.lock", "go.sum"]
  workflow_dispatch:

jobs:
  xyz-depalert:
    runs-on: ubuntu-latest
    # Dependabot PRs cannot read secrets: advisory mode, never a red build
    continue-on-error: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11", cache: "pip" }
      - run: pip install --quiet cyberxyz-scanner
      - name: Scan package-lock.json
        if: hashFiles('package-lock.json') != ''
        env:
          XYZ_API_KEY: ${{ secrets.XYZ_API_KEY }}
          XYZ_API_URL: https://api.cyberxyz.io
        run: xyz depalert scan --package-lock package-lock.json --fail-on block
      - name: Scan requirements.txt
        if: hashFiles('requirements.txt') != ''
        env:
          XYZ_API_KEY: ${{ secrets.XYZ_API_KEY }}
          XYZ_API_URL: https://api.cyberxyz.io
        run: xyz depalert scan --requirements requirements.txt --fail-on block
Add it to your repository in one clickOpens GitHub's new-file page with the workflow above already filled in. Review, commit, then add the XYZ_API_KEY secret.
github.com/

Add XYZ_API_KEY under Settings, CI/CD, Variables as masked and protected. Copy the template as .gitlab-ci.yml, or include it from an existing pipeline. It runs three parallel jobs, one per ecosystem, and each only fires when its manifest exists.

.gitlab-ci.ymlyaml
include:
  - remote: 'https://raw.githubusercontent.com/CyberXYZSecurity/XYZ-Exploitability-Scanner/main/integrations/gitlab-ci/cyberxyz-supply-chain.gitlab-ci.yml'

# or inline
image: python:3.11-slim
stages: [scan]
variables:
  XYZ_API_URL: "https://api.cyberxyz.io"
  XYZ_FAIL_ON: "block"
before_script:
  - pip install --quiet cyberxyz-scanner

xyz_depalert_npm:
  stage: scan
  rules: [{ exists: ["package-lock.json", "yarn.lock"] }]
  script:
    - xyz depalert scan --package-lock package-lock.json --fail-on "$XYZ_FAIL_ON"

xyz_depalert_python:
  stage: scan
  rules: [{ exists: ["requirements.txt", "Pipfile.lock", "poetry.lock"] }]
  script:
    - for f in requirements.txt requirements-dev.txt Pipfile.lock poetry.lock; do
        if [ -f "$f" ]; then xyz depalert scan --requirements "$f" --fail-on "$XYZ_FAIL_ON"; fi; done

xyz_depalert_go:
  stage: scan
  rules: [{ exists: ["go.sum"] }]
  script:
    - xyz depalert scan --go-sum go.sum --fail-on "$XYZ_FAIL_ON"

The template uses the python:3.11-slim image and runs unchanged on any Docker-executor runner. On shell executors, drop the image: line and make sure python3 is on the path.

Add XYZ_API_KEY as a secret pipeline variable, then wire the template as the build pipeline. It mirrors the GitHub workflow: same triggers, same manifests, same XYZ_FAIL_ON variable, and runs on the ubuntu, macOS and Windows hosted agents.

azure-pipelines.ymlyaml
trigger:
  branches: { include: [main, master, develop] }
  paths:    { include: ["package-lock.json", "yarn.lock", "requirements*.txt", "Pipfile.lock", "poetry.lock", "go.sum"] }
pool: { vmImage: 'ubuntu-latest' }
variables:
  XYZ_API_URL: 'https://api.cyberxyz.io'
  XYZ_FAIL_ON: 'block'
steps:
  - task: UsePythonVersion@0
    inputs: { versionSpec: '3.11', addToPath: true }
  - script: pip install --quiet cyberxyz-scanner
  - script: |
      if [ -f package-lock.json ]; then xyz depalert scan --package-lock package-lock.json --fail-on "$XYZ_FAIL_ON"; fi
      if [ -f requirements.txt ];  then xyz depalert scan --requirements requirements.txt  --fail-on "$XYZ_FAIL_ON"; fi
      if [ -f go.sum ];            then xyz depalert scan --go-sum go.sum                   --fail-on "$XYZ_FAIL_ON"; fi
    env:
      XYZ_API_KEY: $(XYZ_API_KEY)

Choosing the threshold

XYZ_FAIL_ON sets the narrowest verdict that turns the build red.

XYZ_FAIL_ONtext
block        confirmed malicious only            start here
quarantine   plus day-zero anomaly signals       recommended once you have a baseline
alert        plus CVE, GHSA and OSV advisories   strictest

Start with block, watch the dashboard for a week, then tighten. Quarantine is where the day-zero catches live: new-dependency injection, version jumps and lifecycle-hook changes that have no advisory yet.

What you see in the dashboard

The CLI detects the CI provider from its environment and tags every scan with the pipeline, job and runner. GitHub runs appear as gha/<owner>/<repo>, GitLab runs as gitlab/<group>/<project>/<runner>, Azure runs as ado/<agent>. Filter the Environments view by machine name to see every run from one project, and the proxy log for exactly which package tripped which signal.

The gate checks what the manifest says. To gate what actually gets fetched during npm install, pip install or go get, put the install-time proxy on the runner in a before_script, with an enrollment token from the dashboard so the job needs no interactive login. Blocks then return HTTP 403 from the registry itself and the install aborts.

before_script · any runneryaml
before_script:
  - pip install --quiet cyberxyz-scanner
  - xyz proxy setup --machine-name "ci/$CI_PROJECT_PATH" --enrollment-token "$XYZ_ENROLLMENT_TOKEN" --no-install-daemon
  # npm, pip and go now resolve through proxy.cyberxyz.io for the rest of the job

What it does not do

Said plainly, so nobody discovers it in an incident.

What is coming

See the CLI reference for every xyz command, or book a demo and we will wire the gate into your pipeline on the call.

👋

Let's Talk

Want to learn how CyberXYZ protects your supply chain? We'd love to hear from you.