# Console Workspaces API _Tracking: CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, DOCS-AIAI-31-004_ ## 1. Goals & Scope The console workspaces provide read-only aggregates for Advisory AI operators: - `/console/vuln/*` surfaces tenant-scoped findings annotated with policy verdicts, VEX justifications, Scheduler reachability signals, and Advisory AI rationale. - `/console/vex/*` streams the underlying VEX statements, conflicts, and justification summaries (with SSE support for live updates). All endpoints MUST: 1. Remain deterministic offline (stable sort keys, ISO-8601 UTC timestamps, hashed assets). 2. Operate with Authority-issued DPoP or mTLS client credentials that include `console:read` and either `vuln:read` or `vex:read`. 3. Respect tenant isolation – every request carries `X-StellaOps-Tenant`. ## 2. Shared Request/Response Conventions | Requirement | Description | | --- | --- | | Headers | `Authorization: DPoP `, `DPoP: `, `X-StellaOps-Tenant: `, `Accept: application/json` (or `text/event-stream` for SSE). | | Pagination | Cursor-based via `pageToken`; defaults to 50 items, max 200. Cursors are opaque, base64url, signed. | | Sorting | Findings sorted by `(severity desc, exploitScore desc, findingId asc)`. Statements sorted by `(lastUpdated desc, statementId asc)`. | | Dates | RFC 3339 / ISO-8601 UTC (e.g., `2025-11-08T12:02:11Z`). | | Determinism | All arrays must be pre-sorted; no server-generated uuids in responses. | ## 3. Vulnerability Workspace (`/console/vuln/*`) ### 3.1 `GET /console/vuln/findings` Query parameters: | Parameter | Type | Notes | | --- | --- | --- | | `pageToken` | string | Optional cursor from previous response. | | `pageSize` | int | 1-200, default 50. | | `severity` | string[] | Accepts `critical`, `high`, `medium`, `low`, `info`. | | `product` | string[] | SBOM `purl` or image digest anchors. | | `policyBadge` | string[] | `pass`, `warn`, `fail`, `waived`. | | `vexState` | string[] | `not_affected`, `fixed`, `under_investigation`, etc. | | `reachability` | string[] | `reachable`, `unreachable`, `unknown`. | | `search` | string | Substring match on CVE/GHSA/KEV ID (case-insensitive). | Response body: ```jsonc { "items": [ { "findingId": "tenant-default:advisory-ai:sha256:5d1a", "coordinates": { "advisoryId": "CVE-2024-12345", "package": "pkg:npm/jsonwebtoken@9.0.2", "component": "jwt-auth-service", "image": "registry.local/ops/auth:2025.10.0" }, "summary": "jsonwebtoken <10.0.0 allows algorithm downgrade.", "severity": "high", "cvss": 8.1, "kev": true, "policyBadge": "fail", "vex": { "statementId": "vex:tenant-default:jwt-auth:5d1a", "state": "under_investigation", "justification": "Advisory AI flagged reachable path via Scheduler run 42." }, "reachability": { "status": "reachable", "lastObserved": "2025-11-07T23:11:04Z", "signalsVersion": "signals-2025.310.1" }, "evidence": { "sbomDigest": "sha256:6c81…", "policyRunId": "policy-run::2025-11-07::ca9f", "attestationId": "dsse://authority/attest/84a2" }, "timestamps": { "firstSeen": "2025-10-31T04:22:18Z", "lastSeen": "2025-11-07T23:16:51Z" } } ], "facets": { "severity": [ { "value": "critical", "count": 2 }, { "value": "high", "count": 7 } ], "policyBadge": [ { "value": "fail", "count": 6 }, { "value": "warn", "count": 3 }, { "value": "waived", "count": 1 } ], "reachability": [ { "value": "reachable", "count": 5 }, { "value": "unreachable", "count": 2 }, { "value": "unknown", "count": 1 } ] }, "nextPageToken": "eyJjdXJzb3IiOiJmZjg0NiJ9" } ``` ### 3.2 `GET /console/vuln/facets` Returns the full facet catalog (counts by severity, product, policy badge, VEX state, reachability, KEV flag). Designed for sidebar filters without paging; identical parameter surface as `/findings`. ### 3.3 `GET /console/vuln/{findingId}` Returns the full finding document, including evidence timeline, policy overlays, and export-ready metadata: ```jsonc { "findingId": "tenant-default:advisory-ai:sha256:5d1a", "details": { "description": "jsonwebtoken <10.0.0 allows algorithm downgrade.", "references": [ "https://nvd.nist.gov/vuln/detail/CVE-2024-12345", "https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-45mw-4jw3-g2wg" ], "exploitAvailability": "known_exploit" }, "policyBadges": [ { "policyId": "policy://tenant-default/runtime-hardening", "verdict": "fail", "explainUrl": "https://console.local/policy/runs/policy-run::2025-11-07::ca9f" } ], "vex": { "statementId": "vex:tenant-default:jwt-auth:5d1a", "state": "under_investigation", "justification": "Runtime telemetry confirmed exploitation path.", "impactStatement": "Token exchange service remains exposed until patch 2025.11.2.", "remediations": [ { "type": "patch", "description": "Upgrade jwt-auth-service to 2025.11.2.", "deadline": "2025-11-12T00:00:00Z" } ] }, "reachability": { "status": "reachable", "callPathSamples": [ "api-gateway -> jwt-auth-service -> jsonwebtoken.verify" ], "lastUpdated": "2025-11-07T23:11:04Z" }, "evidence": { "sbom": { "digest": "sha256:6c81…", "componentPath": [ "/src/jwt-auth/package.json", "/src/jwt-auth/node_modules/jsonwebtoken" ] }, "attestations": [ { "type": "scan-report", "attestationId": "dsse://authority/attest/84a2", "signer": "attestor@stella-ops.org", "bundleDigest": "sha256:e2bb…" } ] }, "timestamps": { "firstSeen": "2025-10-31T04:22:18Z", "lastSeen": "2025-11-07T23:16:51Z", "vexLastUpdated": "2025-11-07T23:10:09Z" } } ``` ### 3.4 `POST /console/vuln/tickets` ```jsonc POST /console/vuln/tickets { "tenant": "tenant-default", "selection": [ "tenant-default:advisory-ai:sha256:5d1a", "tenant-default:advisory-ai:sha256:9bf4" ], "targetSystem": "servicenow", "metadata": { "assignmentGroup": "runtime-security", "priority": "P1" } } ``` Response: ```jsonc { "ticketId": "console-ticket::tenant-default::2025-11-08::00018", "payload": { "version": "2025-11-01", "tenant": "tenant-default", "findings": [ { "findingId": "tenant-default:advisory-ai:sha256:5d1a", "severity": "high" }, { "findingId": "tenant-default:advisory-ai:sha256:9bf4", "severity": "critical" } ], "policyBadge": "fail", "vexSummary": "2 reachable findings pending patch.", "attachments": [ { "type": "json", "name": "console-ticket-20251108.json", "digest": "sha256:1fdd…", "contentType": "application/json", "expiresAt": "2025-11-15T00:00:00Z" } ] }, "auditEventId": "console.ticket.export::2025-11-08::00018" } ``` Requests emit `console.ticket.export` audit events (tenant, user, selection counts, target system). ## 4. VEX Workspace (`/console/vex/*`) ### 4.1 `GET /console/vex/statements` Parameters mirror `/console/vuln/findings` plus: | Parameter | Type | Notes | | --- | --- | --- | | `advisoryId` | string[] | CVE/GHSA/OVAL identifiers. | | `justification` | string[] | `exploit_observed`, `component_not_present`, etc. | | `statementType` | string[] | `vex`, `openvex`, `custom`, `advisory_ai`. | | `prefer` | string | `prefer=stream` enables chunked streaming (NDJSON). | Response (paged JSON): ```jsonc { "items": [ { "statementId": "vex:tenant-default:jwt-auth:5d1a", "advisoryId": "CVE-2024-12345", "product": "registry.local/ops/auth:2025.10.0", "status": "under_investigation", "justification": "exploit_observed", "lastUpdated": "2025-11-07T23:10:09Z", "source": { "type": "advisory_ai", "modelBuild": "aiai-console-2025-10-28", "confidence": 0.74 }, "links": [ { "rel": "finding", "href": "/console/vuln/findings/tenant-default:advisory-ai:sha256:5d1a" } ] } ], "nextPageToken": null } ``` When `Accept: text/event-stream`, the endpoint emits events (see §4.3) instead of paged JSON. ### 4.2 `GET /console/vex/statements/{statementId}` Returns the canonical statement plus provenance extracts. SSE clients can call this endpoint when they need full bodies after receiving a summary event. ### 4.3 `GET /console/vex/events` (SSE) Streams live updates for VEX statements affecting the tenant: - Event types: `statement.created`, `statement.updated`, `statement.deleted`, `statement.conflict`. - Fields: `id`, `advisoryId`, `product`, `vexState`, `severityHint`, `policyBadge`, `conflictSummary`, `sequence`. - Replay: Clients include `Last-Event-ID`; server resumes from sequence. - Heartbeats every 15 seconds (`event: keepalive`, `data: {}`). Example event payload: ```jsonc event: statement.updated data: { "statementId": "vex:tenant-default:jwt-auth:5d1a", "advisoryId": "CVE-2024-12345", "product": "registry.local/ops/auth:2025.10.0", "state": "fixed", "justification": "solution_available", "sequence": 4182, "updatedAt": "2025-11-08T11:44:32Z" } ``` ## 5. Signals & Scheduler Integration - Reachability data is materialized by Scheduler delta jobs (`SCHED-CONSOLE-23-001`). `/console/vuln/findings` should cache the most recent job ID and expose `signalsVersion`. - VEX justification fields reference Excititor statement IDs; ensure the gateway checks Excititor availability and degrades gracefully (returns `state: unavailable` plus telemetry). - Scheduler must publish `console.vuln.refresh` events whenever advisory/VEX deltas warrant workspace refresh; console SSE endpoint may piggyback on the same Redis/NATS channel. ## 6. Determinism & Offline Notes 1. All responses are compressible JSON; no CDN fonts/assets referenced. 2. SSE endpoints must tolerate sealed mode by operating on loopback addresses only. 3. `authority-sealed-ci.json` (see DEVOPS-AIRGAP-57-002) is the evidence Authority consumes before enabling these APIs for sealed tenants; configure `airGap.sealedMode` and set `requiresAirgapSealConfirmation: true` on the Console client so `/token` refuses requests until the sealed harness uploads a fresh, passing artefact. Console responses echo `sealed: true/false` flags for UI badges once Authority has confirmed the evidence. ## 7. Sample Payloads for Docs - `docs/api/console/samples/vuln-findings-sample.json` – exported via `scripts/generate-console-samples.ts` (placeholder script to be added when backend lands). - `docs/api/console/samples/vex-statement-sse.ndjson` – contains 5 chronological SSE events for screenshot reproduction. > Until backend implementations ship, use the examples above to unblock DOCS-AIAI-31-004; replace them with live captures once the gateway endpoints are available in staging. ## Exports (draft contract v0.3) ### Routes - `POST /console/exports` — start an evidence bundle export job. - `GET /console/exports/{exportId}` — fetch job status and download locations. - `GET /console/exports/{exportId}/events` — SSE stream of job progress (optional). ### Security / headers - `Authorization: DPoP ` - `DPoP: ` - `X-StellaOps-Tenant: ` - `Idempotency-Key: ` (recommended for POST) - `Accept: application/json` (status) or `text/event-stream` (events) - Required scopes: `console:read` AND `console:export` (proposal). ### Request body (POST) ```jsonc { "scope": { "tenantId": "t1", "projectId": "p1" }, "sources": [ { "type": "advisory", "ids": ["CVE-2024-12345"] } ], "formats": ["json", "ndjson", "csv"], "attestations": { "include": true, "sigstoreBundle": true }, "notify": { "webhooks": ["https://hooks.local/export"], "email": ["secops@example.com"] }, "priority": "normal" } ``` ### Response: 202 Accepted - `exportId`: string - `status`: `queued|running|succeeded|failed|expired` - `estimateSeconds`: int - `retryAfter`: int seconds (for polling) - `links`: `{ status: url, events?: url }` ### Response: GET status ```jsonc { "exportId": "console-export::tenant-default::2025-12-06::0007", "status": "running", "estimateSeconds": 420, "outputs": [ { "type": "manifest", "format": "json", "url": "https://.../manifest.json?sig=...", "sha256": "...", "expiresAt": "2025-12-06T13:10:00Z" } ], "progress": { "percent": 42, "itemsCompleted": 210, "itemsTotal": 500, "assetsReady": 12 }, "errors": [] } ``` ### Response: SSE events - `started`: `{ exportId, status }` - `progress`: `{ exportId, percent, itemsCompleted, itemsTotal }` - `asset_ready`: `{ exportId, type, id, url, sha256 }` - `completed`: `{ exportId, status: "succeeded", manifestUrl }` - `failed`: `{ exportId, status: "failed", code, message }` ### Manifest shape (downloaded via outputs) - `version`: string (date) - `exportId`, `tenantId`, `generatedAt` - `items[]`: `{ type: advisory|vex|policy|scan, id, url, sha256 }` - `checksums`: `{ manifest, bundle }` ### Limits (proposed) - Max request body 256 KiB; max sources 50; max outputs 1000 assets/export. - Default job timeout 30 minutes; idle SSE timeout 60s; backoff via `Retry-After`. ### Error codes (proposal) - `ERR_CONSOLE_EXPORT_INVALID_SOURCE` - `ERR_CONSOLE_EXPORT_TOO_LARGE` - `ERR_CONSOLE_EXPORT_RATE_LIMIT` - `ERR_CONSOLE_EXPORT_UNAVAILABLE` ### Samples - Request: `docs/api/console/samples/console-export-request.json` - Status: `docs/api/console/samples/console-export-status.json` - Manifest: `docs/api/console/samples/console-export-manifest.json` - Events: `docs/api/console/samples/console-export-events.ndjson` ### Open items (needs guild sign-off) - Final scopes list (`console:export` vs broader `console:*`). - Final limits and error codes; checksum manifest format; attestation options. - Caching/tie-break rules for downstream `/console/search` and `/console/downloads`.