Skip to content

credentials

credentials

Credentials-DB at-rest encryption — chooser, provisioning, migration.

Two passphrase storage modes are chosen interactively when systemd-creds isn't available; with it, the chooser is skipped and the credential is sealed silently. Once chosen, the mode is persisted so the resolution chain picks it up on the next daemon start — session mode self-describes via the tmpfs file's presence; keyring sets credentials.use_keyring=true in config.yml. plan_provisioning is the shared decision core: the CLI chooser here and the TUI's modal flow are two renderings of the same plan.

The plaintext→encrypted migration is deprecated in 0.8.0 and slated for removal in 0.9.0. After that release fresh installs stay the only supported entry point; operators with a stale plaintext DB must restore from the .plaintext-backup-<stamp>.tar.gz snapshot this phase writes before re-keying.

CREDENTIALS_COMMANDS = (CommandDef(name='credentials', help='Credentials DB management', children=(CommandDef(name='encrypt-db', help='Migrate a legacy plaintext credentials DB to SQLCipher-encrypted (deprecated in 0.8.0, removed in 0.9.0)', handler=(LazyHandler('terok_sandbox.commands.credentials:_handle_credentials_encrypt_db'))),)),) module-attribute

CREDENTIALS = CREDENTIALS_COMMANDS[0] module-attribute

__all__ = ['CREDENTIALS', 'CREDENTIALS_COMMANDS', 'ProvisioningPlan', 'TierProvisionResult', '_run_credentials_setup_phase', 'credentials_provisioned', 'plan_provisioning', 'provision_passphrase_tier'] module-attribute

TierProvisionResult(passphrase, source, generated) dataclass

Outcome of provision_passphrase_tier.

generated drives the caller's follow-up duty: a minted value must be revealed to the operator (it is their recovery key) and acknowledged via terok_sandbox.vault.store.recovery.acknowledge once they confirm it is saved off-host.

passphrase instance-attribute

The value now landed on the tier — mint or caller-supplied.

source instance-attribute

Which tier holds it, in resolution-chain vocabulary.

generated instance-attribute

True iff this call minted the value (caller passed None).

ProvisioningPlan(provisioned, auto_tier, choices, keyring_available) dataclass

The decision half of first-run passphrase provisioning, frontend-free.

Every frontend renders exactly this: skip when provisioned, provision auto_tier silently when set, otherwise put choices to the operator. keyring_available lets a frontend grey out the keyring choice up front instead of failing after the pick. The CLI chooser below and the TUI's modal flow are two renderings of one plan — the decisions themselves are made here, once.

provisioned instance-attribute

auto_tier instance-attribute

choices instance-attribute

keyring_available instance-attribute

provision_passphrase_tier(cfg=None, *, tier, passphrase=None)

Land a passphrase on tier with no terminal interaction whatsoever.

The programmatic sibling of the setup chooser, built for GUI/TUI frontends that own the conversation themselves: no /dev/tty announce, no ack prompt, no stdout side channel. The caller shows the returned value in its own reveal surface and records the operator's confirmation through terok_sandbox.vault.store.recovery.acknowledge.

passphrase None mints a fresh generate_passphrase value. When the credentials DB already exists encrypted, the supplied value must open it — a mint could never match, so None raises NoPassphraseError and a mismatch raises WrongPassphraseError before anything lands on the tier. This closes the fresh-install trapdoor where an "unlock"-shaped prompt silently keys a brand-new vault to an unvalidated string.

Raises ValueError for a tier outside PROVISIONABLE_TIERS or an explicit empty passphrase (SQLCipher reads "" as "no encryption"), and RuntimeError when the chosen backend (systemd-creds, OS keyring) is unreachable.

