Skip to content

commands

commands

Command registry for terok-sandbox — one module per subsystem.

Follows the same CommandDef / ArgDef pattern as terok_shield.registry. Higher-level consumers (terok, terok-executor) import COMMANDS to build their own CLI frontends without duplicating argument definitions or handler logic.

Per-subsystem modules:

  • sandbox — setup/uninstall (composes shield + vault + gate + clearance into one verb).
  • gate — gate server lifecycle.
  • shield — egress-firewall hooks.
  • vault — vault passphrase verbs (the unlock/lock/seal trio that drives the SQLCipher chain).
  • ssh — SSH-key CRUD against the credentials DB.
  • doctor — host-side health checks.
  • credentials — credentials-DB encryption chooser, provisioning, and migration phase.
  • launch — prepare/run/cleanup for user-owned containers.

Shield commands are delegated to terok-shield's own registry — SHIELD_COMMANDS re-exports the non-standalone subset.

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=_handle_credentials_encrypt_db),)),) module-attribute

DOCTOR_COMMANDS = (CommandDef(name='doctor', help='Run sandbox health checks', handler=_handle_doctor, group='doctor'),) module-attribute

GATE_COMMANDS = (CommandDef(name='gate', help='Git gate inspection', children=(CommandDef(name='path', help="Print the file:// URL of a project's bare mirror", handler=_handle_gate_path, args=(ArgDef(name='project', help='Project name (the mirror is <project>.git)'),)),)),) module-attribute

LAUNCH_COMMANDS = (CommandDef(name='prepare', help='Print podman flags for sandboxing a user-owned container', handler=_handle_prepare, epilog=_BRIDGES_EPILOG, args=(ArgDef(name='container', help='Container name (becomes --name)'), ArgDef(name='--no-shield', action='store_true', help='Disable egress firewall (default: on)', dest='no_shield'), ArgDef(name='--no-gate', action='store_true', help='Disable git gate (default: on; requires --scope)', dest='no_gate'), ArgDef(name='--no-broker', action='store_true', help='Disable vault token broker (default: on; requires --scope)', dest='no_broker'), ArgDef(name='--scope', help='Credential scope; enables vault SSH agent and is required by gate/broker'), ArgDef(name='--profiles', type=_csv_list, help="Override shield profiles for this container (comma-separated, e.g. 'dev,pypi')"), ArgDef(name='--json', action='store_true', dest='output_json', help='Output JSON array instead of a shell-quoted string'))), CommandDef(name='run', help='Launch a sandboxed user-owned container (exec into podman run)', handler=_handle_run, epilog=_BRIDGES_EPILOG, args=(ArgDef(name='container', help='Container name (becomes --name)'), ArgDef(name='--no-shield', action='store_true', help='Disable egress firewall (default: on)', dest='no_shield'), ArgDef(name='--no-gate', action='store_true', help='Disable git gate (default: on; requires --scope)', dest='no_gate'), ArgDef(name='--no-broker', action='store_true', help='Disable vault token broker (default: on; requires --scope)', dest='no_broker'), ArgDef(name='--scope', help='Credential scope; enables vault SSH agent and is required by gate/broker'), ArgDef(name='--profiles', type=_csv_list, help="Override shield profiles for this container (comma-separated, e.g. 'dev,pypi')"))), CommandDef(name='cleanup', help='Revoke tokens and drop shield rules for a sandboxed container', handler=_handle_cleanup, args=(ArgDef(name='container', help='Container name to clean up'),))) module-attribute

SETUP_COMMANDS = (CommandDef(name='setup', help='Install supervisor hooks + shield hooks in one step', handler=_handle_sandbox_setup, args=(ArgDef(name='--no-shield', action='store_true', help='Skip shield install'), ArgDef(name='--no-vault', action='store_true', help='Skip the credentials-DB encryption phase'), ArgDef(name='--echo-passphrase', action='store_true', help='Also print any auto-generated vault passphrase to stdout (default off — the value otherwise only reaches /dev/tty, so non-interactive bootstraps must opt in to capture it)'), ArgDef(name='--passphrase-tier', default=None, help='Force credentials-DB passphrase storage to a specific tier (systemd-creds | keyring | session-file | config) instead of the auto-detect / chooser chain. Required on a non-TTY host without systemd-creds — the silent session-file fallback was removed in v0.0.100 because it minted a passphrase the operator never saw and lost it on the first reboot.'))), CommandDef(name='uninstall', help='Remove supervisor hooks + shield hooks in one step', handler=_handle_sandbox_uninstall, args=(ArgDef(name='--no-shield', action='store_true', help='Skip shield uninstall'),))) module-attribute

