Second factors stay separate from passwords.
Skarbiec supports stored TOTP seeds, recovery codes, and pending one-time challenges without turning any of them into a standing credential shared with a workload.
TOTP for stored logins#
A login item stores its username, password, optional `totp_secret`, and optional recovery codes as separate encrypted fields. `skarbiec totp <item-id>` reads the seed inside Skarbiec and computes the current SHA1, six-digit, thirty-second code in process. It requires no oath-toolkit installation.
The managed browser bridge follows the same boundary. It computes the current code in the native host for one explicit fill request, while login listings expose only metadata and the username.
skarbiec totp platform-admin-exampleA stored value is not automatically a stored second factor#
`skarbiec totp-seed-state [<item-id>]` validates the field through the same Base32-to-six-digit-code path as `skarbiec totp`. It never emits the seed or the computed code. Every result includes a sentence in `description`, and every settled failure includes the first correct action in `repair`.
`present` means the field has a supported Base32 shape and produced a six-digit code; it does not prove that the account still has that seed enrolled. `placeholder` means the field holds an uppercase underscore-delimited value such as `WELES_ADMIN_GOOGLE_TOTP_SECRET`, which is not a secret. `invalid` is other non-empty text that cannot produce a six-digit code. `declared_empty`, `field_absent`, and `unreadable` keep their distinct missing-field, wrong-kind, and decryption meanings.
For `placeholder`, the repair first replaces the placeholder account values with a real account and only then enrolls and stores TOTP. `skarbiec totp` reports `has_seed: true` only for `present`; `routes verify`, `doctor`, and `grant capability` also refuse uppercase placeholder credentials instead of calling non-empty text usable.
skarbiec totp-seed-state platform-admin-exampleOne-use 2FA challenges#
A provider can deliver a challenge only after a login has started. Skarbiec therefore lets an authorized flow issue a capability for a `challenge:` resource before the digits exist. Redemption returns `pending` until the trusted relay stores the code, then returns it once.
The relay writes digits through `apple-challenge-put <challenge-resource>` on stdin. The command refuses an unknown authorization, a non-challenge resource, an empty value, or non-digits. The code remains tied to the authorization that requested it.
printf '%s' '<six-digit-code>' | skarbiec apple-challenge-put challenge:apple/<authorization-id>Recovery is not routine authentication#
- Recovery codes remain a separate encrypted field; they are not emitted by `totp` and are not included in login metadata.
- TOTP seeds and recovery codes require their own exact field grants. Permission to read a password does not imply permission to read either second factor.
- A pending challenge is finite state, not a saved login field. Consuming it does not create a reusable 2FA secret.
Storing a TOTP seed in a login item#
A login item's `totp_secret` field holds a base32-encoded TOTP seed from an authenticator enrollment. This is the value an authenticator app (like Google Authenticator) generates when you set up two-factor authentication on an account.
The seed must reach the vault through standard input or an owner-only file, never as a command-line argument — anything in `argv` is readable by every other process on the host at that moment.
The `set-json` command replaces the complete payload, so retain every existing field when adding totp_secret. In Desktop, Items → Add TOTP Seed → Save Seed uses the real read-merge-write operation and preserves the other credential fields.
- Get the base32 seed value from the authenticator enrollment (Google Authenticator, Authy, 1Password, etc.).
- Read the existing login item to know its username and password: `skarbiec get <item-id>`
- Construct the complete item JSON with the same username, password, and the new totp_secret field.
- Pass the complete updated login JSON on stdin to `skarbiec set-json <item-id> --type login`.
- Verify the code is now retrievable: `skarbiec totp <item-id>`
# Read the existing complete record before editing login.json
skarbiec get my-login
# Store the complete updated JSON, preserving its other fields
cat login.json | skarbiec set-json my-login --type login
# Read the current code
skarbiec totp my-login