Security teams rarely consume vulnerability data in the console where it was generated. It gets piped into a Jira ticket, joined against an asset inventory, diffed against last week's scan, or pasted into a SARIF viewer for a pull request. The Snyk CLI supports that entire downstream workflow through one mechanism: a documented, machine-readable JSON output mode that sits alongside its human-readable terminal report. Rather than scraping colored text or parsing a table, tooling authors can request --json (or --sarif for code scans) and get a stable, field-level structure — vulnerability IDs, CVSS scores, dependency paths, upgrade guidance — that survives across npm, pip, Maven, container, and IaC scans. This post walks through how that output is actually shaped, where it breaks naive parsing (exit codes, multi-project arrays), and what official companion tools exist for turning it into reports rather than raw text.
What does snyk test --json actually return?
It returns a single JSON object describing every dependency vulnerability found in the scanned manifest, with fields designed to be diffed and joined programmatically rather than just read. The payload includes a vulnerabilities array where each entry carries an id (for example SNYK-JS-LODASH-567746), a title, a severity string (low, medium, high, critical), a CVSSv3 vector plus numeric cvssScore, an identifiers object holding any mapped CVE and CWE values, packageName and version, and a from array showing the exact dependency chain from the root package down to the vulnerable transitive dependency. Remediation fields sit next to the detection fields: isUpgradable, isPatchable, and upgradePath tell a script whether a fix is a version bump, a Snyk patch, or unavailable. Above the array, a summary, uniqueCount, dependencyCount, and top-level ok boolean give a scan-level pass/fail signal without iterating the whole array. Because this shape is consistent regardless of package manager, a single parser can handle an npm project on Monday and a Maven project on Tuesday.
Why do exit codes complicate JSON-based automation?
They complicate it because the presence of valid JSON on stdout doesn't tell you whether the command "succeeded" — the exit code does, and it's a separate signal from the payload. snyk test returns exit code 0 when no vulnerabilities are found at or above the configured severity threshold, 1 when vulnerabilities are found, 2 when the CLI itself errored (bad auth, network failure, malformed manifest), and 3 when no supported project was detected at the given path. A naive CI script that only checks $? -ne 0 to decide "did the scan run" will treat a totally normal "vulnerabilities found" result (exit 1) identically to a broken scan (exit 2), and will fail closed on every clean repository that happens to have zero dependencies to test (exit 3). Custom tooling built on top of --json has to branch on the exit code first and only trust the JSON body once it knows which of those four cases it's in — otherwise pipelines either silently swallow real findings or hard-fail on healthy scans.
How does --all-projects change the shape of the output?
It changes the top-level type from an object to an array, which is the single most common parsing bug teams hit when they scale from one manifest to a monorepo. Running snyk test --json against a single package.json yields one JSON object with a vulnerabilities key at the root. Running snyk test --all-projects --json against a repository containing, say, a package.json, a pom.xml, and a requirements.txt yields a JSON array with one result object per detected manifest, each carrying its own packageManager, path, and vulnerabilities list. A jq '.vulnerabilities' query that worked fine on the single-project case returns nothing (or errors) on the array case, because the vulnerabilities are now nested under .[0].vulnerabilities, .[1].vulnerabilities, and so on. Snyk also exposes --json-file-output=<path> specifically so tooling doesn't have to choose between machine-readable output and the readable console summary developers still want to see — the file gets the structured JSON while stdout keeps the familiar formatted report, which matters once --all-projects is in play and the array can span dozens of manifests in a large monorepo.
What official tooling exists for turning that JSON into reports?
Snyk ships two purpose-built companion CLIs — snyk-to-html and snyk-delta — precisely because raw JSON isn't a deliverable most stakeholders want to read. snyk-to-html takes the --json output on stdin (snyk test --json | snyk-to-html -o report.html) and renders it into a templated HTML vulnerability report, which is the pattern most teams use to attach a readable artifact to a build or a compliance packet instead of forwarding a JSON blob. snyk-delta takes two JSON snapshots — typically "before this PR" and "after this PR" — and computes the difference, so a pipeline can fail a build only when a change introduces a new vulnerability rather than failing every build on pre-existing findings the team has already triaged and accepted. That distinction matters operationally: without a delta step, teams either drown in false "regressions" on unrelated commits or turn severity gating off entirely out of alert fatigue. Both tools consume the same stable JSON contract described above, which is the point of documenting the format in the first place — it's meant to be a building block, not an end product.
How do teams wire this JSON into dashboards, tickets, and SIEMs?
They treat the JSON as an ETL source: a scheduled or CI-triggered snyk test --json (or snyk container test --json, snyk iac test --json) writes a file, a script parses the vulnerabilities array, and each record gets mapped into whatever system owns remediation tracking — a Jira issue keyed on the id field to avoid duplicate tickets on re-scan, a row in a vulnerability-management database joined on packageName and from to track which service owns the affected dependency, or an event shipped to a SIEM for correlation with runtime alerts. The snyk monitor --json variant is used differently: instead of a one-time scan, it registers a snapshot with Snyk's backend and returns an object containing id, isMonitored, and a uri pointing at the project's page in the Snyk web UI — tooling authors commonly embed that uri directly into the auto-generated ticket so a triager can click through to the live, continuously-updated project view rather than a static point-in-time file. For source-code (SAST) findings specifically, Snyk Code also supports --sarif in addition to --json, emitting SARIF 2.1.0 so results can flow into any SARIF-consuming code-scanning UI (including GitHub's) using the same run instead of a Snyk-specific parser.
How Safeguard Helps
The pattern above — a documented JSON contract, a couple of exit-code edge cases, and a small ecosystem of companion tools for turning findings into reports — is a useful reference point for how any scanner's output should behave if it's going to be safely wired into a supply chain security program. Safeguard works from the same principle when helping teams stitch together their existing tool output, Snyk included, into a single source of truth: normalizing severity and identifier fields (CVE, CWE, GHSA) across whatever scanners a team already runs, tracking which findings are newly introduced versus previously triaged so gating decisions are based on signal rather than noise, and preserving the dependency-path and remediation context (upgrade path, patch availability) that raw JSON carries but that gets lost when findings are flattened into a generic ticket description. For teams already generating snyk test --json output across dozens of repositories, Safeguard focuses on the layer above the scan — aggregating that structured data across an entire software supply chain, correlating it with SBOM and provenance information, and giving security and engineering teams one place to see what's actually exploitable and what's already been fixed, instead of reconciling scan output repository by repository.