Pipeline and CI/CD Connectors
Connect your build pipeline to CVD Portal. The live connectors are SBOM push, SARIF ingest and FOSSA issues import.
SBOM push
Push a CycloneDX or SPDX JSON SBOM, or a dependency manifest such as package.json, from your pipeline.
| Attribute | Value |
|---|---|
| Method | POST |
| Path | /api/v1/sbom |
| Required scope | sbom:write, on a key made by an administrator |
| Plan | Enterprise |
| Body | { "filename": "sbom.cdx.json", "content": "<file contents>", "productId": "<optional>", "releaseVersion": "<optional>", "releasedAt": "<optional>" } |
| Rate limit | 20 uploads per minute per IP |
The push replaces the stored SBOM for its scope. With productId, the scope is that product. Without it, the scope is the account-wide SBOM. The push then checks the components against OSV.dev and updates the vulnerability findings for that scope before it returns. VEX statements in the vulnerabilities array of a CycloneDX SBOM are on the findings when the response arrives. A VEX status that an administrator set by hand always wins over the SBOM. The daily supply-chain scan sends the alert emails for new findings.
With releaseVersion, the push also stores the SBOM in the archive of that release. releaseVersion needs a productId. releasedAt is optional and takes a date such as 2026-09-30 or an ISO 8601 timestamp with an offset. A date that does not exist, for example 2026-02-30, gets HTTP 400. The push replaces the stored SBOM of the product only when the release is the newest. A push for an older release goes to the archive only. The vulnerability check then does not run, and refresh is null. See SBOM Registry for how the newest release is found.
jq -n --rawfile content sbom.cdx.json \
'{filename: "sbom.cdx.json", content: $content}' |
curl -fsS -X POST https://cvdportal.com/api/v1/sbom \
-H "Authorization: Bearer $CVD_API_KEY" -H "Content-Type: application/json" -d @-
A successful push returns HTTP 200.
{
"success": true,
"count": 3,
"source": "API",
"productId": null,
"productsCovered": 4,
"conformance": { "...": "BSI TR-03183-2 report" },
"minimumElements": { "...": "minimum elements report" },
"refresh": { "scanned": 3, "checked": 3, "unanswered": 0, "fresh": 1, "resolved": 1 },
"refreshError": null,
"release": null
}
refresh.scannedis the number of components in the SBOM.refresh.checkedis the number of components the scan checked against OSV.dev or NVD. A component is checked only when it has a version and a package URL (purl), or a CPE with a version. Whencheckedis 0, nothing was checked. The scan does not count as a check and closes no finding that the SBOM still lists.refresh.unansweredis the number of components that OSV.dev or NVD did not answer for. These components have the identifiers they need. Push again later, or wait for the next scan.refresh.freshis the number of findings that are open now and were not open before this push.refresh.resolvedis the number of findings that this push closed, because the SBOM no longer matches them.releaseisnullwhen the push has noreleaseVersion. Otherwise it holds the releaseid, theversion,createdandcurrent.createdistruewhen this push created the release.currentistruewhen this push also replaced the stored SBOM of the product.
When the vulnerability check fails, for example because OSV.dev does not answer, the SBOM is still stored and the response is still HTTP 200. refresh is then null and refreshError holds a message. The daily scan checks the SBOM again. Do not fail your build on refreshError.
SARIF static analysis ingest
Upload Static Analysis Results Format (SARIF) 2.1.0 reports from tools such as CodeQL, Semgrep, Trivy, and Grype.
| Attribute | Value |
|---|---|
| Method | POST |
| Path | /api/v1/scan/sarif |
| Required scope | sarif:write |
| Plan | Enterprise |
| Format | SARIF 2.1.0 JSON |
| Optional metadata | source (github-actions, gitlab-ci, or manual) and repo, in the query string or the body |
| Rate limit | 20 uploads per minute per IP |
Each result becomes a pipeline finding for your company. The Pipeline Findings page lists them. The server removes duplicates by fingerprint. A new upload of the same scan does not create duplicates, and it does not open a finding again that your team dismissed or fixed.
The verification evidence of a product shows the number of imported and open findings as static analysis evidence. The count covers all findings of the company. It does not link a finding to one product.
FOSSA issues import
Import the dependency vulnerabilities that FOSSA found. The body is the JSON response of the FOSSA issues API, GET https://app.fossa.com/api/v2/issues, or a bare array of its issues.
| Attribute | Value |
|---|---|
| Method | POST |
| Path | /api/v1/scan/fossa |
| Required scope | sarif:write |
| Plan | Enterprise |
| Format | FOSSA issues API v2 JSON ({ "issues": [...] }) |
| Optional metadata | repo and productId, in the query string or the body |
| Rate limit | 20 uploads per minute per IP |
Each issue of type vulnerability becomes a pipeline finding with the source fossa. An issue with a CVE, a FOSSA vulnerability id or a CVSS vector also counts as a vulnerability. Licensing and quality issues are not stored. The response counts them in warnings.
| Finding field | FOSSA field |
|---|---|
| Rule | cve, else vulnId |
| Severity | severity. When FOSSA says unknown, the cvss score decides. With no score, the severity is Medium. |
| Location | source.id, the package locator, for example npm+lodash$4.17.20 |
| Message | title, the package and version, details, and the fix version from remediation |
A finding is matched by its CVE (or FOSSA id) and its package locator within one product. The same CVE in the same package version is one finding, also when FOSSA lists it for more than one project. A re-upload refreshes the finding and never changes its status. When the export names one FOSSA project and you send no repo, the project locator is the repo.
FOSSA findings show on the Pipeline Findings page. They are dependency vulnerabilities from software composition analysis. They evidence Annex I Part II points (1) and (2). The static analysis evidence for clause 7.6 counts SARIF findings only.
FOSSA does not send outbound webhooks. Its webhook URL (/hooks/webhook/{project}) only starts a FOSSA analysis. Read the issues API in a CI step after fossa analyze and send the result to CVD Portal. CVD Portal never stores your FOSSA token.
fossa analyze
# fossa test exits 1 when FOSSA finds issues. Keep that status and export first.
test_status=0
fossa test || test_status=$?
curl -fsS -G https://app.fossa.com/api/v2/issues \
-H "Authorization: Bearer $FOSSA_API_KEY" \
--data-urlencode "category=vulnerability" \
--data-urlencode "scope[type]=project" \
--data-urlencode "scope[id]=$FOSSA_PROJECT_LOCATOR" \
-o fossa-issues.json
curl -fsS -X POST "https://cvdportal.com/api/v1/scan/fossa?productId=<product id>" \
-H "Authorization: Bearer $CVD_API_KEY" -H "Content-Type: application/json" \
--data @fossa-issues.json
exit "$test_status"
The FOSSA API returns issues in pages. Read each page with page and count, and send each page, or join the issues arrays first. One upload holds at most 5,000 findings.
A successful import returns HTTP 200.
{
"ingested": 12,
"created": 3,
"updated": 9,
"projects": ["custom+1234/acme-firmware"],
"warnings": ["Skipped 4 licensing or quality issue(s); only vulnerabilities are stored"]
}
SonarQube and VEX connectors
The Integrations settings page shows a SonarQube Quality Gate webhook (/api/webhooks/sonarqube) and a VEX ingest endpoint (/api/v1/vex) as "On request / Early access". These endpoints are not available.
To record VEX statements now, put them in the vulnerabilities array of a CycloneDX SBOM, or record them on the SBOM settings page. See SBOM Registry. Standalone OpenVEX and CSAF VEX documents are not read.
Optional query or body fields are source (github-actions, gitlab-ci or manual), repo, and productId. With productId, the findings are recorded for that product. Without it, they are recorded for the whole account. The product must belong to the company of the API key, otherwise the request returns 404 and records nothing. A finding is matched by its fingerprint within one product, so the same scan uploaded for two products keeps a separate finding for each. A re-upload refreshes the finding for the same product and never changes its status.
curl -X POST "https://cvdportal.com/api/v1/scan/sarif?source=github-actions&productId=<product id>" \
-H "Authorization: Bearer $CVD_API_KEY" -H "Content-Type: application/json" \
--data @results.sarif
Related guides
- Connecting GitHub covers automated nightly SBOM synchronization.
- CVD Portal app for Jira explains vulnerability synchronization to Jira projects.
- API Authentication documents API keys, OAuth scopes, and approval queues.