SHIELD_COMMANDS = (CommandDef(name='shield', help='Egress firewall management', children=(_SANDBOX_VERBS + _imported_shield_children())),) module-attribute

SSH_COMMANDS = (CommandDef(name='ssh', help='SSH keypair management', children=(CommandDef(name='list', help='List SSH keys stored in the vault', handler=_handle_ssh_list, args=(ArgDef(name='--scope', help='Show keys for a specific credential scope only', default=None),)), CommandDef(name='import', help='Import an OpenSSH keypair from files into the vault DB', handler=_handle_ssh_import, args=(ArgDef(name='scope', help='Credential scope to associate the key with'), ArgDef(name='--private-key', help='Path to the private key file', dest='private_key', required=True), ArgDef(name='--public-key', help='Path to the .pub file (default: derive from the private key)', default=None, dest='public_key'), ArgDef(name='--comment', help="Override the key's comment string", default=None))), CommandDef(name='add', help='Generate a new SSH keypair in the vault for a credential scope', handler=_handle_ssh_add, args=(ArgDef(name='scope', help='Credential scope to associate the key with'), ArgDef(name='--key-type', help='Key algorithm: ed25519 (default) or rsa', default='ed25519', dest='key_type'), ArgDef(name='--comment', help='Comment embedded in the public key (default: tk-main:<scope>)', default=None), ArgDef(name='--force', help='Rotate — unassign all existing keys from the scope and generate fresh', action='store_true'))), CommandDef(name='export', help="Export a scope's SSH keypair to standard OpenSSH files", handler=_handle_ssh_export, args=(ArgDef(name='scope', help='Credential scope to export'), ArgDef(name='--out-dir', help='Directory to write files into', dest='out_dir', required=True), ArgDef(name='--key-id', help='Export a specific ssh_keys.id (default: most recently added)', default=None, dest='key_id', type=int), ArgDef(name='--out-name', help='Override the output filename stem (default: id_<type>_<fp8>)', default=None, dest='out_name'))), CommandDef(name='pub', help="Print a scope's public key to stdout", handler=_handle_ssh_pub, args=(ArgDef(name='scope', help='Credential scope'), ArgDef(name='--key-id', help='Specific ssh_keys.id (default: most recently added)', default=None, dest='key_id', type=int), ArgDef(name='--all', help='Print every key assigned to the scope, one per line', action='store_true', dest='all_keys'))), CommandDef(name='link', help='Link an existing vault key to an additional scope', handler=_handle_ssh_link, args=(ArgDef(name='scope', help='Credential scope to link the key to'), ArgDef(name='--key-id', help='ssh_keys.id of the key already stored in the vault', dest='key_id', type=int, required=True))), CommandDef(name='rename', help='Change the comment of a stored SSH key (selected by fingerprint prefix)', handler=_handle_ssh_rename, args=(ArgDef(name='fingerprint', help='Fingerprint prefix identifying the key (min 8 chars recommended)'), ArgDef(name='comment', help='New comment text'))), CommandDef(name='remove', help='Unassign SSH keys from scopes (orphaned keys cascade-delete)', handler=_handle_ssh_remove, args=(ArgDef(name='--scope', help='Filter by credential scope (exact match)', default=None), ArgDef(name='--comment', help='Filter by comment (supports glob wildcards)', default=None), ArgDef(name='--fingerprint', help='Filter by fingerprint prefix (min 8 chars recommended)', default=None), ArgDef(name='--yes', help='Skip confirmation prompts', action='store_true', dest='yes'))))),) module-attribute

