CyberXYZ Gate,
in your pipeline.
Two steps for any pipeline. Protect routes the job's own installs through the CyberXYZ proxy; scan reads your lockfiles, checks every package against the same engine that answers the install proxy, and fails the build on a bad verdict. GitHub Actions, GitLab CI and Azure DevOps.
- uses: CyberXYZSecurity/depalert-action@v1
with:
api-key: ${{ secrets.XYZ_API_KEY }}
mode: protect # before your installs
- run: npm ci
- uses: CyberXYZSecurity/depalert-action@v1
with:
api-key: ${{ secrets.XYZ_API_KEY }}
fail-on: block # block | quarantine | alertGitLab: gitlab.com/cyberxyz/depalert/scan@1.2.0 and protect@1.2.0
What the gate does
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, uv.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 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 xyz Scanner. On GitHub it is one uses: line. The gate checks what the lockfile says; to
also stop a bad package while the job installs it, add the protect step before your
install steps. Or let xyz Scanner write the gate for whichever provider your repository uses:
pip install --upgrade cyberxyz-scanner
cd your-repo
xyz ci init # detects GitHub, GitLab or Azure from origin and writes the pipeline file
xyz ci init --fail-on quarantinepip 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 blockWhat a blocked pull request looks like
A permanently open pull request on the action's own repository bumps an example app to
faker@6.6.6, the release in which the maintainer gutted his own package: the tarball ships no code at
all, so anything that requires it breaks. npm never removed it. Four years on it is still the latest
tag, npm install faker succeeds, and the gate refuses it on every run. Open the pull request and
click Checks to see it live. This is the job output, verbatim:
▸ Run CyberXYZSecurity/depalert-action@v1
xyz depalert scan package-lock.json
XYZ DepAlert — scanning 2 packages…
1 Threat(s) Detected
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Package@Version ┃ Decision ┃ Scores ┃ Signals ┃ Safe Version ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ faker@6.6.6 │ BLOCK │ CVSS 7.5 │ 2 │ 5.5.3 │
│ │ │ XYZ 3.7 │ │ │
│ │ │ EPSS 0.59% │ │ │
└─────────────────┴──────────┴────────────┴─────────┴──────────────┘
1 clean 1 blocked 0 quarantined 0 alerted
✗ Gate FAILED: 1 threat(s) detected (worst: BLOCK)
Error: verdict block (exit 1), see the scan output aboveVerdicts 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.
0 all packages allowed
1 BLOCK known malicious version, critical advisory or org block list
2 QUARANTINE day-zero anomaly, held for review
3 ALERT advisory match, suspicious but not confirmed
4 ERROR backend unreachable, refusing to fail openGitHub Actions
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 xyz Scanner, finds your manifests and fails the build on a bad verdict.
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
To stop a malicious version while the job installs it, run the same action in mode: protect
before the install steps and keep the scan after them (see Protect):
steps:
- uses: actions/checkout@v4
- uses: CyberXYZSecurity/depalert-action@v1
with:
api-key: ${{ secrets.XYZ_API_KEY }}
mode: protect # route this job's installs through the CyberXYZ proxy
# network-lock: true # also blackhole the public registries on the runner
# strict: true # fail if the proxy could not be configured
- run: npm ci # refused at install time if a version is blocked
- uses: CyberXYZSecurity/depalert-action@v1
with:
api-key: ${{ secrets.XYZ_API_KEY }} # mode: scan (default) gates on the lockfiles
The action exposes verdict and exit-code outputs and sets XYZ_VERDICT for later
steps. Pin to @v1 for the moving major, @v1.2.0 for the exact release, or the commit SHA
if your policy requires it. Without the action, the same gate as plain steps (this version also runs Dependabot pull requests, which cannot read secrets, in advisory mode):
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 blockXYZ_API_KEY secret.GitLab CI
Add XYZ_API_KEY under Settings, CI/CD, Variables as masked and protected, then
include the CyberXYZ DepAlert component from the CI/CD
catalog. scan adds three jobs, one per ecosystem, and each only runs when its manifest exists.
Inputs: fail_on, stage, working_dir, api_url, cli_version. The same component
ships protect for the jobs that install dependencies (see Protect).
# one include, from the GitLab CI/CD catalog
include:
- component: gitlab.com/cyberxyz/depalert/scan@1.2.0
inputs:
fail_on: block # block | quarantine | alert
# or inline, without the component
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.
Azure DevOps
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.
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.
block known malicious versions, critical advisories start here
on the installed version, your org block list
quarantine plus day-zero anomaly signals recommended once you have a baseline
alert plus other CVE, GHSA and OSV advisories strictestStart 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. Quarantine holds the version for human review; block refuses it outright.
What you see in the dashboard
xyz Scanner detects the CI provider from its environment and names the runner: GitHub runs as
gha/<owner/repo>/<runner>, GitLab runs as gitlab/<group/project>/<runner>,
Azure runs as ado/<repo>/agent-<id>. In your own script steps, add --upload to
xyz depalert scan and each gate result is filed on the dashboard's XYZ Scan page under
CI gates with that name (the Action and the GitLab component do not upload yet). Jobs that use
protect register as ci/<provider>/<repo> (override with --machine-name), and every
install they make appears in the Proxy Monitor, with the signal that decided it.
Protect: live blocking on runners
The scan checks what the lockfile says. Protect checks what the job actually fetches: run it
early in the job and every later npm, yarn, pip, uv, Go and
NuGet install goes through the CyberXYZ proxy, the same way it does on a laptop. A blocked version is refused
by the proxy with HTTP 403 (a quarantined one with HTTP 451, or 403 on pip) and the install aborts. Recommended: protect before the install steps, scan as the gate.
- 01GitHub Actions
CyberXYZSecurity/depalert-action@v1withmode: protect(example under GitHub Actions). - 02GitLab CIinclude
gitlab.com/cyberxyz/depalert/protect@1.2.0and addextends: .cyberxyz-protectto the jobs that install dependencies, or- !reference [.cyberxyz-protect, before_script]when the job already has abefore_script. - 03Azure DevOps and any other CI
xyz ci protectwithXYZ_API_KEYset.
include:
- component: gitlab.com/cyberxyz/depalert/protect@1.2.0
inputs:
network_lock: false # true: also blackhole the public registries (needs root)
strict: false # true: fail the job when the proxy could not be configured
- component: gitlab.com/cyberxyz/depalert/scan@1.2.0
build:
extends: .cyberxyz-protect # its before_script runs first
image: node:20
script:
- npm ci # goes through the CyberXYZ proxypip install --quiet cyberxyz-scanner
eval "$(xyz ci protect --format shell)" # GitLab, Jenkins, CircleCI, any shell
xyz ci protect --format github # GitHub: appends to $GITHUB_ENV and masks the token
xyz ci protect --format azure # Azure DevOps: ##vso[task.setvariable] lines
# options
# --network-lock also blackhole the public registries in /etc/hosts (root or passwordless sudo)
# --strict exit non-zero when protection could not be applied
# --machine-name default ci/<provider>/<repo>
If CyberXYZ cannot be reached, protect warns and the job carries on unprotected; pass --strict
(strict: true) to fail it instead. The proxy token only lives in the job's environment: it is
never written to a dotenv artifact. For a long-lived self-hosted runner, enroll the machine once instead,
with an enrollment token from the dashboard and no background service:
xyz proxy setup --enrollment-token "$XYZ_ENROLLMENT_TOKEN" --no-install-daemon.
What it does not do
Said plainly, so nobody discovers it in an incident.
- It does not watch network traffic or processes on the runner. It is a dependency gate, not runner hardening.
- It does not block on lockfile drift, only on the resolved packages present at scan time.
- Without the protect step it does not intercept installs. It scans, then exits.
What is coming
- Check annotations on the pull request.
- Pull-request mode that gates only the dependencies the change introduced.
- Monitoring of popular third-party Actions for tag re-points and release tampering.
See the xyz Scanner reference for every xyz command, or
book a demo and we will wire the gate into your pipeline on the call.
Questions, answered.
Does the gate need an API key?
Yes. The scan authenticates with an XYZ_API_KEY stored as a repository or group secret. A key with read and scan scope is enough; the gate never needs admin scope. Without a key the job fails closed rather than passing silently.
What happens when CyberXYZ is unreachable?
The gate fails closed. A build that cannot reach a verdict exits 4 and stops, rather than installing an unchecked dependency. If you would rather ship during an outage, let exit code 4 pass in your pipeline: the Action exposes an exit-code output, and GitLab accepts allow_failure with exit_codes: [4].
Will it break builds on packages that are merely old or unpopular?
No. The gate fails on the verdict, not on age or download count. Block means a known malicious version, a critical advisory that affects that exact version, or an entry on your organization's block list. Quarantine means day-zero anomaly signals that hold the version for human review. Alert is advisory and does not fail the build unless you choose that threshold.
How long does the scan add to a build?
It reads the lockfile and asks for one batched verdict, so a typical repository adds a few seconds. It never downloads or executes a dependency to reach its answer.
Does it work on GitLab and Azure DevOps too?
Yes. The same command runs anywhere. GitLab has a published CI/CD catalog component, GitHub has a Marketplace action, and Azure DevOps runs xyz Scanner directly in a script step.
Which ecosystems does the gate read?
npm (package-lock.json), PyPI (requirements.txt, Pipfile.lock, poetry.lock, uv.lock) and Go (go.sum) lockfiles. Maven projects are not gated yet; a single Maven package can be checked with xyz check PACKAGE VERSION -e maven. It reads the lockfile you already commit; there is no separate manifest to maintain.
Watch it block a real attack. Then run it on your own installs.
Book 15 minutes. We run real packages through CyberXYZ while you watch, including axios 1.14.1, a hijacked release that dropped remote-control malware in March 2026, and show you exactly what your team would see. Then we connect one laptop or one pipeline for a 30-day proof of value.
What happens next
- 01A 15-minute callReal packages, real verdicts, and your questions.
- 02Connect one laptop or one pipelineOne setup command, or one line in CI. Under an hour.
- 03A 30-day proof of value on your own installsEvery signal, no card.
Not ready for a call?
Check any package, free, and see the verdict we would give it.
Thanks! We'll be in touch.
Check your inbox. We'll reach out within 24 hours.