Skip to content

Latest commit

 

History

History
210 lines (162 loc) · 8.15 KB

File metadata and controls

210 lines (162 loc) · 8.15 KB

QBOM Discovery API — Developer Guide

The Post-Quantum Repository (PQR) service. A pure-Node HTTP API (no web framework) that wraps the precogs-cbom engine so ECUs, CI pipelines, and other tools can register projects and request CBOMs/QBOMs on demand instead of running the CLI. Every scan is persisted per project and rolled up into a fleet view.

For backend developers: this is the service to deploy and (optionally) put behind your gateway / swap the store for Postgres. For frontend developers: the JSON contracts below are what the CISO dashboard and ECU self-service UI consume. GET /api/v1/portfolio returns the exact shape the bundled portfolio.html renders.


Running it

# from an install
CBOM_API_TOKEN=$(openssl rand -hex 24) cbom serve --port 8787 --data-dir /var/lib/pqr

# from the repo
CBOM_API_TOKEN=secret node ./bin/cbom.js serve --port 8787 --data-dir ./.cbom-server

Flags / env:

Flag Env Default Meaning
--port CBOM_API_PORT 8787 listen port
--host 0.0.0.0 bind interface
--data-dir .cbom-server JSON project/scan store (the repository)
--token CBOM_API_TOKEN bearer token clients must present
--no-auth disable auth (local/testing only)

Air-gapped: source/firmware/certificate scans make no outbound calls. Only /probe (reaches your targets) and /vault (reaches your cloud vault) need network — and both are initiated by the server, never inbound.

Auth: when a token is set, every route except GET /health requires Authorization: Bearer <token>. Comparison is constant-time.


Data model

Project ──< Scan (source | firmware | certificate | probe | vault)
             └─ cbom  (CycloneDX 1.6 CBOM)
             └─ qbom  (CycloneDX 1.6 QBOM + readiness)
             └─ summary { score, grade, CRITICAL, HIGH, ... }
             └─ pqcReport { totalVulnerableInstances, ... }

A project groups scans over time; the latest scan per project is its current state, earlier scans drive trends. The store is <data-dir>/projects/*.json + <data-dir>/scans/<projectId>/*.json — swap src/server/store.js for a Postgres-backed module with the same five methods (listProjects, getProject, createProject, addScan, listScans, latestScan) and nothing else changes.


Endpoints

All bodies are JSON unless noted. Errors are { "error": "..." } with a 4xx/5xx status.

GET /health

No auth. { ok, service, version, projects }.

GET /api/v1/projects

{ projects: [{ id, name, compliance, metadata, createdAt }] }.

POST /api/v1/projects

Register (idempotent by name — same name returns the existing project).

{ "name": "ecu-telematics-42", "compliance": ["unece-r155","cnsa-2"], "metadata": {} }

201 { id, name, compliance, metadata, createdAt }. compliance frameworks are auto-evaluated on every subsequent scan of this project.

GET /api/v1/projects/:id

Project + its scan index ({ ...project, scans: [...] }).

POST /api/v1/projects/:id/scan/source

Two ways to supply code:

  • Server-local path (JSON): { "path": "/builds/ecu-42/src" } — the server scans a directory it can already see (mounted build workspace).
  • Tarball upload (binary body, Content-Type: application/x-tar or application/gzip): the server unpacks to a temp dir, scans, discards it.

201 QBOM response (see below).

POST /api/v1/projects/:id/scan/firmware

Raw binary body (ELF/PE/Mach-O/JAR). Optional ?name=ecu-fw.elf. Detects crypto libraries and weak-algorithm symbols baked into the image. → 201 QBOM response.

POST /api/v1/projects/:id/scan/certificate

Raw PEM/DER body. Optional ?name=device.pem. → 201 QBOM response.

POST /api/v1/projects/:id/probe

Live TLS/SSH/LDAP/IPsec probe (server reaches the target):

{ "targets": [ { "host": "api.vehicle.example.com", "port": 443, "type": "tls" } ] }

Single-target shorthand { "host": ..., "port": ..., "type": ... } also works. Requires authorization to scan the target.201 QBOM response.

POST /api/v1/projects/:id/vault

Live metadata-only key-store scan:

{ "provider": "aws", "region": "eu-west-2" }
{ "provider": "azure", "vaultUrl": "https://v.vault.azure.net", "tenantId": "...", "clientId": "...", "clientSecret": "..." }
{ "provider": "hashicorp", "address": "https://vault:8200", "token": "..." }

201 QBOM response.

GET /api/v1/projects/:id/cbom · GET /api/v1/projects/:id/qbom

Latest CycloneDX 1.6 CBOM / QBOM document for the project (404 if never scanned).

GET /api/v1/projects/:id/history

{ scans: [{ id, kind, timestamp, riskScore, riskGrade, findingCount }] }.

GET /api/v1/portfolio

Fleet roll-up across all projects — same shape as portfolio.json (see src/portfolio): { generatedAt, fleet, projects }. This is what the CISO dashboard renders.

QBOM response (returned by every scan endpoint)

{
  "scanId": "scan_...",
  "kind": "source",
  "timestamp": "2026-07-06T...",
  "riskScore": 42, "riskGrade": "D",
  "counts": { "critical": 3, "high": 2, "medium": 1, "low": 0 },
  "quantum": { "shorVulnerable": 5, "groverWeakened": 1, "pqcSafe": 0 },
  "exploitability": {
    "actNow": 17, "scored": 25,
    "top": [ { "priority": 84, "band": "CRITICAL", "asset": "RSA-1024",
               "file": "...", "reasons": ["...", "..."], "factors": {...} } ]
  },
  "cbom": { /* full CycloneDX 1.6 CBOM */ },
  "qbom": { /* full CycloneDX 1.6 QBOM */ }
}