SUPERVISOR_COMMANDS = (CommandDef(name='supervisor', help='Run the per-container supervisor (internal; spawned by the OCI hook)', handler=_handle_supervisor, group='internal', args=(ArgDef(name='container_id', help='Container ID the supervisor manages'), ArgDef(name='sidecar_path', help='Absolute path to the per-container sidecar JSON'))),) module-attribute

VAULT_COMMANDS = (CommandDef(name='vault', help='Vault passphrase management', children=(CommandDef(name='status', help='Show lock state, the passphrase resolution chain, and stored secrets', handler=_handle_vault_status, args=(ArgDef(name='--json', dest='as_json', action='store_true', help='Machine-readable JSON output'),)), CommandDef(name='unlock', help='Provision the credentials-DB passphrase for this session (tmpfs file)', handler=_handle_vault_unlock, args=(ArgDef(name='--force', action='store_true', help='Write the session file even if a durable tier already unlocks the vault'),)), CommandDef(name='lock', help="Clear every stored copy of the passphrase — you'll need it to unlock again", handler=_handle_vault_lock, args=(ArgDef(name='--force', action='store_true', help="Skip the 'have you saved the passphrase?' confirmation"),)), CommandDef(name='list', help='Inventory stored credentials (and optionally proxy tokens)', handler=_handle_vault_list, args=(ArgDef(name='--include-tokens', action='store_true', help='Also show proxy-token rows (token values are masked)'), ArgDef(name='--json', dest='as_json', action='store_true', help='Machine-readable JSON output'))), _PASSPHRASE_GROUP)),) module-attribute

COMMANDS = CommandTree(SETUP_COMMANDS + GATE_COMMANDS + SHIELD_COMMANDS + VAULT_COMMANDS + SSH_COMMANDS + CREDENTIALS_COMMANDS + LAUNCH_COMMANDS + DOCTOR_COMMANDS + SUPERVISOR_COMMANDS) module-attribute

__all__ = ['ArgDef', 'CommandDef', 'CommandTree', 'KeyRow', 'COMMANDS', 'CREDENTIALS_COMMANDS', 'DOCTOR_COMMANDS', 'GATE_COMMANDS', 'LAUNCH_COMMANDS', 'SETUP_COMMANDS', 'SHIELD_COMMANDS', 'SSH_COMMANDS', 'SUPERVISOR_COMMANDS', 'VAULT_COMMANDS', '_ask_passphrase_mode', '_back_up_plaintext_db', '_build_key_rows', '_filter_key_rows', '_forget_config_tier_updates', '_handle_cleanup', '_handle_credentials_encrypt_db', '_handle_doctor', '_handle_gate_path', '_handle_prepare', '_handle_run', '_handle_sandbox_setup', '_handle_sandbox_uninstall', '_handle_shield_setup', '_handle_shield_uninstall', '_handle_ssh_add', '_handle_ssh_export', '_handle_ssh_import', '_handle_ssh_link', '_handle_ssh_list', '_handle_ssh_pub', '_handle_ssh_remove', '_handle_ssh_rename', '_handle_supervisor', '_handle_vault_list', '_handle_vault_lock', 'handle_vault_seal', 'handle_vault_to_keyring', '_handle_vault_unlock', 'SessionProvisionResult', 'SessionShadow', 'clear_redundant_session_file', 'provision_session_passphrase', 'purge_passphrase_tiers', 'session_shadow_state', '_key_id_from_row', '_open_db', '_persist_mode_choice', '_print_key_table', '_provision_passphrase', '_run_credentials_setup_phase', '_validate_scope_name'] module-attribute

SessionProvisionResult(written, validated=False, shadowed_durable=None) dataclass

Outcome of provision_session_passphrase.

written is the load-bearing bit: False means the write was refused because a durable tier already resolves the vault, so a session file would only shadow it (shadowed_durable names that tier). Refusal is a normal outcome, not an error — callers report it ("already unlocked via X") rather than raising. validated says whether the written value was test-opened against an existing DB (vs. an empty install where it becomes the key on first use).

written instance-attribute

validated = False class-attribute instance-attribute

