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

ActorTypeRole
CI PipelineSystemGitHub Actions, GitLab CI, Gitea Actions (template generator); other CI can invoke the CLI directly
StellaOps CLI (stella)ToolRuns scan/gate/verify from the pipeline
GatewayServiceAPI entry point
ScannerServicePerforms image analysis
Policy EngineServiceEvaluates the release gate (api/v1/policy/gate/evaluate)
AttestorServiceSigns scan results (DSSE)

Prerequisites

Source of truth: the CLI command tree is assembled in src/Cli/StellaOps.Cli/Commands/CommandFactory.cs. The CI/CD-facing verbs below are scan, gate, ci, guard, proof, and verify.

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.

PlatformTemplate generator (stella ci init --platform)Credentials
GitHub ActionsgithubOIDC (stellaops/auth@v1) or token
GitLab CIgitlabCI_JOB_TOKEN or token
Gitea ActionsgiteaToken

NOT IMPLEMENTED (no ci init template): Azure DevOps, Jenkins, CircleCI, Tekton. stella ci init accepts only github, gitlab, gitea, or all (see CiCommandGroup.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). Run stella ci init --platform github --template gate to scaffold the file into .github/workflows/stellaops-gate.yml. The CLI binary is stella.

Caveat (see §3): several commands the generated templates emit do not match the implemented CLI surface.

  1. The templates call stella scan image <ref> --format ... --output ... --sbom-output ..., but the implemented stella scan image command is an analysis group (inspect/layers), not a scan-and-emit verb (see §3).
  2. The scan/gitlab templates call stella sbom upload <file> --image ..., but the sbom group (SbomCommandGroup.cs) has no upload subcommand; the closest implemented command is stella sbom publish --image <ref> --file <sbom> (publishes a canonical SBOM as an OCI referrer).
  3. The real generated GitHub gate template also includes a “Gate Summary” step that runs stella gate evaluate ... --output markdown; gate evaluate --output only accepts table, json, and exit-code-only (GateCommandGroup.cs), so markdown falls through to the default table renderer.
  4. The generated verify templates call stella 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-on flag on scan. The scan step emits SARIF; the gate step calls the Policy Engine and surfaces the verdict via its JSON exitCode field. 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):

# 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
CommandDescription
stella auth loginAcquire and cache an access token
stella auth logoutRemove cached tokens for the current credentials
stella auth statusDisplay cached token status
stella auth whoamiDisplay cached token claims (subject, scopes, expiry)

CORRECTED: there are no --token, --oidc, or --keyless flags on stella auth login. The only flags are --force and --no-save. The OIDC token exchange in GitHub Actions is performed by the stellaops/auth@v1 action, 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 to auth login.

3. Scan Execution

CORRECTED: there is no flat stellaops scan <image> --policy ... --fail-on violation command. The scan command is a group (CommandFactory.BuildScanCommand) whose subcommands include run, upload, sarif, entrytrace, replay, layers, layer-sbom, recipe, diff, delta, verify-patches, download, workers, secrets, image, and submit-list. In CI the scan step emits results (SARIF/JSON/SBOM); the pass/fail decision is a separate gate evaluate step.

CORRECTED — stella scan image <ref> --format ... --output ... is NOT an implemented scanning verb. The stella scan image command (CommandFactory.BuildScanImageCommand, “moved from stella image”) is an image-analysis group whose only subcommands are inspect and layers; both currently print placeholder/sample output and neither performs a scan nor accepts --sbom-output, --format, or --output. The stella scan image <ref> --format sarif --output ... / --sbom-output ... forms that appear in this doc’s pipeline examples come verbatim from the templates emitted by stella 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 implemented scan group. The real scanning entrypoints today are stella scan run (runner bundle) and backend submission (stella scan submit-list, stella scan upload). Treat the scan 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

SubcommandPurpose
stella scan runExecute a scanner bundle with the configured runner (--runner dotnet|self|docker, --entry, --target). This is the implemented scanning verb.
stella scan submit-listSubmit 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 replayDeterministic verdict replay from explicit input hashes (--artifact, --manifest, --feeds, --policy)
stella scan image inspect|layersImage-analysis helpers (metadata / layer listing). Placeholder output today; NOT a scan-and-emit verb.

The scan sarif output-format/destination flags are --output/-o (file, defaults to stdout) with --min-severity (none, note, warning, error). scan run forwards scanner-args to the runner rather than taking a --format/--output pair. Policy evaluation and the deploy gate are applied by stella gate evaluate (below), which takes the policy/baseline — not by the scan step.

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):

FlagPurpose
--directory/-dLocal directory to scan (mutually exclusive with --image/-i).
--format/-fcyclonedx, spdx, or both.
--output/-oOutput file path (or directory when --format both).
--source-dateDeterministic SBOM timestamp (Unix epoch or ISO-8601). For --directory this is the only timestamp source; defaults to SOURCE_DATE_EPOCH, else 1970-01-01.
--forceOverwrite an existing output file.
--show-hashPrint 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:

VerdictExit Code
Pass0
Pass with warnings1
Fail (blocking findings)2
Input error10
Network error11
Analysis error12
Unknown error99

So a blocking verdict exits 2, not 1; warnings exit 1.

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
OptionDescription
--image / -iImage digest to evaluate (e.g. sha256:...). Required.
--baseline / -bBaseline reference for comparison (snapshot ID, digest, or last-approved)
--policy / -pPolicy ID to use for evaluation
--allow-overrideAllow override of blocking gates (requires --justification)
--justification / -jJustification for override
--branch, --commit, --pipeline, --envGit/CI context recorded with the decision
--output / -otable (default), json, or exit-code-only
--timeoutRequest 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 / -pgithub, gitlab, gitea, all (required)
--template / -tgate (default), scan, verify, full
--mode / -mscan-only, scan-attest (default), scan-vex
--output / -oOutput directory (default: current directory)
--offlineGenerate offline-friendly bundle with pinned digests
--scanner-imageOverride the scanner image reference
--force / -fOverwrite 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):

VerdictExit CodePipeline Result
PASS0Continue
WARN1Continue or block (pipeline decides; the code is non-zero)
FAIL2Block deployment
Input error10Invalid parameters
Network error11Unable to reach gate/scanner service
Policy/analysis error12Evaluation failed
Unknown error99Unexpected failure

CORRECTED: a FAIL verdict exits 2 (not 1) and a WARN verdict exits 1 (not 0). There is no --fail-on flag controlling this. The proof verify command uses a different exit-code scheme (see §5a). Pipelines branch on the non-zero exit code (or parse the JSON exitCode field), 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:

CORRECTED: there is no stella proof verify --image ... --attestation-type ... --check-rekor --fail-on-missing form, and no --ledger-path. Those flags do not exist. proof verify operates on a --bundle file. Image-level attestation verification is stella verify image. There is no stella evidence-pack verify; the evidence-pack verb is an export subcommand (stella export evidence-pack) — bundle verification is stella proof verify or stella 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-policy configured, verify image now fails closed with an explicit No trust policy configured message instead of silently passing — signer trust cannot be enforced without a policy. Pass --allow-unsigned to 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 verify workflow templates (stella ci init --template verify, CiTemplates.cs) call stella verify image <ref> --require-sbom --require-scan --require-signature. Those split --require-* boolean flags do not exist on verify image; the implemented flag is a single repeatable --require/-r that takes attestation-type values (sbom vex decision, plus approval). Use the --require sbom vex decision form 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 verify actually returns come from AttestationBundleExitCodes(Services/Models/AttestationBundleModels.cs), surfaced via result.ExitCode in ProofCommandGroup.HandleVerifyAsync. The ProofExitCodes type (Proof/ProofExitCodes.cs) defines the advisory §15.2 scheme but the verify handler only uses it for the catch-all SystemError (exception path); a missing bundle returns AttestationBundleExitCodes.FileNotFound (6), not ProofExitCodes.KeyRevoked. The two schemes diverge at codes 2–8, so trust the runtime (AttestationBundleExitCodes) table below, not the advisory ProofExitCodes numbering.

