Skip to content

Vault commands

vault_commands

Executor-level vault helpers: route generation + credential-leak scan.

The vault is served per container: the supervisor spawns on container start via the terok-sandbox OCI hook and reads the per-container sidecar to bind its proxy. Sandbox owns the unlock / lock / passphrase verbs (passphrase-tier CRUD on the DB).

What lives here:

  • routes — regenerate routes.json from the YAML agent roster.
  • clean — remove leaked credential files from shared config mounts.
  • scan_leaked_credentials / _is_injected_credentials_file / _is_injected_codex_auth_file — primitives the scan + clean verbs share.

Both verbs operate on host-side files only.

SANDBOX_TREE = _build_sandbox_tree() module-attribute

VAULT_COMMANDS = (SANDBOX_TREE.find_at(('vault',)),) module-attribute

scan_leaked_credentials(mounts_base)

Return (provider, host_path) for credential files found in shared mounts.

When the vault is active, real secrets should only live in the vault's sqlite DB — not in the shared config directories that get mounted into containers. This function checks each routed provider's mount for credential files that would leak real tokens alongside phantom ones.

Files injected by _write_claude_credentials_file are recognised by their dummy accessToken marker and skipped.

Symlinks are rejected to prevent a container from tricking the scan into reading arbitrary host files via a crafted symlink in the shared mount.

Source code in src/terok_executor/credentials/vault_commands.py
def scan_leaked_credentials(mounts_base: Path) -> list[tuple[str, Path]]:
    """Return ``(provider, host_path)`` for credential files found in shared mounts.

    When the vault is active, real secrets should only live in the
    vault's sqlite DB — not in the shared config directories that get mounted
    into containers.  This function checks each routed provider's mount for
    credential files that would leak real tokens alongside phantom ones.

    Files injected by `_write_claude_credentials_file`
    are recognised by their dummy ``accessToken`` marker and skipped.

    Symlinks are rejected to prevent a container from tricking the scan into
    reading arbitrary host files via a crafted symlink in the shared mount.
    """
    import stat

    from terok_executor.roster import AgentRoster

    roster = AgentRoster.shared()
    base_resolved = mounts_base.resolve(strict=False)
    leaked: list[tuple[str, Path]] = []
    for name, route in roster.vault_routes.items():
        if not route.credential_file:
            continue
        auth = roster.auth_providers.get(name)
        if not auth:
            continue
        try:
            path = mounts_base / auth.host_dir_name / route.credential_file
            # lstat: do not follow symlinks — reject them outright
            st = path.lstat()
            if stat.S_ISLNK(st.st_mode) or not stat.S_ISREG(st.st_mode):
                continue
            # Ensure resolved path stays within the mounts base
            if base_resolved not in path.resolve(strict=True).parents:
                continue
            if st.st_size > 0 and not (
                _is_injected_credentials_file(path) or _is_injected_codex_auth_file(path)
            ):
                leaked.append((name, path))
        except (OSError, TypeError) as exc:
            # Silently skipping turns a real leak into a no-result: the
            # operator would believe the scan was clean.  Surface a
            # warning so it's obvious which provider was not checked
            # and why; the loop continues so other providers still get
            # scanned.
            print(
                f"Warning [vault]: credential leak scan skipped {name!r}: {exc}",
                file=sys.stderr,
            )
            continue
    return leaked