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
- Offline verification uses bundled roots and checkpoints; transparency optional.
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
| Option | Alias | Description |
|---|---|---|
--bundle <path> | -b | Required. Path to attestation bundle (tar.gz). |
--checkpoint <path> | -c | Path to Rekor checkpoint signature file. |
--trust-root <dir> | -r | Path to trust root directory containing CA certificates. |
--artifact <digest> | -a | Expected artifact digest (sha256:…) to verify against. |
--predicate-type <type> | -p | Expected predicate type (e.g., https://slsa.dev/provenance/v1). |
--output <file> | -o | Write verification report to file instead of stdout. |
--format <fmt> | -f | Output format: json, summary (default), or html. |
--strict | Fail if any optional verification step fails. | |
--verbose | Show detailed verification progress. |
Verification Checks
The command performs the following verification checks:
- DSSE Envelope Signature: Validates the DSSE envelope structure and signatures.
- Merkle Inclusion Proof: Verifies Rekor transparency log inclusion proof.
- Checkpoint Signature: Validates checkpoint signature against trusted keys.
- Content Hash: Ensures all file hashes match the manifest.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Verification passed |
| 1 | Verification failed (one or more checks failed) |
| 2 | Error (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
Export bundle on connected system:
stella evidence export --scan-id <id> --output bundle.tar.gzTransfer bundle to air-gapped system via secure media.
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:
- Line endings normalized to LF
- Hex digests always lowercase
- Timestamps in ISO 8601 UTC format
- Paths use forward slashes
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
- No network access required in sealed mode.
- All commands emit deterministic JSON; timestamps in UTC.
- Exit codes: 0 success, 2 verification failed, 4 input error.
