API & CLI Reference
Purpose – give operators and integrators a single, authoritative spec for REST/GRPC calls and first‑party CLI tools (stella-cli, zastava, stella).
Everything here is source‑of‑truth for generated Swagger/OpenAPI and the --help screens in the CLIs.
0 Quick Glance
| Area | Call / Flag | Notes |
|---|---|---|
| Scan entry | POST /scan | Accepts SBOM or image; sub‑5 s target |
| Delta check | POST /layers/missing | <20 ms reply; powers delta SBOM feature |
| Rate‑limit / quota | — | Headers X‑Stella‑Quota‑Remaining, X‑Stella‑Reset on every response |
| Policy I/O | GET /policy/export, POST /policy/import | YAML now; Rego coming |
| Policy lint | POST /policy/validate | Returns 200 OK if ruleset passes |
| Auth | POST /connect/token (OpenIddict) | Client‑credentials preferred |
| Health | GET /healthz | Simple liveness probe |
| Attestation * | POST /attest (TODO Q1‑2026) | SLSA provenance + Rekor log |
| CLI flags | --sbom-type --delta --policy-file | Added to stella |
* Marked TODO → delivered after sixth month (kept on Feature Matrix “To Do” list).
1 Authentication
Stella Ops uses OAuth 2.0 / OIDC (token endpoint mounted via OpenIddict).
POST /connect/token
Content‑Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=ci‑bot&
client_secret=REDACTED&
scope=stella.api
Successful response:
{
"access_token": "eyJraWQi...",
"token_type": "Bearer",
"expires_in": 3600
}
Tip – pass the token via
Authorization: Bearer <token>on every call.
2 REST API
2.0 Obtain / Refresh Offline‑Token
POST /token/offline
Authorization: Bearer <admin‑token>
| Body field | Required | Example | Notes |
|---|---|---|---|
expiresDays | no | 30 | Max 90 days |
{
"jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6...",
"expires": "2025‑08‑17T00:00:00Z"
}
Token is signed with the backend’s private key and already contains "maxScansPerDay": {{ quota_token }}.
2.1 Scan – Upload SBOM or Image
POST /scan
| Param / Header | In | Required | Description |
|---|---|---|---|
X‑Stella‑Sbom‑Type | header | no | trivy-json-v2, spdx-json, cyclonedx-json; omitted ➞ auto‑detect |
?threshold | query | no | low, medium, high, critical; default critical |
| body | body | yes | Either SBOM JSON or Docker image tarball/upload URL |
Every successful /scan response now includes:
| Header | Example |
|---|---|
X‑Stella‑Quota‑Remaining | 129 |
X‑Stella‑Reset | 2025‑07‑18T23:59:59Z |
X‑Stella‑Token‑Expires | 2025‑08‑17T00:00:00Z |
Response 200 (scan completed):
{
"digest": "sha256:…",
"summary": {
"Critical": 0,
"High": 3,
"Medium": 12,
"Low": 41
},
"policyStatus": "pass",
"quota": {
"remaining": 131,
"reset": "2025-07-18T00:00:00Z"
}
}
Response 202 – queued; polling URL in Location header.
2.2 Delta SBOM – Layer Cache Check
POST /layers/missing
Content‑Type: application/json
Authorization: Bearer <token>
{
"layers": [
"sha256:d38b...",
"sha256:af45..."
]
}
Response 200 — <20 ms target:
{
"missing": [
"sha256:af45..."
]
}
Client then generates SBOM only for the missing layers and re‑posts /scan.
2.3 Policy Endpoints (preview feature flag: scanner.features.enablePolicyPreview)
All policy APIs require scanner.reports scope (or anonymous access while auth is disabled).
Fetch schema
GET /api/v1/policy/schema
Authorization: Bearer <token>
Accept: application/schema+json
Returns the embedded policy-schema@1 JSON schema used by the binder.
Run diagnostics
POST /api/v1/policy/diagnostics
Content-Type: application/json
Authorization: Bearer <token>
{
"policy": {
"format": "yaml",
"actor": "cli",
"description": "dev override",
"content": "version: \"1.0\"\nrules:\n - name: Quiet Dev\n environments: [dev]\n action:\n type: ignore\n justification: dev waiver\n"
}
}
Response 200:
{
"success": false,
"version": "1.0",
"ruleCount": 1,
"errorCount": 0,
"warningCount": 1,
"generatedAt": "2025-10-19T03:25:14.112Z",
"issues": [
{ "code": "policy.rule.quiet.missing_vex", "message": "Quiet flag ignored: rule must specify requireVex justifications.", "severity": "Warning", "path": "$.rules[0]" }
],
"recommendations": [
"Review policy warnings and ensure intentional overrides are documented."
]
}
success is false when blocking issues remain; recommendations aggregate YAML ignore rules, VEX include/exclude hints, and vendor precedence guidance.
Preview impact
POST /api/v1/policy/preview
Authorization: Bearer <token>
Content-Type: application/json
{
"imageDigest": "sha256:abc123",
"findings": [
{ "id": "finding-1", "severity": "Critical", "source": "NVD" }
],
"policy": {
"format": "yaml",
"content": "version: \"1.0\"\nrules:\n - name: Block Critical\n severity: [Critical]\n action: block\n"
}
}
Response 200:
{
"success": true,
"policyDigest": "9c5e...",
"revisionId": "preview",
"changed": 1,
"diffs": [
{
"findingId": "finding-1",
"baseline": {"findingId": "finding-1", "status": "Pass"},
"projected": {
"findingId": "finding-1",
"status": "Blocked",
"ruleName": "Block Critical",
"ruleAction": "Block",
"score": 5.0,
"configVersion": "1.0",
"inputs": {"severityWeight": 5.0}
},
"changed": true
}
],
"issues": []
}
- Provide
policyto preview staged changes; omit it to compare against the active snapshot. - Baseline verdicts are optional; when omitted, the API synthesises pass baselines before computing diffs.
- Quieted verdicts include
quietedByandquietflags; score inputs now surface reachability/vendor trust weights (reachability.*,trustWeight.*).
OpenAPI: the full API document (including these endpoints) is exposed at /openapi/v1.json and can be fetched for tooling or contract regeneration.
2.4 Scanner – Queue a Scan Job (SP9 milestone)
POST /api/v1/scans
Authorization: Bearer <token with scanner.scans.enqueue>
Content-Type: application/json
{
"image": {
"reference": "registry.example.com/acme/app:1.2.3"
},
"force": false,
"clientRequestId": "ci-build-1845",
"metadata": {
"pipeline": "github",
"trigger": "pull-request"
}
}
| Field | Required | Notes |
|---|---|---|
image.reference | no* | Full repo/tag (registry/repo:tag). Provide either reference or digest (sha256:…). |
image.digest | no* | OCI digest (e.g. sha256:…). |
force | no | true forces a re-run even if an identical scan (scanId) already exists. Default false. |
clientRequestId | no | Free-form string surfaced in audit logs. |
metadata | no | Optional string map stored with the job and surfaced in observability feeds. |
* At least one of image.reference or image.digest must be supplied.
Response 202 – job accepted (idempotent):
HTTP/1.1 202 Accepted
Location: /api/v1/scans/2f6c17f9b3f548e2a28b9c412f4d63f8
{
"scanId": "2f6c17f9b3f548e2a28b9c412f4d63f8",
"status": "Pending",
"location": "/api/v1/scans/2f6c17f9b3f548e2a28b9c412f4d63f8",
"created": true
}
scanIdis deterministic – resubmitting an identical payload returns the same identifier with"created": false.- API is cancellation-aware; aborting the HTTP request cancels the submission attempt.
- Required scope:
scanner.scans.enqueue.
Response 400 – validation problem (Content-Type: application/problem+json) when both image.reference and image.digest are blank.
2.5 Scanner – Fetch Scan Status
GET /api/v1/scans/{scanId}
Authorization: Bearer <token with scanner.scans.read>
Accept: application/json
Response 200:
{
"scanId": "2f6c17f9b3f548e2a28b9c412f4d63f8",
"status": "Pending",
"image": {
"reference": "registry.example.com/acme/app:1.2.3",
"digest": "sha256:cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe"
},
"createdAt": "2025-10-18T20:15:12.482Z",
"updatedAt": "2025-10-18T20:15:12.482Z",
"failureReason": null,
"surface": {
"tenant": "default",
"generatedAt": "2025-10-18T20:15:12.482Z",
"manifestDigest": "sha256:8b4ddf1a9d3565eb7c2b176a0a64a970795e5ec373dbea3aaebb4208f9759b44",
"manifestUri": "cas://scanner-artifacts/scanner/surface/manifests/default/sha256/8b/4d/8b4ddf1a9d3565eb7c2b176a0a64a970795e5ec373dbea3aaebb4208f9759b44.json",
"manifest": {
"schema": "stellaops.surface.manifest@1",
"tenant": "default",
"imageDigest": "sha256:cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe",
"generatedAt": "2025-10-18T20:15:12.482Z",
"artifacts": [
{
"kind": "sbom-inventory",
"uri": "cas://scanner-artifacts/scanner/images/cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe/sbom.cdx.json",
"digest": "sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
"mediaType": "application/vnd.cyclonedx+json; version=1.6; view=inventory",
"format": "cdx-json",
"sizeBytes": 2048,
"view": "inventory"
}
]
}
}
}
Statuses: Pending, Running, Succeeded, Failed, Cancelled.
2.6 Scanner – Stream Progress (SSE / JSONL)
GET /api/v1/scans/{scanId}/events?format=sse|jsonl
Authorization: Bearer <token with scanner.scans.read>
Accept: text/event-stream
When format is omitted the endpoint emits Server-Sent Events (SSE). Specify format=jsonl to receive newline-delimited JSON (application/x-ndjson). Response headers include Cache-Control: no-store and X-Accel-Buffering: no so intermediaries avoid buffering the stream.
SSE frame (default):
id: 1
event: pending
data: {"scanId":"2f6c17f9b3f548e2a28b9c412f4d63f8","sequence":1,"state":"Pending","message":"queued","timestamp":"2025-10-19T03:12:45.118Z","correlationId":"2f6c17f9b3f548e2a28b9c412f4d63f8:0001","data":{"force":false,"meta.pipeline":"github"}}
JSONL frame (format=jsonl):
{"scanId":"2f6c17f9b3f548e2a28b9c412f4d63f8","sequence":1,"state":"Pending","message":"queued","timestamp":"2025-10-19T03:12:45.118Z","correlationId":"2f6c17f9b3f548e2a28b9c412f4d63f8:0001","data":{"force":false,"meta.pipeline":"github"}}
sequenceis monotonic starting at1.correlationIdis deterministic ({scanId}:{sequence:0000}) unless a custom identifier is supplied by the publisher.timestampis ISO‑8601 UTC with millisecond precision, ensuring deterministic ordering for consumers.- The stream completes when the client disconnects or the coordinator stops publishing events.
2.7 Scanner – Assemble Report (Signed Envelope)
POST /api/v1/reports
Authorization: Bearer <token with scanner.reports>
Content-Type: application/json
Request body mirrors policy preview inputs (image digest plus findings). The service evaluates the active policy snapshot, assembles a verdict, and signs the canonical report payload.
Response 200:
{
"report": {
"reportId": "report-9f8cde21aab54321",
"imageDigest": "sha256:7dbe0c9a5d4f1c8184007e9d94dbe55928f8a2db5ab9c1c2d4a2f7bbcdfe1234",
"generatedAt": "2025-10-23T15:32:22Z",
"verdict": "blocked",
"policy": {
"revisionId": "rev-42",
"digest": "8a0f72f8dc5c51c46991db3bba34e9b3c0c8e944a7a6d0a9c29a9aa6b8439876"
},
"summary": { "total": 2, "blocked": 1, "warned": 1, "ignored": 0, "quieted": 0 },
"verdicts": [
{
"findingId": "library:pkg/openssl@1.1.1w",
"status": "Blocked",
"ruleName": "Block vendor unknowns",
"ruleAction": "block",
"notes": "Unknown vendor telemetry — medium confidence band.",
"score": 19.5,
"configVersion": "1.0",
"inputs": {
"severityWeight": 50,
"trustWeight": 0.65,
"reachabilityWeight": 0.6,
"baseScore": 19.5,
"trustWeight.vendor": 0.65,
"reachability.unknown": 0.6,
"unknownConfidence": 0.55,
"unknownAgeDays": 5
},
"quietedBy": null,
"quiet": false,
"unknownConfidence": 0.55,
"confidenceBand": "medium",
"unknownAgeDays": 5,
"sourceTrust": "vendor",
"reachability": "unknown"
},
{
"findingId": "library:pkg/zlib@1.3.1",
"status": "Warned",
"ruleName": "Runtime mitigation required",
"ruleAction": "warn",
"notes": "Runtime reachable unknown — mitigation window required.",
"score": 18.75,
"configVersion": "1.0",
"inputs": {
"severityWeight": 75,
"trustWeight": 1,
"reachabilityWeight": 0.45,
"baseScore": 33.75,
"reachability.runtime": 0.45,
"warnPenalty": 15,
"unknownConfidence": 0.35,
"unknownAgeDays": 13
},
"quietedBy": null,
"quiet": false,
"unknownConfidence": 0.35,
"confidenceBand": "medium",
"unknownAgeDays": 13,
"sourceTrust": "NVD",
"reachability": "runtime"
}
],
"issues": [],
"surface": {
"tenant": "default",
"generatedAt": "2025-10-23T15:32:22Z",
"manifestDigest": "sha256:1f3c5d7a8e4b921a0c2f6b8de0bb5aa8f5aa51b62e7d7d1864f9c826bfb44d91",
"manifestUri": "cas://scanner-artifacts/scanner/surface/manifests/default/sha256/1f/3c/1f3c5d7a8e4b921a0c2f6b8de0bb5aa8f5aa51b62e7d7d1864f9c826bfb44d91.json",
"manifest": {
"schema": "stellaops.surface.manifest@1",
"tenant": "default",
"imageDigest": "sha256:7dbe0c9a5d4f1c8184007e9d94dbe55928f8a2db5ab9c1c2d4a2f7bbcdfe1234",
"generatedAt": "2025-10-23T15:32:22Z",
"artifacts": [
{
"kind": "sbom-inventory",
"uri": "cas://scanner-artifacts/scanner/images/7dbe0c9a5d4f1c8184007e9d94dbe55928f8a2db5ab9c1c2d4a2f7bbcdfe1234/sbom.cdx.json",
"digest": "sha256:2b8ce7dd0037e59f0f93e4a5cff45b1eb305a511a1c9e2895d2f4ecdf616d3da",
"mediaType": "application/vnd.cyclonedx+json; version=1.6; view=inventory",
"format": "cdx-json",
"sizeBytes": 3072,
"view": "inventory"
},
{
"kind": "sbom-usage",
"uri": "cas://scanner-artifacts/scanner/images/7dbe0c9a5d4f1c8184007e9d94dbe55928f8a2db5ab9c1c2d4a2f7bbcdfe1234/sbom.cdx.pb",
"digest": "sha256:74e4d9f8ab0f2a1772e5768e15a5a9d7b662b849b1f223c8d6f3b184e4ac7780",
"mediaType": "application/vnd.cyclonedx+protobuf; version=1.6; view=usage",
"format": "cdx-protobuf",
"sizeBytes": 12800,
"view": "usage"
}
]
}
}
},
"dsse": {
"payloadType": "application/vnd.stellaops.report+json",
"payload": "eyJyZXBvcnQiOnsicmVwb3J0SWQiOiJyZXBvcnQtOWY4Y2RlMjFhYWI1NDMyMSJ9fQ==",
"signatures": [
{
"keyId": "scanner-report-signing",
"algorithm": "hs256",
"signature": "MEQCIGHscnJ2bm9wYXlsb2FkZXIAIjANBgkqhkiG9w0BAQsFAAOCAQEASmFja3Nvbk1ldGE="
}
]
}
}
- The
reportobject omits null fields and is deterministic (ISO timestamps, sorted keys) while surfacingunknownConfidence,confidenceBand,unknownAgeDays, and asurfaceblock containing the manifest digest and CAS URIs for downstream tooling. dssefollows the DSSE (Dead Simple Signing Envelope) shape;payloadis the canonical UTF-8 JSON andsignatures[0].signatureis the base64 HMAC/Ed25519 value depending on configuration.- Full offline samples live at
samples/policy/policy-report-unknown.json(request + response) andsamples/api/reports/report-sample.dsse.json(envelope fixture) for tooling tests or signature verification.
Response 404 – application/problem+json payload with type https://stellaops.org/problems/not-found when the scan identifier is unknown.
Tip – poll
Locationfrom the submission call untilstatustransitions away fromPending/Running.
# Example import payload (YAML)
version: "1.0"
rules:
- name: Ignore Low dev
severity: [Low, None]
environments: [dev, staging]
action: ignore
Validation errors come back as:
{
"errors": [
{
"path": "$.rules[0].severity",
"msg": "Invalid level 'None'"
}
]
}
# Preview response excerpt
{
"success": true,
"policyDigest": "9c5e...",
"revisionId": "rev-12",
"changed": 1,
"diffs": [
{
"baseline": {"findingId": "finding-1", "status": "pass"},
"projected": {"findingId": "finding-1", "status": "blocked", "ruleName": "Block Critical"},
"changed": true
}
]
}
2.4 Attestation (Planned – Q1‑2026)
POST /attest
| Param | Purpose |
|---|---|
| body (JSON) | SLSA v1.0 provenance doc |
| Signed + stored in local Rekor mirror |
Returns 202 Accepted and Location: /attest/{id} for async verify.
2.8 Runtime – Ingest Observer Events (SCANNER-RUNTIME-12-301)
POST /api/v1/runtime/events
Authorization: Bearer <token with scanner.runtime.ingest>
Content-Type: application/json
| Requirement | Details |
|---|---|
| Auth scope | scanner.runtime.ingest |
| Batch size | ≤ 256 envelopes (scanner.runtime.maxBatchSize, configurable) |
| Payload cap | ≤ 1 MiB serialized JSON (scanner.runtime.maxPayloadBytes) |
| Rate limits | Per-tenant and per-node token buckets (default 200 events/s tenant, 50 events/s node, burst 200) – excess returns 429 with Retry-After. |
| TTL | Runtime events retained 45 days by default (scanner.runtime.eventTtlDays). |
Request body
{
"batchId": "node-a-2025-10-20T15:03:12Z",
"events": [
{
"schemaVersion": "zastava.runtime.event@v1",
"event": {
"eventId": "evt-2f9c02b8",
"when": "2025-10-20T15:03:08Z",
"kind": "ContainerStart",
"tenant": "tenant-alpha",
"node": "cluster-a/node-01",
"runtime": { "engine": "containerd", "version": "1.7.19" },
"workload": {
"platform": "kubernetes",
"namespace": "payments",
"pod": "api-7c9fbbd8b7-ktd84",
"container": "api",
"containerId": "containerd://bead5...",
"imageRef": "ghcr.io/acme/api@sha256:deadbeef"
},
"process": { "pid": 12345, "entrypoint": ["/start.sh", "--serve"], "buildId": "5f0c7c3c..." },
"loadedLibs": [
{ "path": "/lib/x86_64-linux-gnu/libssl.so.3", "inode": 123456, "sha256": "abc123..." }
],
"posture": { "imageSigned": true, "sbomReferrer": "present" },
"delta": { "baselineImageDigest": "sha256:deadbeef" },
"evidence": [ { "signal": "proc.maps", "value": "libssl.so.3@0x7f..." } ],
"annotations": { "observerVersion": "1.0.0" }
}
}
]
}
Responses
| Code | Body | Notes |
|---|---|---|
202 Accepted | { "accepted": 128, "duplicates": 2 } | Batch persisted; duplicates are ignored via unique eventId. |
400 Bad Request | Problem+JSON | Validation failures – empty batch, duplicate IDs, unsupported schema version, payload too large. |
429 Too Many Requests | Problem+JSON | Per-tenant/node rate limit exceeded; Retry-After header emitted in seconds. |
Persisted documents capture the canonical envelope (payload field), tenant/node metadata, and set an automatic TTL on expiresAt. Observers should retry rejected batches with exponential backoff honouring the provided Retry-After hint.
3 StellaOps CLI (stellaops-cli)
The new CLI is built on System.CommandLine 2.0.0‑beta5 and mirrors the Concelier backend REST API.
Configuration follows the same precedence chain everywhere:
- Environment variables (e.g.
API_KEY,STELLAOPS_BACKEND_URL,StellaOps:ApiKey) appsettings.json→appsettings.local.jsonappsettings.yaml→appsettings.local.yaml- Defaults (
ApiKey = "",BackendUrl = "", cache folders under the current working directory)
Authority auth client resilience settings
| Setting | Environment variable | Default | Purpose |
|---|---|---|---|
StellaOps:Authority:Resilience:EnableRetries | STELLAOPS_AUTHORITY_ENABLE_RETRIES | true | Toggle Polly wait-and-retry handlers for discovery/token calls |
StellaOps:Authority:Resilience:RetryDelays | STELLAOPS_AUTHORITY_RETRY_DELAYS | 1s,2s,5s | Comma/space-separated backoff sequence (HH:MM:SS) |
StellaOps:Authority:Resilience:AllowOfflineCacheFallback | STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK | true | Reuse cached discovery/JWKS metadata when Authority is temporarily unreachable |
StellaOps:Authority:Resilience:OfflineCacheTolerance | STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE | 00:10:00 | Additional tolerance window added to the discovery/JWKS cache lifetime |
See docs/dev/32_AUTH_CLIENT_GUIDE.md for recommended profiles (online vs. air-gapped) and testing guidance.
| Command | Purpose | Key Flags / Arguments | Notes |
|---|---|---|---|
stellaops-cli scanner download | Fetch and install scanner container | --channel <stable|beta|nightly> (default stable)--output <path>--overwrite--no-install | Saves artefact under ScannerCacheDirectory, verifies digest/signature, and executes docker load unless --no-install is supplied. |
stellaops-cli scan run | Execute scanner container against a directory (auto-upload) | --target <directory> (required)--runner <docker|dotnet|self> (default from config)--entry <image-or-entrypoint>[scanner-args...] | Runs the scanner, writes results into ResultsDirectory, emits a structured scan-run-*.json metadata file, and automatically uploads the artefact when the exit code is 0. |
stellaops-cli scan upload | Re-upload existing scan artefact | --file <path> | Useful for retries when automatic upload fails or when operating offline. |
stellaops-cli ruby inspect | Offline Ruby workspace inspection (Gemfile / lock + runtime signals) | --root <directory> (default current directory)--format <table|json> (default table) | Runs the bundled RubyLanguageAnalyzer, renders Observation summary (bundler/runtime/capabilities) plus Package/Version/Group/Source/Lockfile/Runtime columns, or emits JSON { packages: [...], observation: {...} }. Exit codes: 0 success, 64 invalid format, 70 unexpected failure, 71 missing directory. |
stellaops-cli ruby resolve | Fetch Ruby package inventory for a completed scan | --image <registry-ref> or --scan-id <id> (one required)--format <table|json> (default table) | Calls GetRubyPackagesAsync (GET /api/scans/{scanId}/ruby-packages) to download the canonical RubyPackageInventory. Table output mirrors inspect with groups/platform/runtime usage; JSON now returns { scanId, imageDigest, generatedAt, groups: [...] }. Exit codes: 0 success, 64 invalid args, 70 backend failure, 0 with warning when inventory hasn’t been persisted yet. |
stellaops-cli db fetch | Trigger connector jobs | --source <id> (e.g. redhat, osv)--stage <fetch|parse|map> (default fetch)`–mode <resume | init |
stellaops-cli db merge | Run canonical merge reconcile | — | Calls POST /jobs/merge:reconcile; exit code 0 on acceptance, 1 on failures/conflicts |
stellaops-cli db export | Kick JSON / Trivy exports | --format <json|trivy-db> (default json)--delta--publish-full/--publish-delta--bundle-full/--bundle-delta | Sets { delta = true } parameter when requested and can override ORAS/bundle toggles per run |
stellaops-cli auth <login|logout|status|whoami> | Manage cached tokens for StellaOps Authority | auth login --force (ignore cache)auth statusauth whoami | Uses StellaOps.Auth.Client; honours StellaOps:Authority:* configuration, stores tokens under ~/.stellaops/tokens by default, and whoami prints subject/scope/expiry |
stellaops-cli auth revoke export | Export the Authority revocation bundle | --output <directory> (defaults to CWD) | Writes revocation-bundle.json, .json.jws, and .json.sha256; verifies the digest locally and includes key metadata in the log summary. |
stellaops-cli auth revoke verify | Validate a revocation bundle offline | --bundle <path> --signature <path> --key <path>--verbose | Verifies detached JWS signatures, reports the computed SHA-256, and can fall back to cached JWKS when --key is omitted. |
stellaops-cli offline kit pull | Download the latest offline kit bundle and manifest | --bundle-id <id> (optional)--destination <dir>--overwrite--no-resume | Streams the bundle + manifest from the configured mirror/backend, resumes interrupted downloads, verifies SHA-256, and writes signatures plus a .metadata.json manifest alongside the artefacts. |
Ruby dependency verbs (stellaops-cli ruby …)
ruby inspect runs the same deterministic RubyLanguageAnalyzer bundled with Scanner.Worker against the local working tree—no backend calls—so operators can sanity-check Gemfile / Gemfile.lock pairs before shipping. The command now renders an observation banner (bundler version, package/runtime counts, capability flags, scheduler names) before the package table so air-gapped users can prove what evidence was collected. ruby resolve reuses the persisted RubyPackageInventory (stored under Mongo ruby.packages and exposed via GET /api/scans/{scanId}/ruby-packages) so operators can reason about groups/platforms/runtime usage after Scanner or Offline Kits finish processing; the CLI surfaces scanId, imageDigest, and generatedAt metadata in JSON mode for downstream scripting.
ruby inspect flags
| Flag | Default | Description |
|---|---|---|
--root <dir> | current working directory | Directory containing Gemfile, Gemfile.lock, and runtime sources. Missing paths set exit code 71. |
--format <table|json> | table | table renders Observation summary + Package/Version/Groups/Platform/Source/Lockfile/Runtime columns; json emits { "packages": [...], "observation": {...} } with the analyzer metadata. |
--verbose / -v | false | Surfaces analyzer trace logging while keeping deterministic output. |
Successful runs exit 0; invalid formats raise 64, unexpected failures return 70. Table output marks runtime usage with [green]Entrypoint[/] and includes every runtime entrypoint path when available. JSON mode mirrors analyzer metadata:
{
"packages": [
{
"name": "rack",
"version": "3.1.0",
"source": "https://rubygems.org/",
"lockfile": "Gemfile.lock",
"groups": ["default"],
"platform": "-",
"runtimeEntrypoints": ["app.rb"],
"runtimeFiles": ["app.rb"],
"runtimeReasons": ["require-static"],
"usedByEntrypoint": true
}
],
"observation": {
"bundlerVersion": "2.5.4",
"packageCount": 2,
"runtimeEdgeCount": 1,
"usesExec": true,
"usesNetwork": true,
"usesSerialization": true,
"schedulerCount": 1,
"schedulers": ["sidekiq"]
}
}
ruby resolve flags
| Flag | Default | Description |
|---|---|---|
--image <registry/ref> | — | Scanner artifact identifier (image digest/tag). Mutually exclusive with --scan-id; one is required. |
--scan-id <id> | — | Explicit scan identifier returned by scan run. |
--format <table|json> | table | json writes the full { "scanId": "…", "imageDigest": "…", "generatedAt": "…", "groups": [{ "group": "default", "platform": "-", "packages": [...] }] } payload for downstream automation. |
--verbose / -v | false | Enables HTTP + resolver logging. |
Errors caused by missing identifiers return 64; transient backend errors surface as 70 (with full context in logs). Table output groups packages by Gem/Bundle group + platform and shows runtime entrypoints or [grey]-[/] when unused. JSON payloads stay stable for downstream automation:
{
"scanId": "scan-ruby",
"imageDigest": "sha256:4f7d...",
"generatedAt": "2025-11-12T16:05:00Z",
"groups": [
{
"group": "default",
"platform": "-",
"packages": [
{
"name": "rack",
"lockfile": "Gemfile.lock",
"groups": ["default"],
"runtimeUsed": true,
"runtimeEntrypoints": ["app.rb"]
}
]
}
]
}
Both commands honour CLI observability hooks: Spectre tables for human output, --format json for automation, metrics reported via CliMetrics.RecordRubyInspect/Resolve, and Activity tags (cli.ruby.inspect, cli.ruby.resolve) for trace correlation. | stellaops-cli offline kit import | Upload an offline kit bundle to the backend | <bundle.tgz> (argument)--manifest <path>--bundle-signature <path>--manifest-signature <path> | Validates digests when metadata is present, then posts multipart payloads to POST /api/offline-kit/import; logs the submitted import ID/status for air-gapped rollout tracking. | | stellaops-cli offline kit status | Display imported offline kit details | --json | Shows bundle id/kind, captured/imported timestamps, digests, and component versions; --json emits machine-readable output for scripting. | | stellaops-cli sources ingest --dry-run | Dry-run guard validation for individual payloads | --source <id>--input <path\|uri>--tenant <id>--format table\|json--output <file> | Normalises gzip/base64 payloads, invokes api/aoc/ingest/dry-run, and maps guard failures to deterministic ERR_AOC_00x exit codes. | | stellaops-cli aoc verify | Replay AOC guardrails over stored documents | --since <ISO8601\|duration>--limit <count>--sources <list>--codes <ERR_AOC_00x,...>--format table\|json--export <file> | Summarises checked counts/violations, supports JSON evidence exports, and returns 0, 11…17, 18, 70, or 71 depending on guard outcomes. | | stellaops-cli config show | Display resolved configuration | — | Masks secret values; helpful for air‑gapped installs | | stellaops-cli runtime policy test | Ask Scanner.WebService for runtime verdicts (Webhook parity) | --image/-i <digest> (repeatable, comma/space lists supported)--file/-f <path>--namespace/--ns <name>--label/-l key=value (repeatable)--json | Posts to POST /api/v1/scanner/policy/runtime, deduplicates image digests, and prints TTL/policy revision plus per-image columns for signed state, SBOM referrers, quieted-by metadata, confidence, Rekor attestation (uuid + verified flag), and recently observed build IDs (shortened for readability). Accepts newline/whitespace-delimited stdin when piped; --json emits the raw response without additional logging. |
Need to debug how the scanner resolves entry points? See the entry-point documentation index, which links to static/dynamic reducers, ShellFlow, and runtime-specific guides.
Example: Pivot from runtime verdicts to debug symbols
$ stellaops-cli runtime policy test \
--image ghcr.io/acme/payments@sha256:4f7d55f6... \
--namespace payments
Image Digest Signed SBOM Build IDs TTL
ghcr.io/acme/payments@sha256:4f7d55f6... yes present 5f0c7c3c..., 1122aabbccddeeff... 04:59:55
- Copy one of the hashes (e.g.
5f0c7c3cb4d9f8a4f1c1d5c6b7e8f90123456789) and locate the bundled debug artefact:ls offline-kit/debug/.build-id/5f/0c7c3cb4d9f8a4f1c1d5c6b7e8f90123456789.debug - Confirm the running binary advertises the same GNU build-id:
readelf -n /proc/$(pgrep -f payments-api | head -n1)/exe | grep -i 'Build ID' - If you operate a debuginfod mirror backed by the Offline Kit tree, resolve symbols with:
debuginfod-find debuginfo 5f0c7c3cb4d9f8a4f1c1d5c6b7e8f90123456789 >/tmp/payments-api.debug
See Offline Kit step 0 for instructions on mirroring the debug store before packaging.
POST /api/v1/scanner/policy/runtime responds with one entry per digest. Each result now includes:
policyVerdict(pass|warn|fail|error),signed, andhasSbomReferrersparity with the webhook contract.confidence(0-1 double) derived from canonicalPolicyPreviewServiceevaluation andquieted/quietedByflags for muted findings.rekorblock carryinguuid,url, and the attestor-backedverifiedboolean when Rekor inclusion proofs have been confirmed.metadata(stringified JSON) capturing runtime heuristics, policy issues, evaluated findings, and timestamps for downstream audit.buildIds(array) lists up to three distinct GNU build-id hashes recently observed for that digest so debuggers can derive/usr/lib/debug/.build-id/<aa>/<rest>.debugpaths for symbol stores.
When running on an interactive terminal without explicit override flags, the CLI uses Spectre.Console prompts to let you choose per-run ORAS/offline bundle behaviour.
Runtime verdict output reflects the SCANNER-RUNTIME-12-302 contract sign-off (quieted provenance, confidence band, attestation verification). CLI-RUNTIME-13-008 now mirrors those fields in both table and --json formats.
Startup diagnostics
stellaops-clinow loads Authority plug-in manifests during startup (respectingAuthority:Plugins:*) and surfaces analyzer warnings when a plug-in weakens the baseline password policy (minimum length 12 and all character classes required).- Follow the log entry’s config path and raise
passwordPolicy.minimumLengthto at least 12 while keepingrequireUppercase,requireLowercase,requireDigit, andrequireSymbolset totrueto clear the warning; weakened overrides are treated as actionable security deviations.
Logging & exit codes
- Structured logging via
Microsoft.Extensions.Loggingwith single-line console output (timestamps in UTC). --verbose / -vraises log level toDebug.- Command exit codes bubble up: backend conflict →
1, cancelled viaCTRL+C→130, scanner exit codes propagate as-is.
Artifact validation
- Downloads are verified against the
X-StellaOps-Digestheader (SHA-256). WhenStellaOps:ScannerSignaturePublicKeyPathpoints to a PEM-encoded RSA key, the optionalX-StellaOps-Signatureheader is validated as well. - Metadata for each bundle is written alongside the artefact (
*.metadata.json) with digest, signature, source URL, and timestamps. - Retry behaviour is controlled via
StellaOps:ScannerDownloadAttempts(default 3 with exponential backoff). - Successful
scan runexecutions create timestamped JSON artefacts insideResultsDirectoryplus ascan-run-*.jsonmetadata envelope documenting the runner, arguments, timing, and stdout/stderr. The artefact is posted back to Concelier automatically.
Trivy DB export metadata (metadata.json)
stellaops-cli db export --format trivy-db (and the backing POST /jobs/export:trivy-db) always emits a metadata.json document in the OCI layout root. Operators consuming the bundle or delta updates should inspect the following fields:
| Field | Type | Purpose |
|---|---|---|
mode | full | delta | Indicates whether the current run rebuilt the entire database (full) or only the changed files (delta). |
baseExportId | string? | Export ID of the last full baseline that the delta builds upon. Only present for mode = delta. |
baseManifestDigest | string? | SHA-256 digest of the manifest belonging to the baseline OCI layout. |
resetBaseline | boolean | true when the exporter rotated the baseline (e.g., repo change, delta chain reset). Treat as a full refresh. |
treeDigest | string | Canonical SHA-256 digest of the JSON tree used to build the database. |
treeBytes | number | Total bytes across exported JSON files. |
advisoryCount | number | Count of advisories included in the export. |
exporterVersion | string | Version stamp of StellaOps.Concelier.Exporter.TrivyDb. |
builder | object? | Raw metadata emitted by trivy-db build (version, update cadence, etc.). |
delta.changedFiles[] | array | Present when mode = delta. Each entry lists { "path": "<relative json>", "length": <bytes>, "digest": "sha256:..." }. |
delta.removedPaths[] | array | Paths that existed in the previous manifest but were removed in the new run. |
When the planner opts for a delta run, the exporter copies unmodified blobs from the baseline layout identified by baseManifestDigest. Consumers that cache OCI blobs only need to fetch the changedFiles and the new manifest/metadata unless resetBaseline is true. When pushing to ORAS, set concelier:exporters:trivyDb:oras:publishFull / publishDelta to control whether full or delta runs are copied to the registry. Offline bundles follow the analogous includeFull / includeDelta switches under offlineBundle.
Example configuration (appsettings.yaml):
concelier:
exporters:
trivyDb:
oras:
enabled: true
publishFull: true
publishDelta: false
offlineBundle:
enabled: true
includeFull: true
includeDelta: false
Authentication
- API key is sent as
Authorization: Bearer <token>automatically when configured. - Anonymous operation is permitted only when Concelier runs with
authority.allowAnonymousFallback: true. This flag is temporary—plan to disable it before 2025-12-31 UTC so bearer tokens become mandatory.
Authority-backed auth workflow:
- Configure Authority settings via config or env vars (see sample below). Minimum fields:
Url,ClientId, and eitherClientSecret(client credentials) orUsername/Password(password grant). - Run
stellaops-cli auth loginto acquire and cache a token. Use--forceif you need to ignore an existing cache entry. - Execute CLI commands as normal—the backend client injects the cached bearer token automatically and retries on transient 401/403 responses with operator guidance.
- Inspect the cache with
stellaops-cli auth status(shows expiry, scope, mode) or clear it viastellaops-cli auth logout. - Run
stellaops-cli auth whoamito dump token subject, audience, issuer, scopes, and remaining lifetime (verbose mode prints additional claims). - Expect Concelier to emit audit logs for each
/jobs*request showingsubject,clientId,scopes,status, and whether network bypass rules were applied.
Tokens live in ~/.stellaops/tokens unless StellaOps:Authority:TokenCacheDirectory overrides it. Cached tokens are reused offline until they expire; the CLI surfaces clear errors if refresh fails.
For offline workflows, configure StellaOps:Offline:KitsDirectory (or STELLAOPS_OFFLINE_KITS_DIR) to control where bundles, manifests, and metadata are stored, and StellaOps:Offline:KitMirror (or STELLAOPS_OFFLINE_MIRROR_URL) to override the download base URL when pulling from a mirror.
Configuration file template
{
"StellaOps": {
"ApiKey": "your-api-token",
"BackendUrl": "https://concelier.example.org",
"ScannerCacheDirectory": "scanners",
"ResultsDirectory": "results",
"DefaultRunner": "docker",
"ScannerSignaturePublicKeyPath": "",
"ScannerDownloadAttempts": 3,
"Offline": {
"KitsDirectory": "offline-kits",
"KitMirror": "https://get.stella-ops.org/ouk/"
},
"Authority": {
"Url": "https://authority.example.org",
"ClientId": "concelier-cli",
"ClientSecret": "REDACTED",
"Username": "",
"Password": "",
"Scope": "concelier.jobs.trigger advisory:ingest advisory:read",
"TokenCacheDirectory": ""
}
}
}
Drop appsettings.local.json or .yaml beside the binary to override per environment.
2.5 Misc Endpoints
| Path | Method | Description |
|---|---|---|
/healthz | GET | Liveness; returns "ok" |
/metrics | GET | Prometheus exposition (OTel) |
/version | GET | Git SHA + build date |
2.6 Authority Admin APIs
Administrative endpoints live under /internal/* on the Authority host and require the bootstrap API key (x-stellaops-bootstrap-key). Responses are deterministic and audited via AuthEventRecord.
| Path | Method | Description |
|---|---|---|
/internal/revocations/export | GET | Returns the revocation bundle (JSON + detached JWS + digest). Mirrors the output of stellaops-cli auth revoke export. |
/internal/signing/rotate | POST | Promotes a new signing key and marks the previous key as retired without restarting the service. |
Rotate request body
{
"keyId": "authority-signing-2025",
"location": "../certificates/authority-signing-2025.pem",
"source": "file",
"provider": "default"
}
The API responds with the active kid, previous key (if any), and the set of retired key identifiers. Always export a fresh revocation bundle after rotation so downstream mirrors receive signatures from the new key.
3 First‑Party CLI Tools
3.1 stella
Package SBOM + Scan + Exit code – designed for CI.
Usage: stella [OPTIONS] IMAGE_OR_SBOM
| Flag / Option | Default | Description |
|---|---|---|
--server | http://localhost:8080 | API root |
--token | env STELLA_TOKEN | Bearer token |
--sbom-type | auto | Force trivy-json-v2/spdx-json/cyclonedx-json |
--delta | false | Enable delta layer optimisation |
--policy-file | none | Override server rules with local YAML/Rego |
--threshold | critical | Fail build if ≥ level found |
--output-json | none | Write raw scan result to file |
--wait-quota | true | If 429 received, automatically wait Retry‑After and retry once. |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Scan OK, policy passed |
| 1 | Vulnerabilities ≥ threshold OR policy block |
| 2 | Internal error (network etc.) |
3.2 stella‑zastava
Daemon / K8s DaemonSet – watch container runtime, push SBOMs.
Core flags (excerpt):
| Flag | Purpose |
|---|---|
--mode | listen (default) / enforce |
--filter-image | Regex; ignore infra/busybox images |
--threads | Worker pool size |
3.3 stellopsctl
Admin utility – policy snapshots, feed status, user CRUD.
Examples:
stellopsctl policy export > policies/backup-2025-07-14.yaml
stellopsctl feed refresh # force OSV merge
stellopsctl user add dev-team --role developer
4 Error Model
Uniform problem‑details object (RFC 7807):
{
"type": "https://stella-ops.org/probs/validation",
"title": "Invalid request",
"status": 400,
"detail": "Layer digest malformed",
"traceId": "00-7c39..."
}
5 Rate Limits
Default 40 requests / second / token.
429 responses include Retry-After seconds header.
6 FAQ & Tips
- Skip SBOM generation in CI – supply a pre‑built SBOM and add
?sbom-only=trueto/scanfor <1 s path. - Air‑gapped? – point
--servertohttp://oukgw:8080inside the Offline Update Kit. - YAML vs Rego – YAML simpler; Rego unlocks time‑based logic (see samples).
- Cosign verify plug‑ins – enable
SCANNER_VERIFY_SIG=trueenv to refuse unsigned plug‑ins.
7 Planned Changes (Beyond 6 Months)
These stay in Feature Matrix → To Do until design is frozen.
| Epic / Feature | API Impact Sketch |
|---|---|
| SLSA L1‑L3 attestation | /attest (see §2.4) |
| Rekor transparency log | /rekor/log/{id} (GET) |
| Plug‑in Marketplace metadata | /plugins/market (catalog) |
| Horizontal scaling controls | POST /cluster/node (add/remove) |
| Windows agent support | Update LSAPI to PDE, no API change |
8 References
- OpenAPI YAML →
/openapi/v1.yaml(served by backend) - OAuth2 spec: https://datatracker.ietf.org/doc/html/rfc6749
- SLSA spec: https://slsa.dev/spec/v1.0
9 Changelog (truncated)
- 2025‑07‑14 – added delta SBOM, policy import/export, CLI
--sbom-type. - 2025‑07‑12 – initial public reference.