CLI Air-Gap Guide
Audience: operators running the Stella Ops CLI in offline or air-gapped (sealed-mode) environments.
This guide covers the offline usage patterns for the Stella Ops CLI: importing and verifying offline kits and mirror bundles, checking sealed-mode status, and the determinism rules every command must obey when no network egress is permitted. For the platform-level policy behind these commands, see the air-gap overview and sealing and egress.
Offline kit commands
- Import an offline kit (local verification + activation)
stella offline import \ --bundle ./bundle-2025-12-14.tar.zst \ --verify-dsse \ --verify-rekor \ --trust-root /evidence/keys/roots/stella-root.pub - Check current offline kit status
stella offline status --output table
Prerequisites
- CLI installed from offline bundle;
local-nugets/and cached plugins available. - Mirror/Bootstrap bundles staged locally; no external network required.
- Set
STELLA_OFFLINE=trueto prevent outbound fetches.
Common commands
The stella airgap group exposes import, seal, unseal, status, diff, time-anchor and export-evidence. Two verbs this guide previously listed — verify-bundle and list — were never registered on the command group and are removed here rather than left as commands that cannot resolve.
- Import a mirror bundle into the local data store
Imported mirror-bundle metadata is written durably understella airgap import --bundle /mnt/media/mirror.tar --verify-only%LocalApplicationData%/stellaops/offline-kit/state/mirror-bundles, so the import history survives fresh CLI processes instead of relying on process-local memory. The verb accepts--tenant,--global,--dry-run,--force,--verify-onlyand--json; there is no--generationoption. - Report the measured air-gap posture
See Air-gap posture below for what the three answers mean.stella airgap status --scope installation --output json - Compare two OfflineKit envelope manifests
See Comparing manifests below.stella airgap diff ./manifest-base.json ./manifest-target.json --output json
Air-gap posture
stella airgap status reads Platform’s environment-state custodian (GET /api/v1/platform/environment-state/airgap-seal/{scope}) and interprets the airgap-seal document with the family’s standard interpreter. It reports one of three answers, and they are deliberately distinguishable:
| Answer | Exit | Meaning |
|---|---|---|
declared | 0 | A document exists; isSealed is a measured value. |
not-declared | 0 | Nobody has declared posture for this scope. This is not “unsealed”. |
unreadable | non-zero | The posture could not be established. An error, never a value. |
Do not treat an environment as sealed or unsealed on the strength of a not-declared or unreadable answer. stella airgap seal is the audited writer of that document, and the Console’s air-gap indicator reads the same one.
Time-anchor status
stella airgap time-anchor status --scope installation --trust-roots ./tsa-roots.pem --output json
With --trust-roots, status binds that public certificate bundle to the central timestamp-authority trust purpose and uses the same stored-proof verifier as the enforcement consumers. The RFC3161 signature and custom-root chain must verify, and the recorded anchor time, token digest and signer fingerprint must agree with the proof. A wrong explicit format is refused; omitted format keeps the existing schema default of RFC3161, which strict token decoding independently checks. Certificate revocation is not established by this verification.
A failed requested verification returns exit 1, availability: "Unreadable", staleness: "Unknown", anchorTime: null, verified: false and a failure reason. It does not retain the raw document’s freshness result. Successful verification reports verificationRequested: true and verified: true; the staleness grade then describes the verified anchor against its configured budget.
Without --trust-roots, status preserves its read-only reporting mode: it shows the recorded document and its calculated age without cryptographic verification. JSON reports verificationRequested: false, verified: false and reverification: null; human output says it was not re-verified. These recorded values are not verification evidence. An absent scope remains NotDeclared with unknown staleness and exit 0; an unreadable custodian result exits 1. An absent scope also remains a measured absence when trust roots were supplied, with verified: false because there was no proof to check.
See Timestamp public trust for the central purpose boundary and offline verification limits.
Comparing manifests (stella airgap diff)
stella airgap diff <base> <target> compares two OfflineKit envelope manifests — the JSON that GET /api/offlinekit/v1/manifest returns and that POST /api/offlinekit/v1/validate accepts. Both sides are read from local files, so the command needs no network.
# Human-readable inventory delta
stella airgap diff ./manifest-2026-02.json ./manifest-2026-03.json
# Machine-readable, with an explicit `identical` flag
stella airgap diff ./manifest-2026-02.json ./manifest-2026-03.json --output json
# Restrict the artifact comparison to one declared domain
stella airgap diff ./base.json ./target.json --component scanner
What it reports: changed bundle header fields (version, channel, kind, bundleId, baseBundleId, isDelta, capturedAt, releaseManifestSha256), and per artifact whether it was added, removed or changed (sha256, size, domain, mediaType, generation), plus an unchanged count. Ordering is ordinal by artifact name, so two runs over the same inputs produce identical output.
--component filters artifacts by their declared domain (for example scanner); all compares every domain. Bundle-header changes are always reported, because a filtered view that hid a channel or delta-base change would hide the reason the artifact set moved.
What it deliberately does not do. diff does not verify signatures, does not check DSSE custody and does not hash any asset. Validation belongs to the owner (POST /api/offlinekit/v1/validate); a clean diff says two inventories match, not that either bundle is trustworthy.
Exit codes for this verb:
| Exit | Meaning |
|---|---|
0 | Both manifests were read and compared, whether or not they differ. |
| non-zero | The comparison could not be performed. Nothing was compared. |
A difference is a result, not a failure. A comparison that never happened is a failure, and it must never be readable as “no differences” — which is why a missing file, malformed JSON, a duplicate artifact name or a foreign manifest lineage all exit non-zero and print no diff. Use the identical field in --output json to gate a script on sameness.
diff refuses the other manifest lineages in StellaOps.AirGap.Bundle by name. That refusal is not pedantry: BundleManifest and BundleManifestV2 both declare schemaVersion "2.0.0" with disjoint shapes, so the field an implementer would dispatch on first cannot tell them apart.
Determinism & offline rules
- Commands must succeed without egress; any outbound attempt is a bug — report it with logs.
- Hashes and signatures are verified locally using bundled trust roots; no OCSP/CRL.
- Outputs are stable JSON/NDJSON; timestamps use UTC.
- Mirror-bundle import metadata is persisted locally with deterministic ordinal ordering for content paths and bundle items.
Exit codes
Exit codes are per verb, not global. The commands documented above follow their handlers:
stella airgap status—0measured (declared or not-declared), non-zero unreadable.stella airgap diff—0compared, non-zero could not compare.stella airgap time-anchor status—0readable (or absent),1unreadable or failed requested proof verification. Checkverifiedto distinguish a successful cryptographic check from the no-trust reporting mode.
For any other verb, read the code the command itself returns rather than assuming a shared scale; this guide does not assert one it has not verified.
Logs
- Default stderr structured JSON: includes
tenant,bundleId,mirrorGeneration,sealedflag. - For audits, use
--log-file /var/log/stella/airgap.log --log-format json.
Tips
- Keep bundles on read-only media to avoid hash drift.
- Use
--dry-runonstella airgap importto validate without writing. stella airgap diffreads files only, so it is safe to run inside a sealed estate against media that has not been imported yet.
See also
- Air-gap overview — sealed-mode concepts and posture.
- Sealing and egress — egress-policy context for these commands.
- CLI attest guide — offline attestation verification.
