On this page

Credential operations

Credential operations is the single Stado surface for inspecting and changing the Skarbiec vault held by a fleet host. The host is always named with --host; credential values remain on stdin or in an owner-only file and never enter a local or remote process argument vector.

Invocations

  • stado credentials item put --host <TARGET> --role <ROLE> --type <KIND> [--json] reads the canonical JSON item from stdin and stores it as the item playing ROLE: the one item tagged stado:role:<ROLE> is rotated, or a new item is created under a random id with that tag. Two items in the role are refused. Skarbiec's set-json answer has to say whether it created the item; an answer that is not JSON or carries no boolean created (a Skarbiec older than set-json --if-absent) is refused as <host>: Skarbiec set-json for <item> did not say whether it created the item (<why>); it printed: <its answer>, before the write is checked against the vault.
  • stado credentials item show --host <TARGET> <ITEM> [--field <FIELD>] [--json] reports metadata, length and SHA-256, never field values, and the item's context descriptors as context: <name>=<value> lines ("context" in JSON): login_method, account_ref, provider, product, role and whatever else the writer declared. A nested context entry is reported as <name> (structured), without its contents. A host whose Stado predates this reports context: not reported: <its stado> on <host> predates context reporting. This is where a login row that Weles refuses with declares unsupported login_method (absent) shows what it lacks.
  • stado credentials item retag --host <TARGET> <ITEM> [--tags <COMMA-SEPARATED-TAGS>] [--json]; omitting --tags is read-only. A tag is letters, digits, ., _, -, :, @ or +: the address in brama:account:<address>, which Brama writes beside brama:login:<login item> on a subscription item, is accepted as it is, so correcting the login reference lists the account tag back unchanged instead of dropping it. Any other byte is refused as tag must contain only letters, digits, '.', '_', '-', ':', '@' or '+'.
  • Before retag or stado credentials item rename changes anything, the host's Skarbiec binary is read for the command's usage line. A build without it is refused as <host>: <item> could not be retagged: the Skarbiec build at <binary> predates the retag operation (... renamed ... predates the rename operation). A binary that cannot be read at all, because it is not there or the host has no strings, is refused as <host>: the Skarbiec build at <binary> could not be read for its commands: <the words strings gave>, never as an old build.
  • stado credentials item delete --host <TARGET> <ITEM> [--json] deletes one owner-controlled item of a retired product or role with Skarbiec's own delete on the host whose owner key can write it, printing <host>: deleted <ITEM> ("status": "deleted" with Skarbiec's answer in JSON); Skarbiec refuses an item a credential lifecycle or Weles controls and keeps the deletion restorable. stado credentials item restore --host <TARGET> <ITEM> [--json] undoes it with Skarbiec's restore, printing <host>: restored <ITEM>, so a step that still needs the item, such as stado release catalog withdraw-publisher reading a retired product's publisher bearer, can run before the item is deleted again. Stado Desktop offers both under Hosts → Credentials → Available operations….
  • stado credentials token mint --host <TARGET> <CONSUMER> --capabilities <CAPABILITIES> --audience <AUDIENCE> [--ttl-seconds <SECONDS>] [--replace-capabilities] [--token-item <ITEM> --token-field <FIELD> | --raw-token | --token-file-name <NAME>] [--json].
  • stado credentials token sync <CONSUMER> --from-host <SOURCE> --host <TARGET> --source-token-file <SOURCE-PATH> --token-file <TARGET-PATH> [--check] [--json] delivers an existing bearer only when both declared vaults have the same owner and complete consumer grant.
  • stado credentials vault list [--host <TARGET>] [--json] lists nonsecret vault counts and the declared authority for one host or the fleet; stado credentials vault items [<VAULT> | --host <TARGET>] [--match <TEXT>] [--json] lists one vault's item names, kinds, states and tags, never a value. A host whose Skarbiec refuses skarbiec vaults (a release older than the command, or a vault it cannot read) carries error with that binary's path and its own stderr (<binary> vaults failed: <words>), and counts among the hosts not read; a host with no Skarbiec binary says absent.
  • stado credentials vault sync --host <TARGET> [--check] [--json]; --check reports conflicts and loss without replacing the live vault.
  • stado credentials acquisition-scopes sync --host <TARGET> <SOURCE> [--ttl-seconds <SECONDS>] delivers and registers the checked-in catalog, then settles the roles its rows name. The grants keep the lifetime their current registration has left (the earliest unexpired grant among the catalog's consumers), so registering again never extends them; --ttl-seconds states a lifetime instead, and a catalog with no current registration needs it. stado service ensure registers a service's installed catalog the same way, keeping the current lifetime. After registering, each role:<ROLE> row is checked against the host vault: a role no live item plays, while the live item of the same id that plays no role exists (the item the row named before rows named roles), gives that item the tag stado:role:<ROLE> and keeps its other tags. The answer is one JSON line, {"status":"reconciled","catalog":<name>,"roles":{"held":<n>,"adopted":[…],"unadopted":[{"role","item","reason"}],"unheld":[…],"contested":[…]}}, and every role that stays unreadable is also named on stderr with its next step.
  • stado credentials item upgrade --host <TARGET> [--apply] [--json] runs Skarbiec's idempotent schema pass on the host that owns the vault and prints, among its counts, <host>: items a former owner controlled: <n> would move to the owner (moved to the owner with --apply). Skarbiec 0.4.8 moves items a former owner of the vault (a recipient with owner_until) still controls to the current owner, because after rotate-owner nothing could retag, rotate or delete them; an older Skarbiec answers -.
  • stado credentials grant add --host <TARGET> <CONSUMER> --role <ROLE> --field <FIELD> --token-file <TARGET-PATH> [--json] adds to CONSUMER's grant the field of the item playing ROLE in TARGET's vault, both as read:role:<ROLE>#<FIELD> and as a read of the item playing it now; it names the bearer file on the target and never reads its bytes into argv. It refuses a role no item, or more than one item, plays.
  • stado credentials grant show --host <TARGET> <CONSUMER> [--token-file <TARGET-PATH>] [--json] reports capabilities and whether the named file carries the recorded bearer. A grant row the vault lists without a capabilities array, or a capability without its item or action, is refused with the row the vault answered, never read as a grant holding no capability.
  • stado credentials grant consolidate --host <TARGET> --from <RETIRED-CONSUMER> [--from <ANOTHER> ...] --token-file <ABSOLUTE-STADO-BEARER-PATH> [--json] verifies the existing Stado bearer, unions the named grants into consumer stado, and reads the resulting grant back without copying a bearer to the operator.
  • stado credentials grant rebind --host <TARGET> --token-file <ABSOLUTE-STADO-BEARER-PATH> [--json] binds the stado grant back to the bearer file the fleet holds, with the same capabilities, audience and expiry, when that file no longer opens it; it refuses when the file already opens the grant, refuses a first capability the vault lists without its item or action instead of probing with empty words, and reads the grant back after.
  • stado credentials grant revoke --host <TARGET> <RETIRED-CONSUMER> [--json] revokes a retired consumer only when the stado grant already holds every capability it had; it never revokes stado.
  • stado credentials grant renew [--force] [--json] re-issues this host's own workload-agent grant until revoked at the authoritative vault, with the capabilities it already carries; without --force it does nothing for a grant that already lives until revoked. On the host that holds the owner vault it then renews and binds every consumer grant the service directory declares: one whose service is placed on this host and that has an end, or whose declared bearer file does not open it, is re-issued until revoked against that file with its capabilities and audience (declared grants: kept <consumer> until revoked, with its capabilities and bearer or declared grants: re-bound <consumer>: <file>: …); one whose service is placed elsewhere and that has an end is kept through skarbiec grant keep, and the output says its bearer is checked where it lives. It names each direct grant with an end that no declaration owns, which keeps its end. It exits 1 with the failed step's sentence, including declared grants: renewing or binding the grant failed for <consumer>: <cause> (a refused re-issue, a declared bearer file missing on its own host, or a bearer that still does not open the grant) and a registry or grant listing that cannot be read. In Stado Desktop this is Renew this host's agent grant and keep declared consumer grants under Hosts → Credentials → Available operations….
  • stado credentials signing-profile ensure --provider apple --host <TARGET> <ITEM> --profile <FIELD>=<BUNDLE-ID> [--profile …] --credentials <API-KEY-ITEM> [--type <TYPE>] [--certificate-type <TYPE>] makes or finds a product's code-signing provisioning profiles through App Store Connect with the team API key held in <API-KEY-ITEM> (key_id, issuer_id, private_key_p8_base64) and stores each, base64, as <FIELD> of <ITEM> in TARGET's owner vault, where the product's release manifest reads it. An active profile of the same name is reused, not duplicated; a bundle id the team never registered is refused by name.
  • stado credentials sparkle-key mint <PRODUCT> --info-plist <PATH> [--replace] [--json] generates a desktop product's Sparkle Ed25519 update key in this process, stores it under role <PRODUCT>-sparkle (private_key, public_key) and writes the public half into the app's Info.plist as SUPublicEDKey, so the key never passes a shell or a person. A role that already holds a key is refused without --replace, because copies installed with the old key can never take an update signed by a new one. Stado Desktop offers both under Hosts → Credentials → Available operations….
  • stado config migrate-identities removes retired local identity settings in one validated write after the bearer has been placed; it preserves the prior document.
  • stado credentials backup audit --host <TARGET> [--object <STADO-URI> ... | --inventory-namespace <NAMESPACE> ...] [--reclaim-twins [--apply]] [--json]. The report names why: each same-size pair left same_size_unproven is printed as unproven: <path>: <why its content could not be read> (unproven in --json), and each deletion the host refused under --apply follows the refused: line as <path>: <the unlink error> (reclaim.delete_failures). A class the pass printed no row of reads none. In --json, namespace_inventory.local_storage and local_backup are the root's namespaces, an empty list for a root read and found empty, and null for a root whose inventory never ended. A host whose Stado predates these causes reports the counts without them.
  • stado credentials seed list --host <TARGET> [--login-item <ITEM>] [--json] joins nonsecret TOTP seed state with sign-in evidence.
  • stado credentials seed enrol --host <TARGET> --login-item <ITEM> [--json] asks Weles to enrol an authenticator for that login row and reports the vault's own seed state afterwards.

stado credentials vault show [--json] remains the local-machine vault-authority report.

Declaration

Every host operation first reads the host's effective Stado configuration through the same reader used by host config show. The authority is resolved.skarbiec_vault_file, sourced from secrets.skarbiec.vault_file (or its SKARBIEC_VAULT_FILE configuration override). $HOME/ is expanded against the target account. No conventional vault path is guessed when the declaration is absent.

Host configuration

stado host config show TARGET --json
stado host config set TARGET alerts.email_to '"operator@example.com"'
stado host config unset TARGET alerts.email_to

config show prints readable fields by default and the host's native {file, resolved} document with --json. Set and unset return that document after the write, without mixing the mutation's text acknowledgement into JSON. Their optional --reload-service UNIT reconciles the named managed service only after successful readback. Without that option these configuration operations reconcile no service.

The reader asks the host's own ~/.stado/bin/stado for config show --json. Only a usage refusal with exit status 2 selects the earlier config show grammar. That second command must succeed and return JSON with a nonempty file string and a resolved object. Human text is never parsed into configuration. A transport failure or another nonzero status does not select another grammar. This preserves effective configuration resolution on both supported installed interfaces without guessing values, changing the host binary, or requiring an upgrade before inspection.

A mutation runs exactly once. Its text acknowledgement is kept separate from the configuration read. If the mutation fails, its actual command and status are reported. If it succeeds but the subsequent read fails, the error says the mutation completed with exit 0 and includes its output and the read failure. Stado does not repeat or roll back that write automatically.

Bearer delivery between synchronized copies

Vault synchronization copies the canonical grants, including revocations. It does not copy the raw bearer files that consumers hold. If two hosts previously issued different bearers for the same consumer, copying the vault can therefore leave a consumer with an invalid token file. Do not preserve local grants during a pull or reissue a stale grant to hide that mismatch.

Use the existing bearer on the source host. Check the destination first:

stado credentials token sync stado \
  --from-host SOURCE --host TARGET \
  --source-token-file '~/.stado/stado-skarbiec-token' \
  --token-file '~/.stado/stado-skarbiec-token' \
  --check --json

Remove --check to deliver that bearer into the named destination file. Both hosts must already declare their vaults, and their owner and complete grant for this consumer must match. A different destination grant is a refusal: synchronize the vault from its declared authority first. The operation never writes either vault, renews a grant, changes its capabilities, or recreates a revoked grant.

The source file must be an owner-controlled regular file with no group or other permissions. Both paths must resolve inside their respective host account's home; parent directories must already exist. Symlinks, expired or missing grants, and a source bearer whose hash does not match the grant are refused. A workload-bound grant still needs its own signing identity; this command does not transfer signing keys.

Delivery sends the bearer over Stado's protected host channels in memory, not through arguments or output. It atomically installs an owner-only file and reads it back against the destination grant. token_synced means the file changed; token_unchanged means it already held the bearer and was not rewritten; token_checked means a read-only check matched. changed records the file effect. The receipt names both hosts, resolved paths, consumer and unchanged grant metadata, never bearer bytes.

Each host does its half through its installed Stado, stado credentials token custody OPERATION VAULT CONSUMER FILE, which sync runs over the host channel and an operator never needs to type. OPERATION is one of five declared operations: export at the source reads the consumer's grant and bearer and prints both; at the destination install and check verify the export against the destination's own grant, and install-shared and check-shared against the owner's grant for a destination that reads the source's vault through its resolver route (--shared-vault). The two install operations write the bearer, the two check operations only verify it. Any other word is refused by the parser with the five names listed, before anything is read.

--check exits unsuccessfully for a missing or mismatched destination file and changes nothing. JSON refusals use Stado's normal error envelope and refused code, with the source or destination host and the failed operation. A successful custody check proves the bearer matches the grant, not that a dependent service works: verify the consumer's real request through its declared service route afterwards.

Refusals

  • <target> declares no vault authority; add it to secrets.skarbiec.vault_file
  • <target> declares no credential item <item>; add it to the vault declared by secrets.skarbiec.vault_file
  • <target> declares no grant for <consumer>; add it to the vault declared by secrets.skarbiec.vault_file
  • <target>: the catalog reached <path> and was NOT registered: no grant of this catalog is current in <vault>, so there is no lifetime on record to keep, and Skarbiec requires one: state it with stado credentials acquisition-scopes sync --host <target> <catalog> --ttl-seconds <seconds>
  • <target>: no item plays role <role>, so every read of role:<role> is refused; store it with stado credentials item put --host <target> --role <role> (stderr; the registration still succeeds)
  • <target>: no item plays role <role>, and its former item <item> could not be given the tag stado:role:<role>: <Skarbiec's reason>; until it carries the tag every read of role:<role> is refused (stderr). <item> is not owner-controlled here means a former vault owner still controls the item; stado credentials item upgrade --host <target> --apply with Skarbiec 0.4.8 moves it to the owner, and the next registration adopts it.
  • <target>: several items carry stado:role:<role>; exactly one may play role <role>, so its reads are refused until the others are retagged (stderr)

Other operation-specific refusals retain Skarbiec's actionable sentence and name the host, item, grant, catalog, mirror, backup object or seed evidence that could not be read or changed.

A failed host configuration command reports the requested operation, canonical target, remote executable ~/.stado/bin/stado, remote exit status and original output. A host-channel failure names the target and operation without inventing a child exit status. If both configuration grammars fail, both failures remain in the diagnostic. An unsupported nested --json is not blamed on the outer credential command. A successful bare command that returns prose or a different JSON shape is refused rather than treated as an empty configuration or used to guess a vault.

Configuration set and unset include a subsequent read of the result. A failed readback after a successful mutation reports the committed operation separately; it does not imply rollback. Inspect the host's actual configuration before deciding what remains to change.

grant consolidate refuses a missing source or target grant, a bearer that fails a real Skarbiec verification, and grants whose acquire or lifecycle actions cannot share Stado's read grant. It leaves source grants intact during cutover. config migrate-identities refuses a missing bearer file or invalid resulting configuration and leaves the original untouched. An old publisher/deployer item must first be declared under its product name.

Real host qualification

The read-only tests/host_health/configuration.rs journey in the Stado repository uses two actual registry hosts. It changes no host files, grants, services or credentials. Supply these environment inputs from the deployment being qualified:

Input Meaning
STADO_HOST_CONFIG_TARGET Host whose installed release serves either supported JSON configuration grammar.
STADO_HOST_CONFIG_EXPECTED_FILE Its independently known effective configuration file.
STADO_HOST_CONFIG_EXPECTED_VAULT Its independently known declared vault path.
STADO_HOST_CONFIG_REFUSAL_TARGET Different host with an actual configuration-command failure, not merely the older JSON grammar.
STADO_HOST_CONFIG_ITEM Credential item to inspect on that refusing host; the failed prerequisite prevents its read.
STADO_HOST_CONFIG_REMOTE_EXIT Independently observed nonzero exit status of the failed remote configuration command.
cargo test --manifest-path stado-rs/Cargo.toml --test host-configuration-journey -- --ignored --nocapture

The journey compares the readable host's effective configuration with the declared expectations and checks that a dependent credential refusal identifies the failed host, command, executable and remote status. It retains the source revision, working-tree patch, binary digest, commands, exit statuses and responses under .build/host-configuration/<run>/. Missing inputs record a blocked result, not a pass. The test is explicitly selected because it requires those real deployments.

tests/desktop_api/configuration.rs separately exercises genuine installed Stado binaries, the host channel's local transport, and the real native HTTP API in an isolated home. It checks CLI and API configuration reads, set and unset persisted state, single-document JSON readback, rejected unconfirmed API mutations, invalid values that leave bytes unchanged, repeated removal, and a real malformed configuration refusal. It reconciles no service and changes no operator configuration.

cargo test --manifest-path stado-rs/Cargo.toml --test service-journeys journey::configuration::current_installed_host_configuration_cli_and_api -- --nocapture
STADO_HOST_CONFIG_LEGACY_BINARY="$PRIOR_STADO_EXECUTABLE" cargo test --manifest-path stado-rs/Cargo.toml --test service-journeys journey::configuration::prior_installed_host_configuration_cli_and_api -- --ignored --nocapture

The prior executable must be a genuine same-platform release that rejects config show --json with exit 2 and returns its native JSON document for config show. The journey verifies those real answers, stages a copy only inside the isolated home, and records the installed binary's version and digest. It does not use a script or simulated executable. Missing input records a blocked result. Reports, source revision and patch identity, command statuses, API responses, persisted-state observations and service logs remain under .build/desktop-api/<run>/; the fixture home and temporary files are removed after its own service exits.

Stado Desktop

The Hosts inspector contains a Credentials section. It reads stado credentials vault list --host <TARGET> --json, renders the authority state, declared path and number of vault files seen, displays the command's refusal when the declaration is absent, and opens the existing bounded-bearer sheet. That sheet now invokes stado credentials token mint --host <TARGET> and renders only nonsecret receipt fields unless the operator explicitly requests the one-time raw-token reveal.

Under Hosts → Credentials → Available operations…, Read host configuration, Set host configuration value, and Remove host configuration value use the same CLI operations through POST /api/operator/run. The selected host supplies the positional target. Set and remove require the existing mutation review; reads do not. Their forms accept the dotted key, the value for set, and an optional managed service to reconcile. The operation sheet retains the command, exit status, JSON result, and original refusal, including a successful mutation followed by failed readback. No second configuration parser lives in Desktop.

In the same menu, Check or synchronize a bearer without changing grants exposes the same consumer, source host, source path and destination path. The selected host is the destination. Check only; do not replace the selected host's file is initially selected. The form reviews the command and shows the selected Stado API's complete result or refusal; Desktop keeps no bearer copy. The shared host-configuration diagnostic reaches this result through the operator API; Desktop does not reinterpret a remote option refusal as an error in the form's own options.

Merge retired grants into Stado takes one old consumer per line and an absolute bearer path on the selected host. Migrate this Stado source's identity configuration runs on the selected Stado API source, not on the inspected host. Both are reviewed mutations; the result or exact refusal appears in the operation sheet. Reconcile each other host's configuration through its own Stado source.

Source: this website