Source code in src/terok_sandbox/commands/credentials.py
def provision_passphrase_tier(
    cfg: SandboxConfig | None = None,
    *,
    tier: str,
    passphrase: str | None = None,
) -> TierProvisionResult:
    """Land a passphrase on *tier* with no terminal interaction whatsoever.

    The programmatic sibling of the setup chooser, built for GUI/TUI
    frontends that own the conversation themselves: no ``/dev/tty``
    announce, no ack prompt, no stdout side channel.  The caller shows
    the returned value in its own reveal surface and records the
    operator's confirmation through
    [`terok_sandbox.vault.store.recovery.acknowledge`][terok_sandbox.vault.store.recovery.acknowledge].

    *passphrase* ``None`` mints a fresh
    [`generate_passphrase`][terok_sandbox.vault.store.encryption.generate_passphrase]
    value.  When the credentials DB already exists encrypted, the
    supplied value must open it — a mint could never match, so ``None``
    raises [`NoPassphraseError`][terok_sandbox.vault.store.encryption.NoPassphraseError]
    and a mismatch raises
    [`WrongPassphraseError`][terok_sandbox.vault.store.encryption.WrongPassphraseError]
    *before* anything lands on the tier.  This closes the fresh-install
    trapdoor where an "unlock"-shaped prompt silently keys a brand-new
    vault to an unvalidated string.

    Raises [`ValueError`][ValueError] for a tier outside
    [`PROVISIONABLE_TIERS`][terok_sandbox.vault.store.tiers.PROVISIONABLE_TIERS]
    or an explicit empty passphrase (SQLCipher reads ``""`` as "no
    encryption"), and [`RuntimeError`][RuntimeError] when the chosen
    backend (systemd-creds, OS keyring) is unreachable.
    """
    from ..config import SandboxConfig
    from ..vault.store import systemd_creds as _systemd_creds
    from ..vault.store.db import CredentialDB
    from ..vault.store.encryption import (
        NoPassphraseError,
        generate_passphrase,
        is_plaintext_sqlite,
        store_passphrase_in_keyring,
    )

    if tier not in PROVISIONABLE_TIERS:
        raise ValueError(
            f"cannot provision tier {tier!r};"
            f" expected one of: {', '.join(sorted(PROVISIONABLE_TIERS))}"
        )
    tier = PassphraseTier(tier)
    if passphrase == "":  # nosec: B105 — rejecting the empty sentinel, not comparing a secret
        # The keyring and systemd-creds writers refuse an empty value
        # themselves; guard the session-file tier to the same standard
        # so no branch can report success while leaving nothing usable.
        raise ValueError("refusing to provision an empty passphrase")
    if cfg is None:
        cfg = SandboxConfig()

    generated = passphrase is None
    if cfg.db_path.exists() and not is_plaintext_sqlite(cfg.db_path):
        if passphrase is None:
            raise NoPassphraseError(
                f"{cfg.db_path} is already encrypted — a freshly minted passphrase"
                " could never open it; pass the existing passphrase explicitly"
            )
        # Raises WrongPassphraseError on mismatch — before the write, so
        # a bad value never lands on any tier.
        CredentialDB(cfg.db_path, passphrase=passphrase).close()
    if passphrase is None:
        passphrase = generate_passphrase()

    if tier is PassphraseTier.SYSTEMD_CREDS:
        if not _systemd_creds.is_available():
            raise RuntimeError(
                "systemd-creds is unavailable (needs systemd ≥ 257 with the"
                " Varlink io.systemd.Credentials interface); choose a different tier"
            )
        _systemd_creds.seal(passphrase, cfg.vault_systemd_creds_file, key_mode="auto")
        return TierProvisionResult(passphrase, PassphraseTier.SYSTEMD_CREDS, generated)

    if tier is PassphraseTier.KEYRING:
        if not store_passphrase_in_keyring(passphrase):
            raise RuntimeError(
                "OS keyring is unreachable or denied; choose a different storage mode"
            )
        _persist_mode_choice(PassphraseTier.KEYRING)
        return TierProvisionResult(passphrase, PassphraseTier.KEYRING, generated)

    from .._yaml import write_secret_text

    write_secret_text(cfg.vault_passphrase_file, passphrase + "\n")
    return TierProvisionResult(passphrase, PassphraseTier.SESSION_FILE, generated)

credentials_provisioned(cfg=None)

Return True iff setup's credentials phase has nothing left to provision.

The pre-flight probe for frontends that want to collect the tier choice before dispatching setup non-interactively: True when the DB is already SQLCipher-encrypted or some tier of the resolution chain already holds a passphrase, False when a non-TTY setup run would fail closed asking for a tier.

Not infallible: a configured-but-broken durable tier (an unsealable systemd-creds credential, a dead passphrase_command) propagates its fail-closed WrongPassphraseError — callers should surface that as a hard failure, not read it as False.

Source code in src/terok_sandbox/commands/credentials.py
def credentials_provisioned(cfg: SandboxConfig | None = None) -> bool:
    """Return ``True`` iff setup's credentials phase has nothing left to provision.

    The pre-flight probe for frontends that want to collect the tier
    choice *before* dispatching ``setup`` non-interactively: ``True``
    when the DB is already SQLCipher-encrypted or some tier of the
    resolution chain already holds a passphrase, ``False`` when a
    non-TTY setup run would fail closed asking for a tier.

    Not infallible: a configured-but-broken durable tier (an
    unsealable systemd-creds credential, a dead ``passphrase_command``)
    propagates its fail-closed
    [`WrongPassphraseError`][terok_sandbox.vault.store.encryption.WrongPassphraseError]
    — callers should surface that as a hard failure, not read it as
    ``False``.
    """
    from ..config import SandboxConfig
    from ..vault.store.encryption import is_plaintext_sqlite

    if cfg is None:
        cfg = SandboxConfig()
    if cfg.db_path.exists() and not is_plaintext_sqlite(cfg.db_path):
        return True
    return _resolve_existing(cfg) is not None

plan_provisioning(cfg=None)

Probe the host and return the provisioning decisions a frontend should render.

Propagates the fail-closed WrongPassphraseError of a configured-but-broken durable tier (see credentials_provisioned) — surface it as a hard failure, not as "unprovisioned".

Source code in src/terok_sandbox/commands/credentials.py
def plan_provisioning(cfg: SandboxConfig | None = None) -> ProvisioningPlan:
    """Probe the host and return the provisioning decisions a frontend should render.

    Propagates the fail-closed ``WrongPassphraseError`` of a
    configured-but-broken durable tier (see
    [`credentials_provisioned`][terok_sandbox.commands.credentials.credentials_provisioned])
    — surface it as a hard failure, not as "unprovisioned".
    """
    from ..config import SandboxConfig
    from ..vault.store import systemd_creds as _systemd_creds
    from ..vault.store.encryption import keyring_backend_available

    if cfg is None:
        cfg = SandboxConfig()
    keyring_ok = keyring_backend_available()
    if credentials_provisioned(cfg):
        return ProvisioningPlan(
            provisioned=True, auto_tier=None, choices=(), keyring_available=keyring_ok
        )
    auto_tier = PassphraseTier.SYSTEMD_CREDS if _systemd_creds.is_available() else None
    return ProvisioningPlan(
        provisioned=False,
        auto_tier=auto_tier,
        choices=CHOOSER_TIERS,
        keyring_available=keyring_ok,
    )