cyberxyz-scanner
CyberXYZ Security CLI. Real-time supply-chain protection for npm, PyPI, Go and .NET (NuGet) on macOS, Linux and Windows.
The CLI pairs with the CyberXYZ platform to give you per-machine package inventory, proxy
enforcement on every npm, pip, go and dotnet install, and CI/CD gating on flagged
dependencies. It is the implementer's interface to a platform that also exposes the same
controls in a web dashboard.
Full documentation: cyberxyz.io/docs/cli.html covers install, login, proxy setup, every audit subcommand, API key auth, CI/CD integration, config / env vars, and troubleshooting. This README is the quick start.
Install
The package is published on PyPI as cyberxyz-scanner. The CLI binary it installs is named
xyz.
With pip
pip install cyberxyz-scanner
With uv
uv pip install cyberxyz-scanner
Verify the install:
xyz --help
Quick start (one-time per machine)
# 1. Sign in. Opens your browser at app.cyberxyz.io; sign in the way you do on the
# dashboard (password + authenticator app or passkey, or SSO), check the code
# matches the terminal, and approve. The CLI never sees your password.
xyz login
# 2. Enroll this machine. One command:
# - Registers the device with your organization
# - Points npm (~/.npmrc), pip, Go (GOPROXY) and NuGet at the CyberXYZ proxy,
# for each one that is installed
# - Installs the background service for dashboard "Scan now" support
# (LaunchAgent on macOS, systemd --user on Linux, Task Scheduler on Windows)
xyz proxy setup --machine-name "Alex's MacBook"
Over SSH or on a headless box, xyz login --no-browser prints the link and code to open
on any other device (the browser is also skipped automatically when SSH_CONNECTION is set
or Linux has no display). The legacy email and password prompt is still available as
xyz login --password (or $XYZ_EMAIL / $XYZ_PASSWORD for scripts); accounts with MFA
or an SSO-enforcing organization must use the browser login. xyz logout revokes the
session on the server and removes it locally.
The CLI session stays signed in while you use it and expires after 90 days without use.
While the CyberXYZ agent runs on the machine it refreshes the session daily, so you only sign in
once; a machine that stays off for 90 days signs out.
It is stored owner-only in ~/.xyz/config.json. Each
machine's session is listed under Settings > CLI sessions in the dashboard, where you
(or an org admin) can revoke it. Login codes expire after 10 minutes and work once: only
approve a code you just requested yourself, never one someone sent you.
That's it. Every later install on this device goes through the CyberXYZ proxy: the exact package version is checked before it downloads, blocked or quarantined packages are refused with the reason, and the install shows up in your dashboard.
For fleets, skip the interactive login: an org admin creates an enrollment token in the
dashboard (Machines > Enrollment) and MDM runs
xyz proxy setup --enrollment-token pxe_xyz_... (or places the token at
/Library/Application Support/CyberXYZ/enrollment-token).
For environments that should not run a long-running background process (CI build agents,
sealed builds), pass --no-install-daemon.
On company-managed machines, install the agent at system level so developers cannot stop it:
sudo xyz proxy setup --system # macOS / Linux
xyz proxy setup --system # Windows, from an elevated PowerShell
This installs a root/SYSTEM service (macOS LaunchDaemon io.cyberxyz.agent, Linux
cyberxyz-agent.service, Windows scheduled task CyberXYZAgent) that starts at boot and
restarts if it is killed. The macOS .pkg does the same, and enrolls automatically when MDM
places an enrollment token at /Library/Application Support/CyberXYZ/enrollment-token.
Without --system (or without admin rights) setup installs the per-user service as before.
Always-on protection
Every minute the background agent checks what each package manager will actually use, repairs anything that no longer points at the CyberXYZ proxy, and reports the result to the dashboard with its heartbeat. Repairs never remove a private or internal registry, and config files are only written for tools that are installed:
| Tool | What is checked and repaired |
|---|---|
| npm, pnpm | ~/.npmrc registry + token, pnpm's global rc; as root also <npm prefix -g>/etc/npmrc |
| yarn | ~/.yarnrc.yml npmRegistryServer (token scoped to the proxy host under npmRegistries) and registry in ~/.yarnrc |
| bun | ~/.bunfig.toml [install] registry |
| pip | user pip.conf / pip.ini: the proxy is the index-url; extra indexes on pypi.org are removed, private indexes are kept; as root also the system file |
| uv | uv.toml: the proxy is the default [[index]]; pypi.org indexes are removed, private indexes are kept; as root also the system uv.toml |
| Go | GOPROXY=<proxy>,direct (the proxy refuses blocked modules with 403, so there is no fall-through); <proxy> only while the network lock is on; as root also $GOROOT/go.env |
| NuGet | NuGet.Config: the CyberXYZ source is added and nuget.org sources removed; private feeds are kept |
| Poetry | cannot be forced globally, so it is covered by the network lock only |
The system-level agent does this for every local user account, writing files owned by that
user. The machine token is stored where the agent can always read it
(/Library/Application Support/CyberXYZ/machine-token, /etc/cyberxyz/machine-token,
%ProgramData%\CyberXYZ\machine-token, or ~/.xyz/machine-token per user), so deleting a
config file only gets it rewritten. Registry overrides in shell startup files
(NPM_CONFIG_REGISTRY, PIP_INDEX_URL, GOPROXY=direct, ...) and in the Windows user
environment are reported, never edited.
Network lock. When an org admin turns it on, the system-level agent also maps the public
registries (registry.npmjs.org, registry.yarnpkg.com, registry.npmmirror.com, pypi.org,
files.pythonhosted.org, proxy.golang.org) to 0.0.0.0 in the hosts file, so tools that ignore
config still cannot reach them. NuGet is not locked yet (it relies on config). Edits to the
lock are reverted and reported, and the block is removed when the org turns it off.
xyz proxy remove and uninstalling the service are reported to the dashboard before anything
is removed. Under the system service, only an administrator can remove it
(sudo xyz proxy remove). xyz proxy status shows the per-tool report, the service level,
the network lock state, when the agent last reported, and whether the agent is running the CLI
version that is installed. It works without sudo under the system service: config tokens are
compared with a fingerprint (sha256) the service publishes, never with a per-user copy.
Upgrades. pip install -U / pipx upgrade cannot restart a root service. The agent
(1.4.66+) notices the new version on disk and restarts onto it by itself within a few minutes,
between jobs. sudo xyz proxy restart does it immediately; the first CLI run after an upgrade
says so when the system agent is still on older code.
If CyberXYZ is unreachable
The proxy reuses its verdict for any package it checked in the last 24 hours. For anything else, your org's setting decides: block (the default; the developer sees "retry in a minute") or allow unchecked. Org admins change it in Settings > Supply-chain proxy.
Audit installed packages
Each command below audits the matching ecosystem on this machine, runs the CyberXYZ watchlist + deep check on suspect packages, and uploads the full inventory to the platform.
xyz audit npm # local + global node_modules
xyz audit python # active Python environment via pip
xyz audit venvs # every virtualenv / conda / pipx environment on the disk
xyz audit go # modules of the current Go module / GOPATH
xyz audit nuget # packages.lock.json files under cwd
xyz audit ide # VS Code / Cursor / Windsurf / VSCodium extensions
xyz audit browser # Chrome / Edge / Brave / Firefox extensions
xyz audit system # Homebrew, apt, rpm, winget, Chocolatey, macOS apps
xyz audit models # AI model files and Hugging Face references (see below)
xyz audit exposure # credentials an install-time payload could steal here (see below)
xyz audit # npm, python, venvs, go, ide, browser and system back-to-back
A bare xyz audit does not run nuget (it needs a lockfile), models or exposure; run
those on their own.
By default each command uses the watchlist pre-filter for speed (~25-40s on a typical
machine). Pass --full to skip the pre-filter and deep-check every package (slower but
covers advisory-only matches at scan time).
Conda environments (1.4.70+): a Python package that conda installed from an Anaconda or
conda-forge channel is not a PyPI package, so xyz audit python / venvs tell the API
where it came from. A conda-only name that PyPI does not have (for example
anaconda-anon-usage) is listed as ? name@version not checked: conda channel defaults
instead of being quarantined as "not on the public registry"; there is no conda registry
we index to check it against. Known-malicious and advisory checks still run by version,
and anything pip or uv installed into a conda env gets the full PyPI checks. A package
counts as conda-installed only when its INSTALLER file says conda and the env's
conda-meta/ records it.
xyz audit exposure: what an install-time payload could steal
The Shai-Hulud and Hades waves ran a payload from npm/PyPI install hooks that read every
environment variable, ~/.npmrc tokens, cloud credentials and CI secrets, then published
itself with what it found. xyz audit exposure lists what such a payload would find on this
machine or in this CI step:
xyz audit exposure # table
xyz audit exposure --json -o exposure.json
xyz audit exposure --fail-on high # CI gate: exit 1 on a high item
| Category | What is checked | High when |
|---|---|---|
env |
environment variables whose name (*_TOKEN, *_SECRET, *_PASSWORD, *_API_KEY, AWS_*, PYPI*, NPM_TOKEN, NODE_AUTH_TOKEN, TWINE_PASSWORD, GITHUB_TOKEN ...) or value (npm, PyPI, GitHub, GitLab, AWS key shapes, URLs with a password) looks like a credential |
publish token or cloud key |
ci |
GitHub Actions / GitLab / Azure Pipelines: GITHUB_TOKEN, ACTIONS_ID_TOKEN_REQUEST_URL (= id-token: write), how many secret-looking variables this step sees |
OIDC token request granted |
npm |
_authToken / _auth / _password in ~/.npmrc (or NPM_CONFIG_USERCONFIG) and the project .npmrc; npmAuthToken in .yarnrc.yml |
always (publish token) |
pypi |
password in ~/.pypirc |
always |
netrc |
~/.netrc (_netrc on Windows) machine passwords |
github.com, gitlab.com, npm, PyPI hosts |
docker |
inline auths in ~/.docker/config.json; a credsStore / credHelpers is listed as a safer setup |
always |
aws |
~/.aws/credentials and config: static keys (high), session keys (medium), SSO (safer setup) |
static key |
gcloud |
application_default_credentials.json, legacy_credentials/*/adc.json, credentials.db, GOOGLE_APPLICATION_CREDENTIALS |
refresh token or service-account key |
kube |
kubeconfig (KUBECONFIG list) embedded token, client-key-data, password; exec plugins are a safer setup |
embedded credential |
gh |
oauth_token in the gh CLI hosts.yml (keyring = safer setup) |
always |
git |
~/.git-credentials |
publish hosts |
ssh |
private keys in ~/.ssh without a passphrase (only the key header is read) |
never (medium) |
Each item is reported by name, location (your home shown as ~) and a fingerprint: the
first 8 hex characters of sha256(value), enough to see the same token in two places, not
enough to recover it. Values are never printed, written to a file or uploaded; the audit is
offline, read-only and starts no processes. In CI it also prints what to change: run installs
with --ignore-scripts, keep permissions: minimal (grant id-token: write only to the
deploy job, pass secrets to the one step that needs them) and route installs through the
proxy with xyz ci protect. Exit codes: 0, or 1 when an item is at or above --fail-on
(default none, report only).
Fix, blast radius, SBOM and AI models (1.4.62+)
All four read the manifests and lockfiles in the current directory, the same ones
depalert scan reads: package.json / package-lock.json, yarn.lock, pnpm-lock.yaml,
bun.lock, requirements*.txt,
Pipfile / Pipfile.lock, pyproject.toml / poetry.lock / uv.lock,
go.mod / go.sum, and NuGet packages.lock.json.
xyz fix: an upgrade plan for flagged dependencies
xyz fix # plan only: package, current, issue (KEV badge), → target, why
xyz fix --write # show a unified diff, confirm, then edit the specs
xyz fix --write --yes # no prompt (CI bots)
xyz fix --json
Every package is checked in one /proxy/check/batch call. For each flagged one
(block, quarantine, alert, or any advisory) the plan gives the nearest clean release
above the installed version. A clean installed version is never told to move. A
transitive package is traced to the direct dependency that pulls it ("via express →
debug, bump express"); transitive pins are never edited.
--write edits only direct dependency specs: package.json (keeps ^ / ~ / >=
and the file's formatting; complex ranges are listed for a manual edit) and == pins
in requirements*.txt (extras, markers and comments kept; hash-pinned lines are left
for pip-compile). Lockfiles are never touched: run npm install, poetry lock,
uv lock or pip-compile afterwards. Exit codes: 0 plan complete, 1 a flagged
package has no clean target, 4 could not check (backend unreachable, or no credential).
xyz impact: who is exposed when a package goes bad
xyz impact debug --version 2.6.8 # ecosystem guessed from the project, else npm
xyz impact requests -e pypi --json
Prints the direct dependents and whether each declared range admits the affected version, how many packages reach it on a required path vs only through an optional extra, and the top paths. Run inside a project, it also says whether this project reaches it and through which direct dependency. "Not yet indexed" means the path is unknown, not that there is none.
xyz sbom: CycloneDX 1.5 or SPDX 2.3
xyz sbom -o sbom.cdx.json # CycloneDX 1.5 JSON with a vulnerabilities section
xyz sbom --format spdx -o sbom.spdx.json # SPDX 2.3 JSON
xyz sbom --no-verdicts # components only, no network call
Components carry a purl (pkg:npm/…, pkg:pypi/…, pkg:golang/…, pkg:nuget/…),
the version and a scope (required / optional; dev dependencies are excluded),
and the dependency graph comes from the lockfile when it records one. Vulnerabilities
list the advisory ids, CVSS and EPSS ratings, a cyberxyz:kev property for CISA KEV
entries, and the safe version as the recommendation.
xyz audit models: AI model artifacts
xyz audit models # cwd + the Hugging Face cache
xyz audit models ./ml --no-hf-cache
xyz audit models --aibom -o aibom.cdx.json # CycloneDX 1.6 ML-BOM
Finds model files (.safetensors .bin .pt .pth .ckpt .gguf .onnx .pkl .h5) and hashes
them with a streamed sha256 (files over --max-hash-size are listed as UNHASHED),
Hugging Face references in code (from_pretrained, hf_hub_download, pipeline(model=…),
snapshot_download, flagging trust_remote_code=True), and models in
~/.cache/huggingface/hub. Files are never loaded or unpickled. The table shows each
repo or file with its verdict, load safety, trust_remote_code and reasons. NOT WATCHED,
PENDING and UNSCANNED are not verdicts. Exit 1 when anything is MALICIOUS.
KEV
When an advisory is in CISA's Known Exploited Vulnerabilities catalog, a bold KEV
marker leads the score cell in depalert scan, the vulnerability tables and the
xyz fix plan, and SARIF results carry "kev": true (plus a kev rule tag).
Use CyberXYZ from AI coding agents (MCP)
xyz mcp serve is a Model Context Protocol server:
Claude Code, Cursor, VS Code (Copilot agent mode), Windsurf, Codex CLI and Gemini CLI
call it to check packages, vulnerabilities, dependencies and AI models before they add
or install anything. It uses your xyz login session (or XYZ_API_KEY), needs
Python 3.10+ (the mcp package installs automatically there), and only ever speaks the
protocol on stdout.
xyz mcp install --client claude-code --hooks # MCP server + install hook, user scope
xyz mcp install --client cursor --scope project --rules
xyz mcp install --client all --dry-run # show every diff, change nothing
xyz mcp status # which clients are configured, does auth work
xyz mcp serve --tools # list the tools
| Client | --scope user |
--scope project |
|---|---|---|
claude-code |
claude mcp add --scope user (else ~/.claude.json) |
claude mcp add --scope project (else .mcp.json) |
cursor |
~/.cursor/mcp.json |
.cursor/mcp.json |
vscode |
printed (code --add-mcp … / MCP: Open User Configuration) |
.vscode/mcp.json (servers, type: stdio) |
windsurf |
~/.codeium/windsurf/mcp_config.json |
global only |
codex |
~/.codex/config.toml [mcp_servers.cyberxyz] |
global only |
gemini |
~/.gemini/settings.json |
.gemini/settings.json |
Files are merged, never overwritten (other servers and settings stay), the previous
version is kept as .bak, and a file that is not plain JSON (comments) is left alone
with the snippet printed. --rules adds the CyberXYZ dependency policy where the agent
reads rules (CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md
between markers, after asking; .cursor/rules/cyberxyz.mdc, .windsurf/rules/cyberxyz.md).
Tools
| Tool | What the agent gets |
|---|---|
check_package / check_packages |
install-time decision (allow / alert / quarantine / block) with behaviour signals (malicious release, typosquat, install scripts, dependency confusion, org block), advisories with CVSS / EPSS / KEV / fixed-in, a safe version and a one-line recommendation |
upgrade_plan |
is the installed version affected, nearest clean release above it, latest clean |
scan_project |
every manifest / lockfile in the project checked, flagged packages with the path from a direct dependency, what to bump, npm overrides |
dependency_paths / package_dependencies / blast_radius |
why a transitive package is installed, what a package pulls in, who depends on a bad version |
get_vulnerability / search_vulnerabilities |
one advisory in full; the advisory corpus by package, text, ecosystem, severity |
model_risk / model_load_safety / check_model_file |
Hugging Face model dependency risk, per-file load safety, lookup by sha256 |
machines_with_package |
which machines in your organization installed a package, at which version |
Plus the cyberxyz-dependency-policy prompt and resource: check before adding, never
install block / quarantine, prefer the safe version, unknown needs a human, never
trust_remote_code or pickle-load a model that is not CLEAN.
Install hook (Claude Code). --hooks adds a PreToolUse hook on Bash that runs
xyz hook check-install: npm|pnpm|yarn|bun add/install <pkg>, pip / uv pip install <pkg>, uv add, poetry add, pipx install, go get / go install, and
dotnet add package are checked in one call, and blocked or quarantined packages are
denied with the reason and a safe version. Lockfile installs (npm install, npm ci,
pip install -r …) pass: the proxy checks what they fetch. --hook-strict asks you on
alerts; the hook fails open with a warning if CyberXYZ is unreachable unless
--hook-fail-closed is set. It never auto-approves a command.
Lookups from the MCP server and the hook identify themselves (X-XYZ-Client: mcp /
agent-hook) so the platform can keep them out of the install log: asking about a
package is not installing it.
Which installs did an agent make? (1.4.71+). Coding agents set a marker in the
shells where they run commands (AI_AGENT, CURSOR_AGENT, CODEX_THREAD_ID,
COPILOT_AGENT, GEMINI_CLI, ...). The CLI sends it as X-XYZ-Agent. For npm and pip,
opt in with xyz proxy agent-hints --enable (or xyz proxy setup --agent-hints): in an
agent's shell npm and pip then add xyz-agent/<name> to their user agent, and the
dashboard marks those installs and registry misses as agent-driven. A person's terminal
is unchanged, nothing is inferred, and --disable removes the hook.
Other useful commands
# One-off safety check on a single package + version
xyz check axios 1.14.1 -e npm
# CI/CD gate. Non-zero exit on flagged packages.
xyz depalert scan --package-lock package-lock.json --fail-on block
xyz depalert scan --yarn-lock yarn.lock # yarn 1 classic and yarn 2-4 (berry)
xyz depalert scan --pnpm-lock pnpm-lock.yaml # lockfileVersion 5.x, 6.x, 9.x
xyz depalert scan --bun-lock bun.lock # text lockfile; not the binary bun.lockb
xyz depalert scan --requirements requirements.txt --fail-on quarantine
xyz depalert scan --requirements poetry.lock # also Pipfile.lock, uv.lock
xyz depalert scan --go-sum go.sum
xyz depalert scan -p axios@1.14.1 -p lodash@4.17.21
# SBOM upload (runs syft on a path, or takes a Syft / CycloneDX JSON SBOM)
xyz inventory upload ./my-app
xyz inventory upload --sbom syft.json
# Diagnostic / housekeeping
xyz --version
xyz proxy status # proxy config per tool, service level, network lock
xyz proxy whoami # what (org, machine) does my token resolve to
xyz proxy restart # restart the agent onto the installed CLI (sudo for the system service)
xyz proxy remove # restore default registries (reported to your dashboard)
xyz scans list # history of recent scans for your org
xyz upgrade # pull the latest release from PyPI
Code, IaC, secrets and image scanning (code-scan)
xyz code-scan runs open-source scanners installed on your machine (it can install missing
ones with your own package manager, after asking) and merges what they report into one table,
JSON document or SARIF 2.1.0 file. It works without logging in; when you are signed in the
results also go to your CyberXYZ dashboard (--no-upload or XYZ_NO_UPLOAD=1 to opt out;
secret values are never sent). For installed packages use xyz audit.
Run it from a project. /, your home, the temp directory, or a folder with no project
markers (.git, package.json, pyproject.toml, go.mod, *.csproj, ...) is scanned only
after a yes on a terminal; in CI or without a terminal it is refused unless --force-target.
xyz code-scan secrets . # hardcoded credentials (values always redacted)
xyz code-scan iac ./infra # Terraform, CloudFormation, Kubernetes, Helm, Dockerfile
xyz code-scan image python:3.11-slim --sbom sbom.cdx.json # vulns + misconfig + secrets, CycloneDX SBOM
xyz code-scan code . # SAST with the built-in xyz rules
xyz code-scan code . --config ./my-rules.yml # add your own opengrep rules
xyz code-scan ci . # GitHub Actions / GitLab CI / Azure Pipelines hardening (built in)
xyz code-scan all . --format sarif -o xyz.sarif --fail-on high
xyz code-scan engines # which engines are installed, versions, paths
| Command | Engine | Fallback / option |
|---|---|---|
secrets |
gitleaks (dir on 8.19+, detect --no-git before) |
trivy fs --scanners secret, or --engine trivy; --history adds gitleaks git (no trivy fallback) |
iac |
trivy config |
checkov with --engine checkov |
image |
trivy image (vuln, misconfig, secret) |
--sbom FILE writes CycloneDX |
code |
opengrep + built-in xyz rules + any --config |
without opengrep: bandit for Python, gosec for Go |
ci |
built in (no engine) | --first-party-pins off|info|low|medium|high (default info) |
all |
secrets + iac + code, merged (--include-ci adds ci; default from the next minor) |
--skip-missing runs whatever is installed; add --fail-on-partial to exit 5 when a scanner was skipped |
The built-in rules (xyz_cli/rules/) cover supply-chain patterns: shell=True and
os.system with dynamic commands, eval/exec of downloaded data, curl | sh in scripts, CI
files and Dockerfiles, pickle.load, torch.load without weights_only=True,
trust_remote_code=True, hardcoded cloud keys and private keys, disabled TLS verification
(verify=False, NODE_TLS_REJECT_UNAUTHORIZED=0, InsecureSkipVerify), child_process.exec
with template strings, and npm install scripts that download binaries. Without opengrep the
built-in rules do not run and xyz says so.
CI pipeline hardening (ci). Supply-chain attacks now land in the pipeline itself: a
third-party action whose tag is moved to malicious code (tj-actions/changed-files), a
pull_request_target workflow that builds a fork's code with the repository's secrets, an
issue title pasted into a shell script, a postinstall hook that reads the deploy token from
the job's environment (Shai-Hulud). Trivy and Checkov barely look at workflow files, so
xyz code-scan ci checks them itself: it parses .github/workflows/*.yml, action.yml,
.gitlab-ci.yml and azure-pipelines*.yml and needs nothing installed. Findings are
category iac (they go to the dashboard's IaC tab) with rule ids:
| Rule | Severity | What it finds |
|---|---|---|
ci-unpinned-action |
high (branch) / medium (tag); actions/*, github/* at --first-party-pins (default info) |
uses: an action or reusable workflow by tag or branch instead of a 40-character commit SHA |
ci-unpinned-docker-image |
medium | uses: docker://image:tag without @sha256: |
ci-secrets-to-unpinned-action |
high | an unpinned third-party action or reusable workflow gets secrets (with:, env:, job env, secrets: inherit) or runs in a job with id-token: write |
ci-pwn-request |
high (low behind a same-repository guard) | pull_request_target, workflow_run or issue_comment workflows that check out or fetch the pull request's code |
ci-script-injection |
high | ${{ github.event.pull_request.title }}, bodies, comments, commit messages, github.head_ref and other attacker-controlled values inside run: or actions/github-script; Azure $(Build.SourceBranch)-style macros in scripts |
ci-input-injection |
low | free-text ${{ inputs.x }} inside run: (boolean, number and choice inputs are skipped) |
ci-permissions-missing |
medium | no top-level permissions: and a job without its own |
ci-permissions-write-all |
high | permissions: write-all |
ci-id-token-workflow-level |
medium | id-token: write granted to every job of a multi-job workflow |
ci-install-scripts-with-secrets |
medium (low for pnpm/bun, or after xyz ci protect) |
npm ci/npm install/yarn/pnpm install/bun install without --ignore-scripts, or pip install of unpinned or unhashed requirements (requirements files are read), in a job holding secrets or an OIDC grant |
ci-checkout-persist-credentials |
medium (write token) / low (repository default) / info (read-only) | actions/checkout without persist-credentials: false in a job that later runs third-party code |
ci-gitlab-unpinned-include |
medium | GitLab include: of a project without a SHA ref:, a remote: without integrity:, or a component on a moving version |
ci-invalid-workflow |
medium | the file is not valid YAML, so none of the checks ran on it (line and column from the parser); never reported as clean |
Safe patterns are not reported: ${{ github.event.pull_request.number }} or head.sha in a
script, untrusted values passed through env: and used as "$TITLE", and actions/checkout
in a pull_request_target workflow without a pull-request ref:. Put # xyz:ignore (or
# xyz:ignore[rule-id]) on a line to accept a finding. Fix guidance is in each message; the
SHA to pin to is not looked up (the check is offline).
Common options: --format table|json|sarif, -o FILE, --fail-on critical|high|medium|low|none
(default low, meaning any non-info finding fails), --timeout SECONDS per engine (default
900). Severities are normalised to critical/high/medium/low/info, paths are relative to the
scanned directory, and duplicate findings are merged, including the same secret found by two
engines. Secret values are never printed in any format; secret findings carry no snippet.
SARIF suppressions (# nosec, nosemgrep, gitleaks:allow) are honoured.
| Exit | Meaning |
|---|---|
| 0 | Clean: nothing at or above --fail-on |
| 1 | Findings at or above --fail-on |
| 2 | Usage error |
| 3 | Engine not installed: a requested scanner could not run (the install command for your OS is printed) |
| 4 | Engine error: non-zero exit, timeout, or no report (the engine's stderr tail is printed) |
| 5 | Partial, opt-in (all --skip-missing --fail-on-partial): nothing at or above --fail-on in the scanners that ran, but at least one was skipped because its engine is missing. Without --fail-on-partial, --skip-missing exits 0 in that case, as before |
A run that skipped a scanner is never reported as clean. The table ends with, for example,
Partial: secrets scanned, IaC and SAST skipped (engines missing). instead of No findings,
and JSON, SARIF (runs[0].properties) and the dashboard upload carry status
(complete / partial / failed), coverage and one scanners entry per requested scanner
(ran, skipped with reason: "engine missing", or failed with reason: "engine error").
Secrets in git history (--history). A secret that was committed and later deleted is
gone from the working tree but not from the repository: every clone and the hosting
provider still have it. xyz code-scan secrets --history (and xyz code-scan all --history)
also runs gitleaks git over the commit history. Each history finding carries the commit
sha, its author date, the file path and line at that commit, and in_head: whether HEAD still
holds the same secret. One that is gone from HEAD says removed from tree but still in git
history — rotate it, rewriting history is not enough once pushed; one committed only on
another branch says so. A secret still in HEAD that the working-tree scan already reports is
not listed twice.
xyz code-scan secrets . --history # every ref (git log --all)
xyz code-scan secrets . --history --history-depth 200 # last 200 commits of HEAD
xyz code-scan secrets . --history --since-commit origin/main # only what this branch adds
The same guarantees hold: values are never printed or uploaded (gitleaks' report is read
from a pipe, never written to disk; xyz checks HEAD for the value by handing it to git grep
on stdin, then drops it), the repo's .gitleaks.toml is extended and .gitleaksignore /
gitleaks:allow are honoured, and --exclude, the dependency/build-folder skip and the
known-benign values still apply (gitignored paths are not skipped in history: a .env
committed before it was gitignored is what this is for). Trivy has no git-history mode: with
only trivy installed, --history stops with "engine not installed" (exit 3) and scans
nothing; all --history --skip-missing reports secrets history skipped. History findings
appear in JSON (commit, commit_date, in_head, reachable_from_head), in SARIF
properties (commit, commitDate, inHead) and in the dashboard upload. In CI, check out
the history (actions/checkout with fetch-depth: 0); xyz warns when the clone is shallow.
Installing the engines. xyz never downloads an engine itself. It looks on PATH and in
the usual install folders (Homebrew's bin, ~/.local/bin, ~/.opengrep/cli). When an
engine is missing and you are at a terminal, it asks once for all of them:
Missing engines for this scan:
trivy brew install trivy
opengrep curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash
(official opengrep install script; installs to ~/.opengrep/cli and links ~/.local/bin/opengrep)
Install trivy and opengrep now (opengrep runs its official install script)? [Y/n]
On yes it runs your own package manager (or opengrep's official script), checks the engine is
now there and prints its version and path, and continues the same scan. --install-engines
installs without asking (for setup scripts), --no-install never asks. In CI (CI,
GITHUB_ACTIONS, GITLAB_CI, ...) or without a terminal xyz never asks and never installs;
it prints the command. xyz code-scan engines lists every engine with its version, path and
install command; xyz code-scan engines --install installs the missing ones.
Which installer xyz picks, from what the machine already has:
| Engine | macOS | Linux | Windows |
|---|---|---|---|
| trivy | brew install trivy |
brew install trivy if Homebrew is present; otherwise Aqua's official apt or rpm repository steps are printed for you to run (they need sudo) |
winget install --id AquaSecurity.Trivy --exact, else scoop install trivy |
| gitleaks | brew install gitleaks |
brew install gitleaks; otherwise printed: sudo apt-get install -y gitleaks or go install |
winget install --id Gitleaks.Gitleaks --exact, else scoop install gitleaks |
| opengrep | official script curl -fsSL https://raw.githubusercontent.com/opengrep/opengrep/main/install.sh | bash (there is no Homebrew formula and no PyPI package; the script verifies the release signature when cosign is installed) |
same | official install.ps1 from the same repo, via PowerShell |
The fallbacks are printed only, never installed by xyz:
| Engine | macOS | Linux | Windows |
|---|---|---|---|
| bandit | pip install "bandit[sarif]" |
same | same |
| gosec | brew install gosec |
go install github.com/securego/gosec/v2/cmd/gosec@latest |
same as Linux |
| checkov | pip install checkov |
same | same |
In CI. GitHub Actions: run xyz code-scan all . --format sarif -o xyz.sarif and upload
the file with github/codeql-action/upload-sarif (set if: always() on the upload so
findings still show when the gate fails). Each run is tagged
automationDetails.id = xyz-code-scan/<scan>/, so secrets, iac and code can be
uploaded separately without replacing each other.
- run: pip install cyberxyz-scanner "bandit[sarif]"
- run: go install github.com/zricethezav/gitleaks/v8@latest && echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- run: xyz code-scan all . --skip-missing --format sarif -o xyz.sarif --fail-on high
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: xyz.sarif
GitLab CI: run the same command in a job and keep xyz.sarif (or --format json) as a job
artifact. GitLab's Security dashboard reads its own report format, not SARIF, so treat the
exit code as the gate.
Licensing. xyz uses Trivy (Apache-2.0), Gitleaks (MIT), Opengrep (LGPL-2.1) as external engines, plus bandit (Apache-2.0), gosec (Apache-2.0) and checkov (Apache-2.0) when present. They run as separate programs; none of them is bundled. The built-in rules are CyberXYZ's own, released under MIT. xyz does not use or reference Semgrep Registry rules.
CI/CD integrations
Set XYZ_API_KEY as a secret and add one of these; any push or PR that pulls in a
malicious or vulnerable package fails the build with a clear reason. Create the key at
https://app.cyberxyz.io/dashboard/api-keys (a free account is enough:
https://app.cyberxyz.io/register). The gate needs it: without a key, or with a key the API
rejects or that is not bound to an organisation, it exits 4 and checks nothing; it never
falls back to anonymous checks.
- GitHub Actions:
uses: CyberXYZSecurity/depalert-action@v1(GitHub Marketplace) - GitLab CI/CD catalog:
gitlab.com/cyberxyz/depalert - Azure DevOps Pipelines:
integrations/azure-pipelines/cyberxyz-supply-chain.yml - Or generate one:
xyz ci init
Route the job's own installs through the proxy (protect)
scan checks lockfiles after the fact. protect runs early in the job and points npm,
yarn, pip, uv, Go and NuGet at the CyberXYZ proxy, so a malicious version is refused at
install time, the same way it is on laptops. Recommended: protect before the install
steps, scan as the gate.
# GitHub Actions
- uses: CyberXYZSecurity/depalert-action@v1
with:
api-key: ${{ secrets.XYZ_API_KEY }}
mode: protect # network-lock: true also blocks the public registries on the runner
- run: npm ci # goes through the proxy
- uses: CyberXYZSecurity/depalert-action@v1
with:
api-key: ${{ secrets.XYZ_API_KEY }} # mode: scan (default) gates on the lockfiles
GitLab: include gitlab.com/cyberxyz/depalert/protect@1.2.0 and add
extends: .cyberxyz-protect (or - !reference [.cyberxyz-protect, before_script]) to the
jobs that install dependencies. Any other CI: eval "$(xyz ci protect --format shell)"
with XYZ_API_KEY set. If CyberXYZ is unreachable, protect warns and the build carries
on unprotected; pass --strict (strict: true) to fail it instead.
The Action (cli-version) and the GitLab components (cli_version) install the newest
1.4.x CLI, at least 1.4.66 (>=1.4.66,<1.5), so CLI fixes reach your pipelines without a
new action release and a 1.5 never arrives unannounced. Pin an exact version ("1.4.70")
if you need reproducible runs, or pass latest.
All of them run the same xyz depalert scan engine your laptops use. It reads
package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock, requirements*.txt,
Pipfile.lock, poetry.lock, uv.lock and go.sum. --package-lock also accepts a
yarn, pnpm or bun lockfile and reads it by its name. Each package is checked under its
registry name: npm aliases as the package they install, workspace, link:, file: and
git dependencies left out. bun.lockb is binary and is refused with exit 4: run
bun install --save-text-lockfile (the default from bun 1.2) and commit bun.lock.
depalert scan exit codes
| Exit | Meaning |
|---|---|
| 0 | Allowed |
| 1 | Block |
| 2 | Quarantine |
| 3 | Alert |
| 4 | Could not check: the API was unreachable, a manifest could not be read, no credential (xyz login or XYZ_API_KEY), or the credential was rejected / is not bound to an organisation. Never treated as clean; before 1.4.74 a missing credential exited 1, the BLOCK code |
Re-enroll, rotate, remove
To rotate the proxy token on a device, re-run xyz proxy setup --machine-name "..." as
the same user who enrolled it. The platform replaces the old token and the daemon picks up
the new one at next restart. A machine name registered by another member of your org
can only be re-issued by an org admin; pick a different --machine-name otherwise.
To remove a device cleanly, delete it from the dashboard Fleet view. The deletion sweeps proxy_install_log, proxy_tokens, cli_scans, customer_inventory_uploads, customer_package_inventory and scan_jobs in one transaction. Re-enroll with the same command above.
Platform
- Dashboard: https://app.cyberxyz.io
- Documentation: https://cyberxyz.io/docs/cli.html
License
Proprietary. See LICENSE.
Contact
Email: support@cyberxyz.io
Metadata
Release files for cyberxyz-scanner 1.4.74
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cyberxyz_scanner-1.4.74.tar.gz | 612.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cyberxyz_scanner-1.4.74-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 996.0 kB
Release files / cyberxyz_scanner-1.4.74.tar.gz
| Download URL | cyberxyz_scanner-1.4.74.tar.gz |
|---|---|
| Size | 612.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b39e1067b4197c22e462e9ff5e8fe7cbe1c39f3ee90cdaefbb058f0a2738dbb8
|
|
BLAKE2b-256 checksum How to use checksums |
231cb7467575f215c81f77f45ebeaf30434f07e999d1743d5dfd6201c3b6e9ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logRelease files / cyberxyz_scanner-1.4.74-py3-none-any.whl
| Download URL | cyberxyz_scanner-1.4.74-py3-none-any.whl |
|---|---|
| Size | 383.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd3cb7d99b641309a24a18b2ccd7e4e018ba6385990dc5e2241166d855d3537c
|
|
BLAKE2b-256 checksum How to use checksums |
123fa8589e86fb4df2dfd791fa6b51fc378110191637bc2719b4957c5462b6fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency log