CLI Attest Guide

Audience: engineers and operators who sign, verify, and inspect Stella Ops attestations from the command line.

This guide shows how to use the Stella Ops CLI to verify DSSE envelopes, create timestamped attestations, work with RFC-3161 timestamp tokens, build FixChain envelopes, and run fully offline verification in air-gapped environments. It closes with ready-to-use CI/CD snippets for GitHub Actions and GitLab CI. For sealed-mode behaviour and bundle import, pair it with the CLI air-gap guide.

Verify DSSE

stella attest verify --envelope bundle.dsse.json --policy policy.json \
  --root keys/root.pem --transparency-checkpoint checkpoints/rekor.json

Timestamped attestations

Create a DSSE envelope and request RFC-3161 timestamping:

stella attest sign \
  --predicate ./predicate.json \
  --predicate-type https://slsa.dev/provenance/v1 \
  --subject oci://registry/app@sha256:abc123 \
  --digest sha256:abc123 \
  --key ./keys/signing.pem \
  --timestamp \
  --tsa https://tsa.example \
  --output attestation.dsse.json

Request and inspect standalone timestamp tokens:

stella ts rfc3161 --hash sha256:abc123 --tsa https://tsa.example --out artifact.tst
stella ts info --tst artifact.tst
stella ts verify --tst artifact.tst --artifact ./artifact.bin --trust-root ./roots.pem

Store timestamp evidence alongside an attestation:

stella evidence store --artifact attestation.dsse.json \
  --tst artifact.tst --rekor-bundle rekor.json \
  --tsa-chain tsa-chain.pem --ocsp ocsp.der --crl crl.der

Evidence is stored under ~/.stellaops/evidence-store/sha256_<digest>/ by default (the colon in the digest is replaced with an underscore).

Attach and sign existing DSSE envelopes

Attach a pre-built DSSE envelope to an OCI artifact:

stella attest attach \
  --image registry.example.com/app@sha256:abc123 \
  --attestation attestation.dsse.json

Append a local ES256 software signature before the attach:

stella attest attach \
  --image registry.example.com/app@sha256:abc123 \
  --attestation attestation.dsse.json \
  --sign \
  --key ./keys/operator-p256.pem \
  --key-id operator-key-2026-06

The CLI signs the DSSE pre-authentication encoding over the existing envelope payload. For unsigned input it creates the first signature; for signed input it appends another signature. It does not rewrite or canonicalize the payload. --sign requires --key <pem>; --sign-keyless is currently an explicit unsupported path for this command. When combined with --offline, the local bundle stores the signed envelope.

Registry references default to HTTPS. An insecure operator-controlled lab registry must be explicit, for example --image http://127.1.1.5/lab/app@sha256:...; the CLI preserves that scheme for attach, list, fetch, verify, and remove operations. attest oci-verify --key accepts PEM or DER SPKI public keys. For a direct PEM file, its filename stem is the trust key id and must match the DSSE signature’s keyid.

Timestamp requirements during verify

Require RFC-3161 evidence and enforce skew:

stella attest verify --envelope attestation.dsse.json \
  --require-timestamp --max-skew 5m --format json

The JSON output includes a timestamp block:

{
  "timestamp": {
    "required": true,
    "maxSkew": "00:05:00",
    "present": true,
    "generationTime": "2026-01-19T12:00:00Z",
    "tsaUrl": "https://tsa.example",
    "tokenDigest": "sha256:...",
    "withinSkew": true
  }
}

--max-skew accepts relative durations (5m, 30s, 2h) or hh:mm:ss.

List attestations

stella attest list --tenant default --issuer dev-kms --format table

Show attestation

stella attest show --id a1b2c3 --output json

FixChain attestations

Create a FixChain envelope only when the analyzer source identity is known. Prefer BinaryIndex-owned local analyzer metadata when your deployment publishes it with a trusted digest pin:

stella attest fixchain \
  --sbom sbom.cdx.json \
  --diff patch-diff.json \
  --golden CVE-2026-0001.golden.json \
  --out fixchain.dsse.json \
  --no-rekor \
  --analyzer-metadata binaryindex-analyzer-metadata.json \
  --analyzer-metadata-digest sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

--analyzer-metadata-digest is the SHA-256 of the exact metadata file bytes. The CLI accepts the metadata only when the digest pin matches, then copies the metadata sourceDigest into the FixChain predicate. Tampered metadata, missing digest pins, malformed sourceDigest, or disagreement with an explicit source digest all fail closed and write no envelope.

If trusted metadata is not available, supply the analyzer source digest directly:

stella attest fixchain \
  --sbom sbom.cdx.json \
  --diff patch-diff.json \
  --golden CVE-2026-0001.golden.json \
  --out fixchain.dsse.json \
  --no-rekor \
  --analyzer-source-digest sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
  --analyzer-name StellaOps.BinaryIndex \
  --analyzer-version 2.1.0

--analyzer-source-digest must be the real sha256:<64 hex> digest for the BinaryIndex/analyzer source that produced the diff evidence. The manual fallback also requires the real analyzer name and version; the command must not invent StellaOps.BinaryIndex or a version when trusted metadata is absent. It fails closed and writes no envelope when neither trusted metadata nor a valid explicit source digest plus analyzer identity is available; it must not emit sha256:unknown.


Verify Offline (Air-Gapped Environments)

Verify attestation bundles completely offline without network access.

Synopsis

stella attest verify-offline --bundle <path.tar.gz> [options]

Options

OptionAliasDescription
--bundle <path>-bRequired. Path to attestation bundle (tar.gz).
--checkpoint <path>-cPath to Rekor checkpoint signature file.
--trust-root <dir>-rPath to trust root directory containing CA certificates.
--artifact <digest>-aExpected artifact digest (sha256:…) to verify against.
--predicate-type <type>-pExpected predicate type (e.g., https://slsa.dev/provenance/v1).
--output <file>-oWrite verification report to file instead of stdout.
--format <fmt>-fOutput format: json, summary (default), or html.
--strictFail if any optional verification step fails.
--verboseShow detailed verification progress.

Verification Checks

The command performs the following verification checks:

  1. DSSE Envelope Signature: Validates the DSSE envelope structure and signatures.
  2. Merkle Inclusion Proof: Verifies Rekor transparency log inclusion proof.
  3. Checkpoint Signature: Validates checkpoint signature against trusted keys.
  4. Content Hash: Ensures all file hashes match the manifest.

Exit Codes

CodeMeaning
0Verification passed
1Verification failed (one or more checks failed)
2Error (file not found, parse error, etc.)

Examples

# Basic offline verification
stella attest verify-offline --bundle evidence.tar.gz

# Full verification with all options
stella attest verify-offline \
  --bundle evidence.tar.gz \
  --checkpoint checkpoint.sig \
  --trust-root /path/to/roots/ \
  --artifact sha256:abc123def456 \
  --predicate-type https://slsa.dev/provenance/v1

# Generate JSON verification report
stella attest verify-offline \
  --bundle evidence.tar.gz \
  --format json \
  --output report.json

# Strict mode (fail on optional check failures)
stella attest verify-offline --bundle evidence.tar.gz --strict

Sample Output

Attestation Verification Report
================================
Bundle: evidence.tar.gz
Status: VERIFIED

Checks:
  [PASS] DSSE envelope signature valid
  [PASS] Merkle inclusion proof verified (log index: 12345)
  [PASS] Checkpoint signature valid (origin: rekor.sigstore.dev)
  [PASS] Content hash matches manifest

Artifact: sha256:abc123...
Signed by: identity@example.com
Timestamp: 2026-01-14T10:30:00Z

Bundle Format

The attestation bundle should be a tar.gz archive containing:

evidence.tar.gz
├── attestation.dsse.json    # DSSE envelope with signature
├── manifest.json            # File inventory with SHA-256 hashes
├── metadata.json            # Generation timestamp, tool versions
├── certs/
│   ├── signing-cert.pem     # Signing certificate
│   └── fulcio-root.pem      # Fulcio root CA (optional)
└── rekor-proof/             # Transparency log proof (optional)
    ├── inclusion-proof.json
    └── checkpoint.sig

Air-Gap Workflow

  1. Export bundle on connected system:

    stella evidence export --scan-id <id> --output bundle.tar.gz
    
  2. Transfer bundle to air-gapped system via secure media.

  3. Verify offline on air-gapped system:

    stella attest verify-offline --bundle bundle.tar.gz --trust-root /roots/
    

Cross-Platform Determinism

The verification output is deterministic across platforms:

CI/CD Integration

GitHub Actions

# .github/workflows/verify-attestation.yml
name: Verify Attestation

on:
  workflow_dispatch:
    inputs:
      artifact_path:
        description: 'Path to artifact with attestation'
        required: true

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Download artifact
        uses: actions/download-artifact@v4
        with:
          name: signed-artifact
          path: ./artifacts

      - name: Install StellaOps CLI
        run: |
          dotnet tool install --global StellaOps.Cli

      - name: Verify attestation
        run: |
          stella attest verify \
            --envelope ./artifacts/attestation.dsse.json \
            --policy ./policy/verify-policy.json \
            --root ./keys/trusted-root.pem \
            --output ./verification-report.json

      - name: Upload verification report
        uses: actions/upload-artifact@v4
        with:
          name: verification-report
          path: ./verification-report.json

GitLab CI

# .gitlab-ci.yml
verify-attestation:
  stage: verify
  image: mcr.microsoft.com/dotnet/sdk:10.0
  before_script:
    - dotnet tool install --global StellaOps.Cli
    - export PATH="$PATH:$HOME/.dotnet/tools"
  script:
    - |
      stella attest verify \
        --envelope ./artifacts/attestation.dsse.json \
        --policy ./policy/verify-policy.json \
        --root ./keys/trusted-root.pem \
        --output ./verification-report.json
  artifacts:
    paths:
      - verification-report.json
    expire_in: 1 week
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Notes