TazPod: Vault Lifecycle Detail

Level 3 (Detail) — Complete vault lifecycle: unlock, lock, save, push, pull.

Concept

The vault is an encrypted tar.gz archive (vault.tar.aes) stored under .tazpod/vault/. It holds all operator secrets (AWS credentials, SSH keys, API tokens) and is only decrypted into a tmpfs RAM mount during active sessions.

Path Constants

File: internal/vault/vault.go (lines 17-25)

ConstantPathPurpose
VaultDir/workspace/.tazpod/vaultEncrypted archive directory
VaultFileVaultDir + /vault.tar.aesEncrypted vault file
MountPath/home/tazpod/secretsRAM mount point (tmpfs)
AwsLocalHome/home/tazpod/.awsAWS credential home
AwsVaultDirMountPath + /.awsAWS creds inside RAM
PassCacheMountPath + /.vault_passCached passphrase file

Commands

tazpod unlock

File: cmd/tazpod/vault_cmd.gounlock() (lines 13-25)

  1. If MountPath already mounted → reuse cached passphrase, SetupIdentity(), setupBindAuth(), return
  2. Mount 64MB tmpfs at MountPath
  3. If VaultFile exists:
    • Read encrypted file
    • Prompt for passphrase (3 attempts, term.ReadPassword)
    • crypto.Decrypt()vault.Untar() into MountPath
    • Cache passphrase to PassCache
  4. If VaultFile doesn’t exist → new vault: prompt passphrase, cache it
  5. SetupIdentity() — create /workspace/.tazpod/ subdirs
  6. setupBindAuth() — bind-mount MountPath/.aws~/.aws

tazpod lock

File: cmd/tazpod/vault_cmd.golock() (lines 27-34)

  1. Unmount ~/.aws (AWS bridge)
  2. vault.Lock() — unmount tmpfs at MountPath
  3. All secrets in RAM are destroyed

Auto-Lock on Last Shell Exit (v0.3.22)

The final fix for vault auto-lock uses marker files, not process counting. Each interactive shell creates a marker in /tmp/.tazpod-shells/$$; on EXIT it removes its own marker, cleans stale markers for dead PIDs, and locks the vault only when no markers remain.

  • first shell exit of N>1 → other markers still exist → no lock
  • last shell exit → no markers remain → tazpod lock
  • dead shells leave stale markers, but those are removed on the next shell start by checking /proc/<pid> and comm == bash

This replaced the earlier experimental fixes:

  • v0.3.20: pgrep-based counting + unconditional execInContainer("tazpod lock")
  • v0.3.21: removed unconditional host-triggered lock
  • v0.3.22: marker files under /tmp/.tazpod-shells/ — final verified solution

TD-027 documents the full regression history: deletion of the old unlock→exit shell behavior in commit 7f8e719, host-side lock bug introduced in a9652b3, and multiple failed intermediate counting strategies before the final marker-file approach.

tazpod save

File: cmd/tazpod/vault_cmd.gosave() (lines 36-39)

  1. vault.Save("") in internal/vault/vault.go
  2. If MountPath not mounted → print warning “Vault is not mounted” but returns success (TD-022)
  3. Load cached passphrase (or prompt if missing)
  4. TarDir(MountPath) → tar.gz bytes
  5. crypto.Encrypt() with passphrase
  6. Write to .tazpod/vault/vault.tar.aes

Returns SHA256 content hash (of the plaintext before encryption) for skip detection.

TD-022: save prints a success message even when the RAM vault is not mounted.

tazpod pull vault

File: cmd/tazpod/sync.gopullVault() (lines 90-102)

  1. loadVaultAWSCredentials() — read AWS creds from RAM if mounted
  2. Resolves vault file path via vaultFilePath() — tries /workspace/.tazpod/ (container) then cwd/.tazpod/ (host)
  3. s3.DownloadFile("tazpod/vault/vault.tar.aes", vaultFile)

--index N: tazpod pull vault --index 1 downloads the penultimate history copy from tazpod/vault/history/. Index 0 (default) = latest.

tazpod push vault

File: cmd/tazpod/sync.gopushVault() (lines 104-117)

  1. Calls pushVaultInternal(contentHash)
  2. Resolves vault path via vaultFilePath() — same dual-path logic
  3. Hash skip: HeadObject on latest to compare content-sha256 metadata → if unchanged, skip all
  4. Upload history copy to tazpod/vault/history/vault-<TIMESTAMP>.tar.aes with content-sha256 metadata
  5. Upload latest to tazpod/vault/vault.tar.aes with content-sha256 metadata (overwrite)
  6. Prune: ListObjects → keeps max N copies (default 50), DeleteObjects oldest

History/retention only on manual push — the sync daemon does not create history copies.

AWS Enclave Bridge

On unlock, the vault’s .aws directory is bind-mounted to ~/.aws:

/home/tazpod/secrets/.aws  →  /home/tazpod/.aws  (mount --bind)

This makes AWS credentials available to all tools (aws, s3, SDKs) while keeping them inside the RAM enclave. On lock, the bind mount is unmounted with sudo umount -l.

Identity Setup

SetupIdentity() creates these persistent directories under /workspace/.tazpod/:

  • .pi — Pi agent config
  • .omp — OMP state
  • .gemini — Gemini CLI sessions
  • .claude — Claude CLI state
  • .aws — AWS SSO cache (persistent across container recreations)
  • .opencode — OpenCode config + data + state

See Also