shadowed_durable = None class-attribute instance-attribute

SessionShadow(durable_source, redundant) dataclass

A session-file tier sitting on top of a durable tier.

redundant is the actionable bit:

  • True — the session copy is byte-identical to what the durable tier resolves to, so it's pure residue (a past unlock on a box that already auto-unlocks). Safe to delete; nothing is lost.
  • False — the two differ. Either a deliberate re-key in progress or a stale unlock masking the durable value; never auto-removed.
  • None — the durable tier is present but couldn't be read to compare (broken seal, dead helper), so the session file may be doing real work. Left alone.

durable_source instance-attribute

redundant instance-attribute

clear_redundant_session_file(cfg)

Remove the session-unlock file iff it merely duplicates a durable tier.

Same-key residue only: a session file whose passphrase differs from the durable tier is a deliberate override (or a re-key mid-flight) and is kept. Re-verifies state at call time rather than trusting a caller's stale read. Returns the durable tier it deduplicated against, or None when there was nothing safe to remove.

Source code in src/terok_sandbox/commands/vault.py
def clear_redundant_session_file(cfg: SandboxConfig) -> str | None:
    """Remove the session-unlock file iff it merely duplicates a durable tier.

    Same-key residue only: a session file whose passphrase differs from
    the durable tier is a deliberate override (or a re-key mid-flight)
    and is kept.  Re-verifies state at call time rather than trusting a
    caller's stale read.  Returns the durable tier it deduplicated
    against, or ``None`` when there was nothing safe to remove.
    """
    shadow = session_shadow_state(cfg)
    if shadow is None or shadow.redundant is not True:
        return None
    cfg.vault_passphrase_file.unlink(missing_ok=True)
    return shadow.durable_source

handle_vault_seal(*, cfg=None, key='auto')

Seal the credentials-DB passphrase into a systemd-creds credential.

Adds the systemd-creds tier to the resolution chain: machine-bound (TPM2 + host key, or either alone), survives reboot, no OS keyring required. After sealing, every new supervisor resolves the passphrase via systemd-creds decrypt on start — no operator interaction needed at boot, no plaintext-on-disk.

Requires an already-resolvable passphrase — typically from a fresh vault unlock in the current session.

Source code in src/terok_sandbox/commands/vault.py
def handle_vault_seal(*, cfg: SandboxConfig | None = None, key: str = "auto") -> None:
    """Seal the credentials-DB passphrase into a systemd-creds credential.

    Adds the systemd-creds tier to the resolution chain: machine-bound
    (TPM2 + host key, or either alone), survives reboot, no OS
    keyring required.  After sealing, every new supervisor resolves the
    passphrase via ``systemd-creds decrypt`` on start — no operator
    interaction needed at boot, no plaintext-on-disk.

    Requires an already-resolvable passphrase — typically from a fresh
    ``vault unlock`` in the current session.
    """
    from ..vault.store import systemd_creds
    from ..vault.store.encryption import WrongPassphraseError

    if cfg is None:
        cfg = SandboxConfig()

    if not systemd_creds.is_available():
        raise SystemExit(
            "systemd-creds unavailable: needs systemd ≥ 257 with the Varlink"
            " io.systemd.Credentials interface (Fedora ≥ 42, Debian ≥ 13)"
        )

    key_mode = _SEAL_KEY_MODES.get(key)
    if key_mode is None:
        choices = ", ".join(sorted(_SEAL_KEY_MODES))
        raise SystemExit(f"unknown --key value: {key!r} (expected one of: {choices})")

    # A prompt here would accept a freshly-typed value and seal *that*,
    # leaving the next chain walk holding a key that doesn't open the DB.
    try:
        passphrase = cfg.resolve_passphrase()
    except WrongPassphraseError as exc:
        raise SystemExit(f"cannot seal: {exc}") from exc
    if passphrase is None:
        raise SystemExit("no current passphrase to seal — run `terok-sandbox vault unlock` first")
    _require_recovery_acknowledged(cfg, tier="systemd-creds")

    try:
        systemd_creds.seal(passphrase, cfg.vault_systemd_creds_file, key_mode=key_mode)
    except RuntimeError as exc:
        # ``tpm2`` requested on a TPM-less host surfaces as a CalledProcessError
        # bubbled to RuntimeError — pass it through with the hint attached.
        raise SystemExit(str(exc)) from exc

    print(f"→ sealed passphrase to {cfg.vault_systemd_creds_file} (--with-key={key_mode})")

    # The passphrase now lives in the durable sealed credential, so the
    # session file is redundant — and, being higher priority, it would
    # *shadow* the seal until the next reboot wiped it.  Drop it so the
    # chain resolves from the tier the operator just established (same
    # cleanup ``to-keyring`` does).
    if cfg.vault_passphrase_file.exists():
        cfg.vault_passphrase_file.unlink()
        print(f"→ removed now-redundant session file {cfg.vault_passphrase_file}")

    print(
        "  the resolution chain will pick this up the next time a supervisor"
        " starts; no restart required"
    )