stella proof verify runtime exit codes (AttestationBundleExitCodes):

CodeMeaning
0Success — bundle verified
1General verification failure
2Checksum mismatch (bundle integrity)
3Signature failure (DSSE envelope)
4Missing transparency / Rekor inclusion (online mode)
5Format error (malformed bundle)
6Bundle file not found
7Import failed

For reference, the advisory ProofExitCodes scheme (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 for proof verify branching 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-attestation examples previously shown here are upstream Sigstore commands, not StellaOps CLI commands, and the --type https://stellaops.io/attestation/... predicate URIs were illustrative. Use stella verify image / stella proof verify for 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 attestation command and no attestation show --scan verb. DSSE attestations are inspected and verified through the attest group (AttestCommandGroup, wired via CommandFactory.BuildAttestCommand) and the unified verify group (VerifyCommandGroup).

The live root stella attest verify (CommandFactory.BuildAttestCommandHandleAttestVerifyAsync) 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 Verification step 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 --root file 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 — use stella attest verify-offline for 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 github command group and the GitHub/GitLab SCM annotation clients) is built but not wired: no IGitHubCodeScanningClient is DI-registered, so every stella github * verb now exits with a clean not available in this release error (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 own github/codeql-action/upload-sarif action (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 github group does NOT update commit status, and there is no commit-status command. Its only five subcommands (GitHubCommandGroup.cs) are upload-sarif, list-alerts, get-alert, update-alert, and upload-status — and upload-status checks 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:

CommandPurpose
stella github upload-sarif <file> --repo owner/repoUpload SARIF to GitHub Code Scanning (--ref, --sha, --wait/-w, --timeout/-t, --tool-name, --github-url)
stella github list-alerts --repo owner/repoList code-scanning alerts
stella github get-alert --repo owner/repoInspect a single alert
stella github update-alert --repo owner/repoUpdate a code-scanning alert state (dismiss/resolve)
stella github upload-status <sarif-id> --repo owner/repoCheck 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 CLI TASKS.md backlog). 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.

OptionDescriptionDefault
--pr-comment / --mr-commentPost summary commentfalse
--check-runCreate GitHub check run with annotationsfalse
--commit-statusUpdate commit statusfalse
--update-existingEdit previous comment instead of newtrue
--collapse-detailsUse collapsible sections for long outputtrue
--evidence-linkInclude link to evidence bundletrue

Evidence Anchoring in Comments

Comments include evidence references for auditability:

Error Handling

ScenarioBehavior
No SCM tokenSkip comment, log warning
API rate limitRetry with backoff, then skip
Comment too longTruncate with link to full report
PR already mergedSkip 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:

IndicatorMeaning
[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:

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 attestation validate.

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 scan group emits SARIF/JSON/SBOM via subcommands, not one fixed envelope). Treat GateEvaluateResponse above 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:

ErrorExit CodeRecovery
Input error (invalid parameters)10Check arguments (e.g. --image digest, --justification when overriding)
Network error (gate/scanner unreachable)11Check connectivity / backend URL; retry with --timeout
Policy / analysis error12Inspect policy/baseline; check service logs
Unknown error99Re-run with --verbose; inspect logs

CORRECTED: there is no uniform “exit 2 for everything” scheme. gate/guard use 10/11/12/99 for error classes (success/warn/fail are 0/1/2); proof verify uses the 0–8 scheme in §5a.

Observability

Metrics

The CLI emits OpenTelemetry metrics under the stellaops.cli.* namespace (CliMetrics.cs). Relevant counters/histograms:

MetricTypeNotes
stellaops.cli.scan.run.countCounterscan run invocations
stellaops.cli.command.duration.msHistogramPer-command duration (tagged with command name)
stellaops.cli.attest.verify.countCounterattest verify invocations
stellaops.cli.scanner.download.countCounterScanner-bundle downloads

CORRECTED: the previously listed cli_scan_total, cli_scan_duration_seconds, and cli_gate_blocked_total are not emitted by the CLI; the canonical names use the stellaops.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