CI/CD Gate Flow
Overview
The CI/CD Gate Flow describes how StellaOps integrates into continuous integration and deployment pipelines to provide automated security gates. The flow covers CLI-based scanning, policy evaluation, and pass/fail decisions that control pipeline progression.
Audience: DevOps and platform engineers wiring the stella CLI into CI/CD pipelines (GitHub Actions, GitLab CI, Gitea Actions) to gate releases on policy verdicts and attestation checks.
Business Value: Shift-left security by catching vulnerabilities before deployment, with deterministic, reproducible verdicts that integrate into existing DevOps workflows.
Actors
| Actor | Type | Role |
|---|---|---|
| CI Pipeline | System | GitHub Actions, GitLab CI, Gitea Actions (template generator); other CI can invoke the CLI directly |
StellaOps CLI (stella) | Tool | Runs scan/gate/verify from the pipeline |
| Gateway | Service | API entry point |
| Scanner | Service | Performs image analysis |
| Policy Engine | Service | Evaluates the release gate (api/v1/policy/gate/evaluate) |
| Attestor | Service | Signs scan results (DSSE) |
Prerequisites
- StellaOps CLI (
stella, also published asstellaops) installed in CI environment - Authority credentials configured: a confidential OAuth client (client credentials) for automation, or the seeded human client (username/password). See
stella auth loginbelow. - Policy / gate baseline defined for the pipeline (the Policy Engine evaluates against a policy ID and an optional baseline)
- Container image built and available
Source of truth: the CLI command tree is assembled in
src/Cli/StellaOps.Cli/Commands/CommandFactory.cs. The CI/CD-facing verbs below arescan,gate,ci,guard,proof, andverify.
Supported CI/CD Platforms
The CLI ships first-class workflow templates via stella ci init for the platforms below. Other CI systems can still invoke the CLI directly (it is a self-contained binary), but no template generator exists for them.
| Platform | Template generator (stella ci init --platform) | Credentials |
|---|---|---|
| GitHub Actions | github | OIDC (stellaops/auth@v1) or token |
| GitLab CI | gitlab | CI_JOB_TOKEN or token |
| Gitea Actions | gitea | Token |
NOT IMPLEMENTED (no
ci inittemplate): Azure DevOps, Jenkins, CircleCI, Tekton.stella ci initaccepts onlygithub,gitlab,gitea, orall(seeCiCommandGroup.cs). These platforms can run the CLI by hand but are not generated.
Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────────┐
│ CI/CD Gate Flow │
└─────────────────────────────────────────────────────────────────────────────────┘
┌────────────┐ ┌───────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐
│ CI Pipeline│ │StellaOps │ │ Gateway │ │ Scanner │ │ Policy │ │ Attestor│
│ │ │ CLI │ │ │ │ │ │ │ │ │
└─────┬──────┘ └─────┬─────┘ └────┬────┘ └────┬────┘ └───┬────┘ └────┬────┘
│ │ │ │ │ │
│ docker build │ │ │ │ │
│───────┐ │ │ │ │ │
│ │ │ │ │ │ │
│<──────┘ │ │ │ │ │
│ │ │ │ │ │
│ stella scan │ │ │ │ │
│ image <ref> │ │ │ │ │
│ + gate eval │ │ │ │ │
│──────────────>│ │ │ │ │
│ │ │ │ │ │
│ │ POST /scans │ │ │ │
│ │────────────>│ │ │ │
│ │ │ │ │ │
│ │ │ Dispatch │ │ │
│ │ │───────────>│ │ │
│ │ │ │ │ │
│ │ │ │ Analyze │ │
│ │ │ │ image │ │
│ │ │ │───┐ │ │
│ │ │ │ │ │ │
│ │ │ │<──┘ │ │
│ │ │ │ │ │
│ │ │ │ Evaluate │ │
│ │ │ │──────────>│ │
│ │ │ │ │ │
│ │ │ │ │ Apply │
│ │ │ │ │ rules │
│ │ │ │ │───┐ │
│ │ │ │ │ │ │
│ │ │ │ │<──┘ │
│ │ │ │ │ │
│ │ │ │ Verdict │ │
│ │ │ │<──────────│ │
│ │ │ │ │ │
│ │ │ │ Sign │ │
│ │ │ │──────────────────────>│
│ │ │ │ │ │
│ │ │ │ DSSE │ │
│ │ │ │<──────────────────────│
│ │ │ │ │ │
│ │ │ Result │ │ │
│ │ │<───────────│ │ │
│ │ │ │ │ │
│ │ Verdict │ │ │ │
│ │<────────────│ │ │ │
│ │ │ │ │ │
│ Exit code │ │ │ │ │
│ (0=pass, │ │ │ │ │
│ 1=fail) │ │ │ │ │
│<──────────────│ │ │ │ │
│ │ │ │ │ │
│ [if pass] │ │ │ │ │
│ docker push │ │ │ │ │
│───────┐ │ │ │ │ │
│ │ │ │ │ │ │
│<──────┘ │ │ │ │ │
│ │ │ │ │ │
Step-by-Step
1. Pipeline Configuration
The examples below mirror the workflow templates generated by
stella ci init(src/Cli/StellaOps.Cli/Commands/CiTemplates.cs). Runstella ci init --platform github --template gateto scaffold the file into.github/workflows/stellaops-gate.yml. The CLI binary isstella.Caveat (see §3): several commands the generated templates emit do not match the implemented CLI surface.
- The templates call
stella scan image <ref> --format ... --output ... --sbom-output ..., but the implementedstella scan imagecommand is an analysis group (inspect/layers), not a scan-and-emit verb (see §3).- The scan/gitlab templates call
stella sbom upload <file> --image ..., but thesbomgroup (SbomCommandGroup.cs) has nouploadsubcommand; the closest implemented command isstella sbom publish --image <ref> --file <sbom>(publishes a canonical SBOM as an OCI referrer).- The real generated GitHub gate template also includes a “Gate Summary” step that runs
stella gate evaluate ... --output markdown;gate evaluate --outputonly acceptstable,json, andexit-code-only(GateCommandGroup.cs), somarkdownfalls through to the default table renderer.- The generated
verifytemplates callstella verify image ... --require-sbom --require-scan --require-signature; those split flags do not exist (see §5a — the real flag is--require/-r).These are template/CLI mismatches, not verified behaviour — adjust the scaffolded files to the implemented command surface before relying on them.
GitHub Actions Example
# Generated by: stella ci init --platform github --template gate
name: StellaOps Release Gate
on:
push:
branches: [main, release/*]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
id-token: write # Required for OIDC token exchange
security-events: write # For SARIF upload
env:
STELLAOPS_BACKEND_URL: ${{ secrets.STELLAOPS_BACKEND_URL }}
jobs:
gate:
name: Release Gate Evaluation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up StellaOps CLI
uses: stellaops/setup-cli@v1
with:
version: latest
- name: Authenticate with OIDC
uses: stellaops/auth@v1
with:
audience: stellaops
- name: Build Container Image
id: build
run: |
IMAGE_TAG="${{ github.sha }}"
docker build -t app:$IMAGE_TAG .
echo "image=app:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Scan Image
id: scan
run: |
stella scan image ${{ steps.build.outputs.image }} \
--format sarif \
--output results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
- name: Evaluate Gate
id: gate
run: |
stella gate evaluate \
--image ${{ steps.build.outputs.image }} \
--baseline production \
--output json > gate-result.json
EXIT_CODE=$(jq -r '.exitCode' gate-result.json)
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
- name: Check Gate Status
if: steps.gate.outputs.exit_code != '0'
run: |
echo "::error::Release gate check failed"
exit ${{ steps.gate.outputs.exit_code }}
The pass/fail decision is produced by
stella gate evaluate, not by a--fail-onflag onscan. The scan step emits SARIF; the gate step calls the Policy Engine and surfaces the verdict via its JSONexitCodefield. See Gate Evaluation below.
GitLab CI Example
# Generated by: stella ci init --platform gitlab --template gate
stages:
- build
- scan
- gate
- deploy
variables:
STELLAOPS_BACKEND_URL: $STELLAOPS_BACKEND_URL
DOCKER_TLS_CERTDIR: "/certs"
.stellaops-setup:
before_script:
- curl -fsSL https://get.stellaops.io/cli | sh
- export PATH="$HOME/.stellaops/bin:$PATH"
build:
stage: build
image: docker:24-dind
services:
- docker:24-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_MERGE_REQUEST_ID
scan:
stage: scan
extends: .stellaops-setup
script:
- stella scan image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--sbom-output sbom.cdx.json
--format json
--output scan-results.json
- stella sbom upload sbom.cdx.json --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
paths:
- sbom.cdx.json
- scan-results.json
reports:
container_scanning: scan-results.json
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_MERGE_REQUEST_ID
gate:
stage: gate
extends: .stellaops-setup
script:
- |
stella gate evaluate \
--image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--baseline production \
--output json > gate-result.json
EXIT_CODE=$(jq -r '.exitCode' gate-result.json)
if [ "$EXIT_CODE" != "0" ]; then
echo "Release gate failed"
exit $EXIT_CODE
fi
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy:
stage: deploy
needs: [gate]
script:
- echo "Deploy $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
rules:
- if: $CI_COMMIT_BRANCH == "main"
2. CLI Authentication
Authentication is handled by stella auth login. It acquires and caches an access token from the configured StellaOps Authority. The grant used depends on how the CLI profile is configured (see CommandFactory.BuildAuthCommand and CommandHandlers.HandleAuthLoginAsync):
- Automation (CI/CD): configure a confidential OAuth client (client ID + secret, via
stella configorSTELLAOPS_*environment variables);stella auth loginthen performs a client-credentials grant. - Interactive: the seeded human client prompts for username/password.
# Acquire and cache a token (uses the configured client credentials in CI)
stella auth login
# CI/kiosk: keep the token in process memory only (do not write the secrets blob / OS keystore)
stella auth login --no-save # implied when STELLAOPS_LOGIN_NO_SAVE=1 in a non-interactive shell
# Force re-authentication, ignoring any cached token
stella auth login --force
| Command | Description |
|---|---|
stella auth login | Acquire and cache an access token |
stella auth logout | Remove cached tokens for the current credentials |
stella auth status | Display cached token status |
stella auth whoami | Display cached token claims (subject, scopes, expiry) |
CORRECTED: there are no
--token,--oidc, or--keylessflags onstella auth login. The only flags are--forceand--no-save. The OIDC token exchange in GitHub Actions is performed by thestellaops/auth@v1action, not by a CLI flag. The git provider’s CI token (e.g.CI_JOB_TOKEN) is used by the SCM integration steps, not passed toauth login.
3. Scan Execution
CORRECTED: there is no flat
stellaops scan <image> --policy ... --fail-on violationcommand. Thescancommand is a group (CommandFactory.BuildScanCommand) whose subcommands includerun,upload,sarif,entrytrace,replay,layers,layer-sbom,recipe,diff,delta,verify-patches,download,workers,secrets,image, andsubmit-list. In CI the scan step emits results (SARIF/JSON/SBOM); the pass/fail decision is a separategate evaluatestep.
CORRECTED —
stella scan image <ref> --format ... --output ...is NOT an implemented scanning verb. Thestella scan imagecommand (CommandFactory.BuildScanImageCommand, “moved fromstella image”) is an image-analysis group whose only subcommands areinspectandlayers; both currently print placeholder/sample output and neither performs a scan nor accepts--sbom-output,--format, or--output. Thestella scan image <ref> --format sarif --output .../--sbom-output ...forms that appear in this doc’s pipeline examples come verbatim from the templates emitted bystella ci init(CiTemplates.cs). Those generated templates reference a scan-image command shape that the current CLI surface does not back — i.e. the templates are ahead of (or out of sync with) the implementedscangroup. The real scanning entrypoints today arestella scan run(runner bundle) and backend submission (stella scan submit-list,stella scan upload). Treat thescan image <ref>scan-and-emit form below as the generated-template shape, not a verified CLI contract.
The generated GitHub/GitLab templates invoke the scan step like this (reproduced from CiTemplates.cs):
# Emitted by `stella ci init` templates — see CORRECTED note above re: scan-image command mismatch
stella scan image docker.io/myorg/myapp:v1.2.3 \
--sbom-output sbom.cdx.json \
--format sarif \
--output scan-results.sarif
Relevant scan subcommands
| Subcommand | Purpose |
|---|---|
stella scan run | Execute a scanner bundle with the configured runner (--runner dotnet|self|docker, --entry, --target). This is the implemented scanning verb. |
stella scan submit-list | Submit a list of images/artifacts to the backend for scanning |
stella scan upload --file <path> | Upload completed scan results to the backend |
stella scan sarif --scan-id <id> | Export an existing scan’s results as SARIF 2.1.0 (--output/-o, --min-severity, --include-reachability, --include-hardening, --pretty) |
stella scan replay | Deterministic verdict replay from explicit input hashes (--artifact, --manifest, --feeds, --policy) |
stella scan image inspect|layers | Image-analysis helpers (metadata / layer listing). Placeholder output today; NOT a scan-and-emit verb. |
The
scan sarifoutput-format/destination flags are--output/-o(file, defaults to stdout) with--min-severity(none,note,warning,error).scan runforwardsscanner-argsto the runner rather than taking a--format/--outputpair. Policy evaluation and the deploy gate are applied bystella gate evaluate(below), which takes the policy/baseline — not by thescanstep.
3a. Source-tree SBOM (stella sbom generate --directory)
For a source-side bill of materials — generated from a checked-out working tree before (or instead of) an image build — use the deterministic sbom generate verb (Commands/Sbom/SbomGenerateCommand.cs). Unlike the image-analysis helpers under scan image, this is a real, implemented, reproducible SBOM producer.
# Source-tree SBOM from the checked-out repo (deterministic; offline-friendly)
stella sbom generate \
--directory ./src \
--format cyclonedx \
--output sbom.cdx.json
Flags (verified against SbomGenerateCommand.cs):
| Flag | Purpose |
|---|---|
--directory/-d | Local directory to scan (mutually exclusive with --image/-i). |
--format/-f | cyclonedx, spdx, or both. |
--output/-o | Output file path (or directory when --format both). |
--source-date | Deterministic SBOM timestamp (Unix epoch or ISO-8601). For --directory this is the only timestamp source; defaults to SOURCE_DATE_EPOCH, else 1970-01-01. |
--force | Overwrite an existing output file. |
--show-hash | Print the golden hash after generation (verify with stella sbom hash / sbom verify). |
Because it is timestamp-pinned and deterministic, the same source tree yields a byte-identical SBOM (and golden hash) across machines — suitable as a CI artifact and as a material for build provenance.
3c. Build provenance (stella attest build)
After the image is built (and pushed, so a manifest digest exists), mint build provenance — the commit -> artifact lineage as an in-toto/SLSA-shaped predicate — and submit it to the deployed Attestor’s links endpoint (POST /api/v1/attestor/links, which signs it and records a transparency-log entry). The ci init gate templates (§1) now emit this step automatically.
# commit / repo / pipeline auto-fill from CI env (CI_COMMIT_SHA, CI_PROJECT_URL, CI_PIPELINE_URL,
# GITHUB_SHA, GITHUB_REPOSITORY, GITHUB_RUN_ID, ...). --subject is the artifact digest.
stella attest build --subject registry/app@sha256:<digest>
# Air-gapped / no backend: emit the (clearly-labelled) UNSIGNED predicate locally instead of submitting.
stella attest build --subject registry/app@sha256:<digest> --offline --output provenance.intoto.json
Key flags (verified against Commands/AttestCommandGroup.cs): --subject/-s (required — the artifact ref/digest; --subject-digest supplies the digest when the ref carries none), --commit/-c, --repo/-r, --builder/-b, --pipeline, --offline (emit unsigned, do not submit), --attach (also push the signed DSSE as an OCI referrer), --no-rekor (sign but skip the transparency log). Missing required lineage (subject digest, commit, repo) is refused with an actionable error — no field is fabricated. Verify a minted envelope with stella attest verify --attestation <dsse.json> --key <public.pem> (add --uuid <rekor-uuid> for a live inclusion check; --offline labels inclusion as NOT CHECKED rather than claiming it).
3b. AI Code Guard (optional)
Run AI code guard checks on a change set and emit CI-friendly output (GuardCommandGroup.cs):
stella guard run . \
--policy .stellaops.yml \
--format sarif \
--output guard.sarif
Key options: a positional path argument (defaults to .), --policy/-p, --base/--head (diff analysis), --format/-f (json default, sarif, gitlab, table), --output/-o (defaults to stdout), --confidence (default 0.7), --min-severity (default low), --sealed (deterministic, no network), --categories/-c, --exclude/-e, --server.
CORRECTED: the output flag is
--output/-o, not--out. Exit codes (GuardExitCodes) are:
| Verdict | Exit Code |
|---|---|
| Pass | 0 |
| Pass with warnings | 1 |
| Fail (blocking findings) | 2 |
| Input error | 10 |
| Network error | 11 |
| Analysis error | 12 |
| Unknown error | 99 |
So a blocking verdict exits
2, not1; warnings exit1.
4. Policy Evaluation
Policy engine evaluates findings against CI-specific rules:
# Policy Set: production
version: "stella-dsl@1"
name: production
rules:
- name: block-critical
condition: severity == 'critical' AND vex_status != 'not_affected'
action: FAIL
- name: block-high-unfixed
condition: severity == 'high' AND fixed_version == null
action: FAIL
- name: block-known-exploited
condition: kev == true
action: FAIL
- name: require-sbom
condition: sbom_complete == false
action: FAIL
message: "SBOM must cover all detected packages"
gates:
ci:
max_critical: 0
max_high_unfixed: 0
require_attestation: true
The policy/gate YAML above is illustrative of the policy authoring shape; the authoritative DSL and gate-condition grammar live in the Policy module. The CI step that invokes the gate is
stella gate evaluate(below).
4b. Gate Evaluation (stella gate)
The deploy decision is produced by the gate command group (GateCommandGroup.cs), which calls the Policy Engine’s gate endpoint (POST api/v1/policy/gate/evaluate) and maps the result to a process exit code.
stella gate evaluate \
--image sha256:abc123... \
--baseline production \
--policy <policy-id> \
--branch "$CI_COMMIT_BRANCH" \
--commit "$CI_COMMIT_SHA" \
--pipeline "$CI_PIPELINE_ID" \
--env production \
--output json
| Option | Description |
|---|---|
--image / -i | Image digest to evaluate (e.g. sha256:...). Required. |
--baseline / -b | Baseline reference for comparison (snapshot ID, digest, or last-approved) |
--policy / -p | Policy ID to use for evaluation |
--allow-override | Allow override of blocking gates (requires --justification) |
--justification / -j | Justification for override |
--branch, --commit, --pipeline, --env | Git/CI context recorded with the decision |
--output / -o | table (default), json, or exit-code-only |
--timeout | Request timeout in seconds (default 60) |
Sibling subcommands: stella gate status --decision-id <id> and stella gate score (score-based gating).
4c. Template Scaffolding (stella ci)
stella ci (CiCommandGroup.cs) generates and validates CI/CD workflow templates:
# Scaffold a release-gate workflow into the current repo
stella ci init --platform github --template gate --mode scan-attest
# List the available templates
stella ci list
# Validate a generated template
stella ci validate .github/workflows/stellaops-gate.yml
Option (ci init) | Values |
|---|---|
--platform / -p | github, gitlab, gitea, all (required) |
--template / -t | gate (default), scan, verify, full |
--mode / -m | scan-only, scan-attest (default), scan-vex |
--output / -o | Output directory (default: current directory) |
--offline | Generate offline-friendly bundle with pinned digests |
--scanner-image | Override the scanner image reference |
--force / -f | Overwrite existing files |
5. Verdict and Exit Code
stella gate evaluate and stella guard run translate their verdict into a process exit code. The values below are the canonical gate/guard codes (GateExitCodes / GuardExitCodes):
| Verdict | Exit Code | Pipeline Result |
|---|---|---|
| PASS | 0 | Continue |
| WARN | 1 | Continue or block (pipeline decides; the code is non-zero) |
| FAIL | 2 | Block deployment |
| Input error | 10 | Invalid parameters |
| Network error | 11 | Unable to reach gate/scanner service |
| Policy/analysis error | 12 | Evaluation failed |
| Unknown error | 99 | Unexpected failure |
CORRECTED: a FAIL verdict exits
2(not1) and a WARN verdict exits1(not0). There is no--fail-onflag controlling this. Theproof verifycommand uses a different exit-code scheme (see §5a). Pipelines branch on the non-zero exit code (or parse the JSONexitCodefield), as shown in the generated templates above.
5a. Attestation / Proof Verification
Sprint: SPRINT_20260112_004_DOC_cicd_gate_verification
Before deploying, pipelines verify the attestation chain and (online) Rekor inclusion. This ensures attestation integrity and a tamper-evident audit trail. Two CLI surfaces apply here:
stella verify image <ref>— verify the attestation chain attached to a container image (VerifyCommandGroup.cs).stella proof verify --bundle <path>— verify an exported attestation bundle’s proof chain (Proof/ProofCommandGroup.cs).
CORRECTED: there is no
stella proof verify --image ... --attestation-type ... --check-rekor --fail-on-missingform, and no--ledger-path. Those flags do not exist.proof verifyoperates on a--bundlefile. Image-level attestation verification isstella verify image. There is nostella evidence-pack verify; theevidence-packverb is an export subcommand (stella export evidence-pack) — bundle verification isstella proof verifyorstella verify bundle.
Verify the attestation chain on an image (online)
stella verify image ghcr.io/org/myapp@sha256:... \
--require sbom vex decision \
--strict \
--output json
stella verify image flags: positional reference; --require/-r (required attestation types — defaults to sbom vex decision; also approval); --trust-policy (path to a YAML/JSON trust policy); --output/-o (table/json/sarif); --strict (fail if any required attestation is missing); --allow-unsigned (opt out of the trust-policy gate with a WARN — see below).
Honest default (SPRINT_20260704_001 / CAT-3): with no
--trust-policyconfigured,verify imagenow fails closed with an explicitNo trust policy configuredmessage instead of silently passing — signer trust cannot be enforced without a policy. Pass--allow-unsignedto explicitly proceed (attestations are structurally parsed but signer trust is NOT enforced; emitted with a[WARN]). The image transparency signal is annotation-presence only and is reported as such (transparency: presence-only), never as a verified inclusion proof.
CORRECTED: the generated
verifyworkflow templates (stella ci init --template verify,CiTemplates.cs) callstella verify image <ref> --require-sbom --require-scan --require-signature. Those split--require-*boolean flags do not exist onverify image; the implemented flag is a single repeatable--require/-rthat takes attestation-type values (sbom vex decision, plusapproval). Use the--require sbom vex decisionform shown above, not the--require-*form the template scaffolds.
Verify an exported bundle (offline / air-gapped)
# proof verify operates on an exported attestation bundle (.tar.gz)
stella proof verify \
--bundle /path/to/attestation-bundle.tar.gz \
--offline \
--output json
# E2E evidence bundle (replay-checked)
stella verify bundle --bundle /path/to/evidence-pack.tar.gz --output json
stella proof verify flags: --bundle/-b (required, path to .tar.gz); --offline (skip Rekor / transparency verification); --output/-o (text default, or json); --verbose. (proof also exposes proof spine show <bundle-id>, but spine retrieval is not yet implemented — it prints a placeholder.)
CORRECTED: the exit codes that
stella proof verifyactually returns come fromAttestationBundleExitCodes(Services/Models/AttestationBundleModels.cs), surfaced viaresult.ExitCodeinProofCommandGroup.HandleVerifyAsync. TheProofExitCodestype (Proof/ProofExitCodes.cs) defines the advisory §15.2 scheme but the verify handler only uses it for the catch-allSystemError(exception path); a missing bundle returnsAttestationBundleExitCodes.FileNotFound(6), notProofExitCodes.KeyRevoked. The two schemes diverge at codes 2–8, so trust the runtime (AttestationBundleExitCodes) table below, not the advisoryProofExitCodesnumbering.
stella proof verify runtime exit codes (AttestationBundleExitCodes):
| Code | Meaning |
|---|---|
| 0 | Success — bundle verified |
| 1 | General verification failure |
| 2 | Checksum mismatch (bundle integrity) |
| 3 | Signature failure (DSSE envelope) |
| 4 | Missing transparency / Rekor inclusion (online mode) |
| 5 | Format error (malformed bundle) |
| 6 | Bundle file not found |
| 7 | Import failed |
For reference, the advisory
ProofExitCodesscheme (defined but largely unused by the verify handler) is: 0 Success · 1 PolicyViolation · 2 SystemError · 3 VerificationFailed · 4 TrustAnchorError · 5 RekorVerificationFailed · 6 KeyRevoked · 7 OfflineModeError · 8 InputError. Do not rely on this numbering forproof verifybranching today.
GitHub Actions Integration
- name: Verify attestation chain
run: |
stella verify image ghcr.io/org/myapp@${{ steps.build.outputs.digest }} \
--require sbom vex decision \
--strict
- name: Push to registry (only if verified)
if: success()
run: |
docker push ghcr.io/org/myapp:${{ github.sha }}
NOT IMPLEMENTED / external: the cosign
verify-attestationexamples previously shown here are upstream Sigstore commands, not StellaOps CLI commands, and the--type https://stellaops.io/attestation/...predicate URIs were illustrative. Usestella verify image/stella proof verifyfor the in-product path. If you verify with cosign directly, consult cosign’s own documentation for current flags.
Related Documentation:
6. SARIF Integration
CLI outputs SARIF for IDE and GitHub integration:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "StellaOps",
"version": "2.1.0",
"informationUri": "https://stellaops.io"
}
},
"results": [
{
"ruleId": "CVE-2024-1234",
"level": "error",
"message": {
"text": "Critical vulnerability in lodash@4.17.20"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "package-lock.json"
}
}
}
]
}
]
}
]
}
7. Attestation Verification
CORRECTED: there is no top-level
stellaops attestationcommand and noattestation show --scanverb. DSSE attestations are inspected and verified through theattestgroup (AttestCommandGroup, wired viaCommandFactory.BuildAttestCommand) and the unifiedverifygroup (VerifyCommandGroup).
The live root stella attest verify (CommandFactory.BuildAttestCommand → HandleAttestVerifyAsync) is envelope-anchored: it takes --envelope/-e (required, path to a DSSE envelope file), --policy (JSON policy: required predicate types / minimum signatures / required signers), --root (an EC/RSA public key or X.509 certificate PEM), --transparency-checkpoint, --require-timestamp, --max-skew, --output/-o, --format/-f (table/json), and --explain.
Real signature check (SPRINT_20260704_001 / CAT-2): the
Signature Verificationstep is now a real cryptographic DSSE check over PAE(payloadType, payload) using the public key in--root— it passes only when a signature actually verifies. (It was previously a placeholder that passed merely because a--rootfile existed.) A tampered payload, a wrong key, or an unsigned envelope fails. The transparency step in this online mode is presence-only and is reported as such — usestella attest verify-offlinefor a real inclusion proof.
The image-anchored path is stella verify image <ref> / stella verify attestation --image <ref> (real ImageAttestationVerifier; §5a — fails closed without a trust policy). The attest group’s own client-side DSSE checker also accepts a raw envelope + public key (stella attest verify --attestation <dsse.json> --key <public.pem> [--uuid <rekor>] [--offline]).
# Verify a DSSE envelope's signature (+ policy / timestamp) against trust material
stella attest verify \
--envelope provenance.intoto.json \
--root cosign.pub \
--require-timestamp \
--format json
# Image-anchored: verify the attestations attached to an OCI image
stella verify attestation --image ghcr.io/org/myapp@sha256:... --trust-policy trust-policy.yaml
# Offline / air-gapped: verify an exported evidence bundle (no network)
stella attest verify-offline --bundle /path/to/attestation-bundle.tar.gz --trust-root ./trust-root
8. PR/MR Comment and Status Integration
QUARANTINED — not available in this release (PLAN_20260704 finding A5, decision D3 — quarantine, not delete; see the evidence-moat program plan “Explicitly deferred / cut” list). The outbound SCM-feedback path (the
stella githubcommand group and the GitHub/GitLab SCM annotation clients) is built but not wired: noIGitHubCodeScanningClientis DI-registered, so everystella github *verb now exits with a cleannot available in this releaseerror (exit code 1, no stack trace). The verb code and the annotation clients are preserved for a future SCM-feedback initiative. The sanctioned scan ingress and feedback path is CLI-in-CI: emit SARIF with the scanner and, on GitHub, upload it with GitHub’s owngithub/codeql-action/upload-sarifaction (see §“CI/CD Pipeline Integration” above). Treat the command table and example below as a design reference, not a runnable path.
StellaOps historically surfaced scan results in the SCM platform via the github command group (GitHubCommandGroup.cs): uploading SARIF to GitHub Code Scanning and managing the resulting code-scanning alerts. The inline --pr-comment / --check-run flags described historically below are roadmap and are not wired on any scan/gate command today.
CORRECTED: the
githubgroup does NOT update commit status, and there is no commit-status command. Its only five subcommands (GitHubCommandGroup.cs) areupload-sarif,list-alerts,get-alert,update-alert, andupload-status— andupload-statuschecks the processing status of a prior SARIF upload (it takes a<sarif-id>argument), it does not write a commit status.
GitHub Integration (QUARANTINED — verbs exit not available in this release)
The github command shapes (design reference; not runnable in this release) are:
| Command | Purpose |
|---|---|
stella github upload-sarif <file> --repo owner/repo | Upload SARIF to GitHub Code Scanning (--ref, --sha, --wait/-w, --timeout/-t, --tool-name, --github-url) |
stella github list-alerts --repo owner/repo | List code-scanning alerts |
stella github get-alert --repo owner/repo | Inspect a single alert |
stella github update-alert --repo owner/repo | Update a code-scanning alert state (dismiss/resolve) |
stella github upload-status <sarif-id> --repo owner/repo | Check the processing status of a prior SARIF upload (NOT a commit-status writer) |
# GitHub Actions: emit SARIF with the scanner, then upload with GitHub's own action.
# (The quarantined `stella github upload-sarif` push is NOT used here.)
- name: Scan and emit SARIF
run: stella scan image "myapp:${{ github.sha }}" --format sarif --output results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
ORPHANED (Draft / roadmap): the rich inline-comment surface below —
--pr-comment,--mr-comment,--check-run,--commit-status,--update-existing,--collapse-details,--evidence-link,--github-token,--gitlab-token— does not exist on any current CLI command (the only reference in source is the CLITASKS.mdbacklog). It is retained here as a design target for the comment/annotation format; do not treat the flags as runnable.
# DRAFT (roadmap) — flags below are not implemented
- name: Scan with PR feedback
run: |
stellaops scan myapp:${{ github.sha }} \
--policy production \
--pr-comment \
--check-run \
--github-token ${{ secrets.GITHUB_TOKEN }}
Example PR comment format:
## StellaOps Scan Results
**Verdict:** :warning: WARN
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 2 |
| Medium | 5 |
| Low | 12 |
### Findings Requiring Attention
| CVE | Severity | Package | Status |
|-----|----------|---------|--------|
| CVE-2026-1234 | High | lodash@4.17.21 | Fix available: 4.17.22 |
| CVE-2026-5678 | High | express@4.18.0 | VEX: Not affected |
<details>
<summary>View full report</summary>
[Download SARIF](https://stellaops.example.com/scans/abc123/sarif)
[View in Console](https://stellaops.example.com/scans/abc123)
</details>
---
*Scan ID: abc123 | Policy: production | [Evidence](https://stellaops.example.com/evidence/abc123)*
GitLab MR Integration (Draft / roadmap)
ORPHANED (Draft / roadmap): GitLab MR note/discussion posting via scan flags is not implemented. The block below is a design target, not a runnable command.
# DRAFT (roadmap) — flags below are not implemented
scan:
stage: test
script:
- stellaops scan $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--policy production \
--mr-comment \
--commit-status \
--gitlab-token $CI_JOB_TOKEN
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Comment Behavior Options (Draft / roadmap)
ORPHANED (Draft / roadmap): none of the options below are implemented on any current CLI command; they describe the intended comment-posting behavior.
| Option | Description | Default |
|---|---|---|
--pr-comment / --mr-comment | Post summary comment | false |
--check-run | Create GitHub check run with annotations | false |
--commit-status | Update commit status | false |
--update-existing | Edit previous comment instead of new | true |
--collapse-details | Use collapsible sections for long output | true |
--evidence-link | Include link to evidence bundle | true |
Evidence Anchoring in Comments
Comments include evidence references for auditability:
- Scan ID: Unique identifier for the scan
- Policy Version: The policy version used for evaluation
- Attestation Digest: DSSE envelope digest for signed results
- Rekor Entry: Log index when transparency logging is enabled
Error Handling
| Scenario | Behavior |
|---|---|
| No SCM token | Skip comment, log warning |
| API rate limit | Retry with backoff, then skip |
| Comment too long | Truncate with link to full report |
| PR already merged | Skip comment |
Evidence-First Annotation Format
PR/MR comments use ASCII-only output for determinism and maximum compatibility:
## StellaOps Security Scan
**Verdict:** [BLOCKING] Policy violation detected
| Status | Finding | Package | Action |
| --- | --- | --- | --- |
| [+] New | CVE-2026-1234 | lodash@4.17.21 | Fix: 4.17.22 |
| [-] Fixed | CVE-2025-9999 | express@4.17.0 | Resolved |
| [^] Upgraded | CVE-2026-5678 | axios@1.0.0 | High -> Medium |
| [v] Downgraded | CVE-2026-4321 | react@18.0.0 | Medium -> Low |
### Evidence
| Field | Value |
| --- | --- |
| Attestation Digest | sha256:abc123... |
| Policy Verdict | FAIL |
| Verify Command | `stella verify image <ref> --strict` |
---
*[OK] 12 findings unchanged | Policy: production v2.1.0*
ASCII Indicator Reference:
| Indicator | Meaning |
|---|---|
[OK] | Pass / Success |
[BLOCKING] | Fail / Hard gate triggered |
[WARNING] | Soft gate / Advisory |
[+] | New finding introduced |
[-] | Finding fixed / removed |
[^] | Severity upgraded |
[v] | Severity downgraded |
Offline Mode
In air-gapped environments:
- Comments are queued locally
- Export comment payload for manual posting
- Generate markdown file for offline review
The DSSE envelope shape and verification details belong to the Attestor module; the example envelope below illustrates the stored signed result that
stella attest verify/stella verify attestationvalidate.
Attestation is stored as a DSSE envelope:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEi...",
"signatures": [
{
"keyid": "sha256:abc123...",
"sig": "MEQCI..."
}
]
}
Gate Behaviors
Soft Gate (Warning Only)
# .stellaops.yaml
gates:
ci:
mode: soft # Report but don't fail
notify:
- slack://security-channel
Hard Gate (Blocking)
gates:
ci:
mode: hard # Fail pipeline on violations
exceptions:
- CVE-2024-9999 # Known false positive
Progressive Gate
gates:
ci:
mode: progressive
thresholds:
- branch: main
max_critical: 0
max_high: 5
- branch: develop
max_critical: 2
max_high: 20
- branch: feature/*
mode: soft # Warn only on feature branches
Data Contracts
Gate Evaluate Output (gate evaluate --output json)
This is the authoritative JSON the gate step parses in CI (the exitCode field drives the pipeline). Shape from GateEvaluateResponse in GateCommandGroup.cs:
interface GateEvaluateResponse {
decisionId: string;
status: 'Pass' | 'Warn' | 'Fail';
exitCode: number; // 0 = Pass, 1 = Warn, 2 = Fail
imageDigest: string;
baselineRef?: string;
decidedAt: string; // ISO-8601
summary?: string;
advisory?: string;
gates?: Array<{
name: string;
result: string; // pass | warn | fail | block
reason: string;
note?: string;
condition?: string;
}>;
blockedBy?: string;
blockReason?: string;
suggestion?: string;
overrideApplied: boolean;
deltaSummary?: { added: number; removed: number; unchanged: number };
}
CLI Scan Output Schema (illustrative)
The schema below is an illustrative shape for scan output; it does not correspond to a single CLI command’s contract (the
scangroup emits SARIF/JSON/SBOM via subcommands, not one fixed envelope). TreatGateEvaluateResponseabove as the load-bearing CI contract.
interface CliScanOutput {
scan_id: string;
image: string;
digest: string;
verdict: 'PASS' | 'WARN' | 'FAIL';
confidence: number;
summary: {
critical: number;
high: number;
medium: number;
low: number;
};
violations: Array<{
cve: string;
severity: string;
package: string;
rule: string;
message: string;
}>;
attestation?: {
digest: string;
rekor_log_index?: number;
};
timing: {
queued_ms: number;
scan_ms: number;
policy_ms: number;
total_ms: number;
};
}
Error Handling
Error/exit codes follow each command’s own scheme (see §5 for gate/guard, §5a for proof). For the gate/guard groups, common conditions are:
| Error | Exit Code | Recovery |
|---|---|---|
| Input error (invalid parameters) | 10 | Check arguments (e.g. --image digest, --justification when overriding) |
| Network error (gate/scanner unreachable) | 11 | Check connectivity / backend URL; retry with --timeout |
| Policy / analysis error | 12 | Inspect policy/baseline; check service logs |
| Unknown error | 99 | Re-run with --verbose; inspect logs |
CORRECTED: there is no uniform “exit 2 for everything” scheme.
gate/guarduse10/11/12/99for error classes (success/warn/fail are0/1/2);proof verifyuses the0–8scheme in §5a.
Observability
Metrics
The CLI emits OpenTelemetry metrics under the stellaops.cli.* namespace (CliMetrics.cs). Relevant counters/histograms:
| Metric | Type | Notes |
|---|---|---|
stellaops.cli.scan.run.count | Counter | scan run invocations |
stellaops.cli.command.duration.ms | Histogram | Per-command duration (tagged with command name) |
stellaops.cli.attest.verify.count | Counter | attest verify invocations |
stellaops.cli.scanner.download.count | Counter | Scanner-bundle downloads |
CORRECTED: the previously listed
cli_scan_total,cli_scan_duration_seconds, andcli_gate_blocked_totalare not emitted by the CLI; the canonical names use thestellaops.cli.prefix.
CI/CD Annotations
GitHub Actions annotations:
::error file=package-lock.json::CVE-2024-1234: Critical vulnerability in lodash@4.17.20
::warning file=Dockerfile::Using outdated base image
Related Flows
- Scan Submission Flow - Underlying scan mechanics
- Policy Evaluation Flow - Policy rule details
- Binary Delta Attestation Flow - Attestation details