handle_vault_to_keyring(*, cfg=None)

Move the current passphrase from its current tier into the OS keyring.

Resolves the passphrase via the chain (or prompts as a last resort), writes it to the keyring, flips credentials.use_keyring to true in config.yml, clears any plaintext credentials.passphrase / credentials.passphrase_command wiring, and removes the session-file and sealed systemd-creds copies.

The validate-before-destroy ordering is deliberate: if the keyring write fails, the source tier is still intact.

Source code in src/terok_sandbox/commands/vault.py
def handle_vault_to_keyring(*, cfg: SandboxConfig | None = None) -> None:
    """Move the current passphrase from its current tier into the OS keyring.

    Resolves the passphrase via the chain (or prompts as a last resort),
    writes it to the keyring, flips ``credentials.use_keyring`` to true
    in ``config.yml``, clears any plaintext ``credentials.passphrase`` /
    ``credentials.passphrase_command`` wiring, and removes the
    session-file and sealed systemd-creds copies.

    The validate-before-destroy ordering is deliberate: if the keyring
    write fails, the source tier is still intact.
    """
    from .. import config as _config
    from ..vault.store.encryption import (
        WrongPassphraseError,
        store_passphrase_in_keyring,
    )

    if cfg is None:
        cfg = SandboxConfig()

    try:
        passphrase, source = cfg.resolve_passphrase_with_source(prompt_on_tty=True)
    except WrongPassphraseError as exc:
        raise SystemExit(f"cannot move to keyring: {exc}") from exc

    if not passphrase:
        raise SystemExit("no current passphrase resolvable; run `terok-sandbox vault unlock` first")
    if source == "keyring":
        print("→ passphrase is already in the keyring; nothing to do")
        return
    _require_recovery_acknowledged(cfg, tier="keyring")

    if not store_passphrase_in_keyring(passphrase):
        raise SystemExit("OS keyring is unreachable or denied; aborting (nothing was changed)")
    print(f"→ stored passphrase in keyring (was: {source})")

    # Switch the config's tier wiring atomically: flip use_keyring on,
    # drop the plaintext + helper fallbacks so the chain can't re-resolve
    # via a stale lower tier.
    from ..paths import config_file_paths

    user_config = next((p for label, p in config_file_paths() if label == "user"), None)
    if user_config is not None:
        # nosec: B105 — clearing config keys to None, not hardcoding secrets
        updates = {  # nosec: B105
            "use_keyring": True,
            "passphrase": None,  # nosec: B105
            "passphrase_command": None,  # nosec: B105
        }
        _yaml_update_section(user_config, "credentials", updates)
        _config._credentials_section.cache_clear()
        print(f"→ updated {user_config} (use_keyring: true, plaintext fields cleared)")

    # Remove the old tier's persistent copy.  Session file is removed
    # because the chain prefers it over keyring; sealed systemd-creds
    # likewise outranks keyring on the resolution order.
    for stale in (cfg.vault_passphrase_file, cfg.vault_systemd_creds_file):
        if stale.exists():
            stale.unlink()
            print(f"→ removed {sanitize_tty(str(stale))}")

