npm CLI
The RingZero npm CLI (ringzero-sca, from the @ringzero/sca package) reads
your project’s package-lock.json — the real resolved graph, direct and
transitive, exactly as npm installed it — and uploads a dependency snapshot to
RingZero for advisory matching. It covers the JavaScript/TypeScript row of the
ecosystem table the way the
Gradle plugin covers Gradle builds.
Requirements
Section titled “Requirements”| Component | Minimum |
|---|---|
| Node.js | 20 |
| Lockfile | package-lock.json v2 or v3 (npm 7+) |
| CLI version | 0.1.0 (current) |
Lockfile v1 (npm 6) is not supported — regenerate it with a current npm
(npm install --lockfile-version 3). Yarn and pnpm lockfiles aren’t read yet;
those projects can upload via the REST API directly.
Quick start
Section titled “Quick start”No install step and no account needed for a first look — the CLI runs in report-only mode without an API key:
npx @ringzero/sca scanRingZero: snapshot of 143 dependencies (12 direct, 131 transitive) from 4 workspacesRingZero: no API key configured — snapshot written to .ringzero/sca/snapshot.json, upload skipped. Set RINGZERO_API_KEY to upload.Inspect .ringzero/sca/snapshot.json — that’s exactly what an upload would
send: package names, versions, and graph edges. No source code, no
vulnerability data (advisory matching happens server-side).
To upload, provide a key and re-run:
RINGZERO_API_KEY=rz_key_5f2a… npx @ringzero/sca scanRingZero: snapshot of 143 dependencies (12 direct, 131 transitive) from 4 workspacesRingZero: uploaded — scan scn_7g8h queuedRingZero: https://app.ringzero.tech/scans/scn_7g8hThe scan id and app link are also written to .ringzero/sca/scan.json for
tooling. Add .ringzero/sca/ to your .gitignore — the reports are build
outputs, not config.
Provide your API key — securely
Section titled “Provide your API key — securely”Uploads authenticate with a scoped API key (rz_key_…), issued once by an
org admin in Settings → API keys. Create a dedicated key for this project
with the minimum it needs — scopes sca:upload (add scans:read if your
tooling polls results) and a project restriction covering just this
project. See Authentication & permissions for the full
scope and role model.
The key is read from the RINGZERO_API_KEY environment variable only —
there is deliberately no --api-key flag, because command lines end up in
process listings, shell history, and CI logs. The CLI never logs the key.
Local development — export the variable from your shell profile or a
git-ignored .env file loaded by your shell tooling.
CI — inject it from your platform’s secret store (see Running in CI).
Associate the upload with your project
Section titled “Associate the upload with your project”The snapshot names the RingZero project it belongs to, resolved in this order:
- The
--projectflag (highest precedence) - The
RINGZERO_PROJECTenvironment variable - The root
package.jsonnamefrom the lockfile
The fallback is fine for a first run, but pin the slug explicitly so package
renames don’t fork your scan history. A new slug creates the project on your
first upload. If your API key carries a
project restriction, a slug outside that list fails the
upload with 422 unknown_project — restricted keys can never create projects.
There is no organization setting: the server derives your org from the API key itself.
What the scan reads
Section titled “What the scan reads”ringzero-sca scan [path] parses package-lock.json at path (default:
the current directory):
- Lockfile v2 and v3 — the formats npm 7+ writes.
- npm workspaces — each workspace becomes its own root module in the
snapshot (with its relative
package.jsonas the manifest), so findings point at the workspace that actually pulls the vulnerable package in. Workspace symlinks and nestednode_modulesversion shadowing resolve the way Node itself resolves them. - Relationships — packages named in a workspace’s
dependencies,devDependencies,optionalDependencies, or installed peer dependencies aredirect; everything else istransitive. Dev-only trees are uploaded too (they run on your build machines), attributed to adevDependenciescontext per workspace. - Deterministic output — the same lockfile always produces byte-identical
snapshot.json, so re-uploads are idempotent: two CI jobs building the same commit converge on one scan.
Suppressions upload from this CLI is Coming soon
— snapshots currently carry an empty suppressions array; triaged findings
can be suppressed in the app, and the
suppressions file format is
final.
Report-only mode (no API key)
Section titled “Report-only mode (no API key)”With no RINGZERO_API_KEY set — or with --offline — the CLI still parses
the lockfile and writes snapshot.json; it just skips the upload and
succeeds, recording why in scan.json
({ "uploaded": false, "reason": "no-api-key" | "offline" }). This is the
same zero-friction canon as the Gradle plugin’s report-only mode: inspect
exactly what would be uploaded before creating a key, and keep air-gapped or
deliberately offline runs green.
Running in CI
Section titled “Running in CI”GitHub Actions example — key from the repository’s encrypted secrets, a fresh snapshot uploaded on every PR:
name: Build
on: pull_request:
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - run: npm ci - name: Upload dependency snapshot env: RINGZERO_API_KEY: ${{ secrets.RINGZERO_API_KEY }} run: npx @ringzero/sca scan --project acme-webThe same pattern applies to any CI system: store the key in the platform’s
secret store and expose it as RINGZERO_API_KEY. Concurrent jobs are safe —
uploads are idempotent on the snapshot’s content hash. Transient failures
(network, 5xx, 429) retry automatically with backoff, honoring the server’s
Retry-After.
Command reference
Section titled “Command reference”ringzero-sca scan [path] [options]| Option | Default | What it does |
|---|---|---|
--project <slug> | RINGZERO_PROJECT, then the root package.json name | The RingZero project (slug or prj_… id); created on first upload |
--lockfile <file> | <path>/package-lock.json | Explicit lockfile path |
--ecosystem <name> | javascript | javascript or typescript — the snapshot’s ecosystem tag |
--api-url <url> | RINGZERO_API_URL, then https://api.ringzero.tech/v1 | API base URL |
--output <dir> | <path>/.ringzero/sca | Where snapshot.json and scan.json are written |
--retries <n> | 3 | Upload retries for transient failures |
--offline | — | Force report-only mode; never uploads |
--json | — | Machine-readable result on stdout |
Precedence is flag > environment variable (RINGZERO_API_KEY,
RINGZERO_API_URL, RINGZERO_PROJECT — the same names the Gradle plugin
reads). Exit codes: 0 success (uploaded or report-only) · 1
snapshot/upload failure · 2 usage error.
Troubleshooting
Section titled “Troubleshooting”Every API response carries an X-Request-Id; the CLI prints it on failures —
quote it to support.
| Symptom | Cause | Fix |
|---|---|---|
401 invalid_token | Key missing/revoked/expired, or the env var isn’t reaching the CLI | Check RINGZERO_API_KEY in the environment that runs the scan; re-issue in Settings → API keys |
403 insufficient_scope | Key lacks sca:upload | Issue a key with the sca:upload scope |
422 unknown_project | The configured slug/id isn’t covered by the key’s project restriction | Pin --project to a slug/id in the key’s list, or issue a key without a restriction |
lockfile v1 … not supported | The project was locked with npm 6 | Regenerate: npm install --lockfile-version 3 |
| Snapshot written but nothing uploaded | No API key resolved — report-only mode | Set RINGZERO_API_KEY; the log line names the mode |
413 snapshot_too_large | Over 10,000 nodes or 10 MB (also enforced client-side) | Scan workspaces separately with [path] + --project |
Next steps
Section titled “Next steps”- The API the CLI calls — for direct integrations and debugging: REST API reference
- Scanning Gradle builds: Gradle plugin
- How findings and suppressions work: Overview