exploitability is the "which findings matter" ranking — actNow is the count of Critical+High-exploitability findings (reachable, exposed, or protecting long-lived data), and top is the ranked shortlist with a human-readable reasons array per finding. Frontends should surface actNow as the headline number and top as the remediation queue, not the raw counts.


ECU self-service flow (the 100–200 ECU story)

TOKEN=... ; API=http://pqr.internal:8787

# 1. register once (idempotent — safe to call every build)
PID=$(curl -s -X POST $API/api/v1/projects -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' -d '{"name":"ecu-telematics-42","compliance":["unece-r155"]}' \
  | node -pe 'JSON.parse(require("fs").readFileSync(0)).id')

# 2. request a QBOM for this ECU's firmware
curl -s -X POST "$API/api/v1/projects/$PID/scan/firmware?name=app.elf" \
  -H "authorization: Bearer $TOKEN" --data-binary @build/app.elf | jq '.quantum'

# 3. CISO dashboard reads the fleet
curl -s $API/api/v1/portfolio -H "authorization: Bearer $TOKEN" > portfolio.json

A 150-ECU simulation is just this loop over 150 names — each gets its own project and QBOM, and the portfolio aggregates them.


Embedding the engine directly (no HTTP)

The npm main is src/engine/index.js. Everything the server does is available programmatically:

const engine = require('precogs-cbom/src/engine');
const cyclonedx = require('precogs-cbom/src/output/cyclonedx');
const qbom = require('precogs-cbom/src/output/qbom');

const result = await engine.runScan('./project', { secretsDetect: true, pqcCheck: true, compliance: 'unece-r155' });
const cbomDoc = cyclonedx.generate(result);
const qbomDoc = qbom.generate(result);

Vault connectors, the policy engine, the portfolio aggregator, the Armis integration, and the advisor are all plain modules under src/ — see docs/INTEGRATIONS.md.


Deployment notes for backend devs

  • Persistence: the JSON store is fine for a PoC and for tens of thousands of scans. For production concurrency, implement src/server/store.js against Postgres (same method surface) — the BOMs are already JSON columns.
  • Scale: scans are CPU-bound and synchronous per request; front with a process manager (pm2/systemd) and N workers, or put a queue in front of /scan/* for large firmware.
  • TLS: terminate TLS at your ingress/load balancer; the server speaks plain HTTP by design (keeps it dependency-free and air-gap friendly).
  • Body size: default cap is 200 MB (firmware). Adjust MAX_BODY in src/server/index.js if needed.