provision_session_passphrase(cfg, passphrase, *, force=False)

Validate passphrase against the DB, then land it on the session tier.

The single writer of the session-unlock tmpfs file — the CLI vault unlock and terok's TUI unlock modal both funnel through here, so the no-shadow and validation guards apply to every caller by construction; neither can store a value the DB rejects, nor silently shadow a working durable tier.

Two guards, in order:

  1. No-shadow. The session tier is the highest-priority slot, so writing it masks any durable tier (systemd-creds / keyring / config) underneath — a reboot then wipes the session copy and the operator only thought the sealed key was in use. When a durable tier already resolves and force is false, nothing is written and the result reports written=False + the shadowed tier. force (re-key / deliberate override) skips this guard.
  2. Validation. When the DB exists (and isn't a legacy plaintext file) the value is test-opened first; a mismatch raises WrongPassphraseError and nothing is written. A missing DB skips validation (opening it just to check would create it as a side effect) — the value becomes its key on first use.
Source code in src/terok_sandbox/commands/vault.py
def provision_session_passphrase(
    cfg: SandboxConfig, passphrase: str, *, force: bool = False
) -> SessionProvisionResult:
    """Validate *passphrase* against the DB, then land it on the session tier.

    The single writer of the session-unlock tmpfs file — the CLI
    ``vault unlock`` and terok's TUI unlock modal both funnel through
    here, so the no-shadow and validation guards apply to every caller
    by construction; neither can store a value the DB rejects, nor
    silently shadow a working durable tier.

    Two guards, in order:

    1. **No-shadow.** The session tier is the highest-priority slot, so
       writing it masks any durable tier (systemd-creds / keyring /
       config) underneath — a reboot then wipes the session copy and the
       operator only *thought* the sealed key was in use.  When a durable
       tier already resolves and *force* is false, nothing is written and
       the result reports ``written=False`` + the shadowed tier.  *force*
       (re-key / deliberate override) skips this guard.
    2. **Validation.** When the DB exists (and isn't a legacy plaintext
       file) the value is test-opened first; a mismatch raises
       [`WrongPassphraseError`][terok_sandbox.WrongPassphraseError] and
       **nothing is written**.  A missing DB skips validation (opening it
       just to check would create it as a side effect) — the value
       becomes its key on first use.
    """
    from .._yaml import write_secret_text
    from ..vault.store.db import CredentialDB
    from ..vault.store.encryption import is_plaintext_sqlite

    if not force:
        shadowed = _active_durable_source(cfg)
        if shadowed is not None:
            return SessionProvisionResult(written=False, shadowed_durable=shadowed)

    validated = False
    if cfg.db_path.exists() and not is_plaintext_sqlite(cfg.db_path):
        # Raises WrongPassphraseError / PlaintextDBFoundError on mismatch —
        # deliberately before the write so a bad value never lands.
        CredentialDB(cfg.db_path, passphrase=passphrase).close()
        validated = True
    write_secret_text(cfg.vault_passphrase_file, passphrase + "\n")
    return SessionProvisionResult(written=True, validated=validated)

purge_passphrase_tiers(cfg)

Remove every stored copy of the credentials-DB passphrase.

Clears the session-unlock tmpfs file, the OS keyring entry, the sealed systemd-creds credential, and the plaintext credentials.passphrase / credentials.passphrase_command wiring in config.yml — then drops the recovery-acknowledged marker, since it's meaningless once no tier remains. After this the vault can only be reopened by re-supplying the passphrase (vault unlock); it is unrecoverable without an off-host copy.

No prompts and no acknowledgement check: this is the raw destructive action. The lock verb gates it behind a typed-SAVED confirmation when recovery is unacknowledged; panic calls it directly — no questions asked.

Source code in src/terok_sandbox/commands/vault.py
def purge_passphrase_tiers(cfg: SandboxConfig) -> None:
    """Remove every stored copy of the credentials-DB passphrase.

    Clears the session-unlock tmpfs file, the OS keyring entry, the
    sealed systemd-creds credential, and the plaintext
    ``credentials.passphrase`` / ``credentials.passphrase_command``
    wiring in ``config.yml`` — then drops the recovery-acknowledged
    marker, since it's meaningless once no tier remains.  After this the
    vault can only be reopened by re-supplying the passphrase
    (``vault unlock``); it is **unrecoverable** without an off-host copy.

    No prompts and no acknowledgement check: this is the raw destructive
    action.  The ``lock`` verb gates it behind a typed-``SAVED``
    confirmation when recovery is unacknowledged; panic calls it
    directly — no questions asked.
    """
    from ..vault.store.encryption import forget_passphrase_in_keyring, load_passphrase_from_keyring

    path = cfg.vault_passphrase_file
    if path.exists():
        path.unlink()
        print(f"→ removed {path}")

    if cfg.credentials_use_keyring:
        if forget_passphrase_in_keyring():
            print("→ cleared keyring entry")
        elif load_passphrase_from_keyring() is None:
            # ``keyring.delete_password`` raises on a missing entry on most
            # backends, which the helper folds to False — a residual entry
            # after that means the backend rejected the delete.
            print("→ keyring entry already absent")
        else:
            raise SystemExit(
                "failed to clear keyring entry;"
                " future supervisors may still auto-unlock from keyring"
            )

    config_updates = _forget_config_tier_updates(cfg)
    if config_updates:
        from .. import config as _config
        from ..paths import config_file_paths

        user_config = next((p for label, p in config_file_paths() if label == "user"), None)
        if user_config is not None and user_config.exists():
            _yaml_update_section(user_config, "credentials", config_updates)
            _config._credentials_section.cache_clear()
            for key in config_updates:
                print(f"→ cleared credentials.{key} from config.yml")

    sealed_cred = cfg.vault_systemd_creds_file
    if sealed_cred.exists():
        try:
            sealed_cred.unlink()
        except OSError as exc:
            raise SystemExit(f"failed to remove sealed credential at {sealed_cred}: {exc}") from exc
        print(f"→ removed sealed credential at {sealed_cred}")

    from ..vault.store.recovery import forget as forget_recovery_marker

    forget_recovery_marker(cfg.vault_recovery_marker_file)

session_shadow_state(cfg)

Describe a session-file-over-durable-tier shadow, or None if there is none.

Returns None in the common cases — no session file, or no durable tier beneath it — without resolving anything. Only when both are present does it resolve the durable chain (omitting the session file) to compare values; that one comparison is the only place status / remediation pay an unseal. The session secret never leaves the process — this reads two tiers and compares, exactly what the resolver already does internally.

Source code in src/terok_sandbox/commands/vault.py
def session_shadow_state(cfg: SandboxConfig) -> SessionShadow | None:
    """Describe a session-file-over-durable-tier shadow, or ``None`` if there is none.

    Returns ``None`` in the common cases — no session file, or no durable
    tier beneath it — without resolving anything.  Only when both are
    present does it resolve the *durable* chain (omitting the session
    file) to compare values; that one comparison is the only place
    status / remediation pay an unseal.  The session secret never leaves
    the process — this reads two tiers and compares, exactly what the
    resolver already does internally.
    """
    from ..vault.store.encryption import (
        NoPassphraseError,
        WrongPassphraseError,
        load_passphrase_from_file,
        resolve_passphrase_with_source,
    )

    session_value = load_passphrase_from_file(cfg.vault_passphrase_file)
    if not session_value:
        return None
    durable_source = _active_durable_source(cfg)
    if durable_source is None:
        return None
    try:
        durable_value, _ = resolve_passphrase_with_source(
            systemd_creds_file=cfg.vault_systemd_creds_file,
            use_keyring=cfg.credentials_use_keyring,
            passphrase_command=cfg.credentials_passphrase_command,
            config_fallback=cfg.credentials_passphrase,
            # ``passphrase_file`` omitted on purpose — resolve the durable
            # chain *under* the session file so we can compare against it.
        )
    except (WrongPassphraseError, NoPassphraseError):
        durable_value = None
    if not durable_value:
        return SessionShadow(durable_source, redundant=None)
    return SessionShadow(durable_source, redundant=(durable_value == session_value))