host / sbl1scope / plaintext credentials2026-05-16
audit report — sbl1 — 2026-05-16

Plaintext credentials,
on disk & in logs.

Plaintext credentials on disk, conformance to the sbl0-only vault rule, and exfiltration risk from local logs and backups.

15critical
06high
03medium
02low
§ 01

Vault is healthy

The architecture is correct. 5 systems verified — preserve these as you remediate the rest.
Vault is server-side on sbl0

~/bin/vault is a thin SSH shim into curious@sbl0:~/.vault/vault.sh. Plaintext never lives on sbl1; it stays in age-encrypted .env.age files on sbl0.

All wrapper scripts pull at exec time

kimi, kimi-cli, krishna-ask, openclaw-discord-gateway, and every user systemd unit call sbl-secret-env <ENV>=sbl0-<name>. No inline tokens in any service unit or wrapper.

sbl-vault-store uses age + SSH stream

New secrets are encrypted to age1y9qu5gsr8feuraym2682hr3vgqm59f85fslzlr3wppdtjr348eyq5v7nxy before they touch disk on sbl0. The plaintext only exists in RAM on sbl1 and inside the SSH tunnel.

Vault listing has zero sbl1-* entries

The 'sbl1 is the commit tier' rule is observed at the vault. All live entries are sbl0-*, sbl3-*, sbl4-*, or sbl10-*. Five legacy unprefixed entries remain.

too.foo workers correctly defer to wrangler secret put

wrangler.jsonc files annotate CF_API_TOKEN as 'set via wrangler secret put' — no inline keys. Repo .gitignore excludes .env, .env.*, .dev.vars, .wrangler/.

§ 02

Findings

24 findings across 8 categories. Each card declares the file owner and the live consumers so rotation can be sequenced without breaking dependent services.

Shell history leak

1 finding
critical~/.bash_history (lines 157-1541)

15+ commands echoed literal ghp_* and github_pat_* GitHub PATs (export GITHUB_TOKEN=…, gh auth login --with-token, scp …bashrc, etc.).

Manager
bash (no manager — pure history)
Consumers
  • none — historical record only
Rotation procedure
  1. Revoke each leaked PAT at github.com/settings/tokens (start with the most recent two).
  2. gh auth refresh -h github.com (writes a new oauth_token).
  3. history -c && shred -u ~/.bash_history && touch ~/.bash_history.

Plaintext credential file

11 findings
critical~/.config/gemini/api_key.txt

Plaintext AIza* Gemini API key (40 bytes).

Manager
manual / Gemini CLI
Consumers
  • any tool reading GEMINI_API_KEY env or this file
Rotation procedure
  1. Revoke key at Google AI Studio.
  2. Create new key.
  3. sbl-secret put sbl0-google-gemini-api-key <new>.
  4. rm ~/.config/gemini/api_key.txt; have callers fetch via sbl-secret-env.
critical~/.config/gh/hosts.yml

GitHub oauth_token in plaintext (standard gh CLI location).

Manager
gh auth login
Consumers
  • git push/pull via gh auth git-credential (configured in ~/.gitconfig)
  • every gh CLI call
Rotation procedure
  1. gh auth refresh -h github.com -s repo,workflow (issues a new token, revokes old).
  2. Verify: gh auth status.
  3. Optionally: sbl-secret put sbl0-github-pat <token from gh auth token>.
critical~/.config/rclone/rclone.conf

gdrive OAuth { access_token, refresh_token, expiry } in plaintext.

Manager
rclone config
Consumers
  • manual rclone copy/mount calls
Rotation procedure
  1. rclone config reconnect gdrive: (re-runs OAuth, writes new tokens).
  2. Old refresh_token can be revoked at myaccount.google.com/permissions.
critical~/.config/gcloud/application_default_credentials.json + legacy_credentials/*/adc.json

gcloud refresh_token + client_secret in plaintext for two identities.

Manager
gcloud auth login
Consumers
  • gcloud CLI, any GCP SDK that loads ADC
Rotation procedure
  1. gcloud auth revoke --all.
  2. gcloud auth login (writes new credentials.db / ADC).
  3. gcloud auth application-default login if needed.
critical~/.kaggle/access_token

KGAT_* Kaggle API key in plaintext (38 bytes).

Manager
Kaggle CLI / Kaggle website download
Consumers
  • ~/bin/run-kaggle-dashboard.sh
  • kaggle CLI
Rotation procedure
  1. kaggle.com → Account → Expire API Token, then Create New Token.
  2. Download replaces the file; alternatively store via sbl-secret put sbl0-kaggle-api-key.
critical~/.cloudflared/5774c5a1-3631-495b-afca-1daa84563fe7.json

Cloudflare tunnel TunnelSecret + AccountTag in plaintext.

Manager
cloudflared tunnel create
Consumers
  • the live cloudflared tunnel routing rahul.dhruvgaba.com → ssh://localhost:22
Rotation procedure
  1. cloudflared tunnel rotate <tunnel-name> — zero-downtime rotation.
  2. Daemon picks up new <uuid>.json automatically.
critical~/.cloudflared/cert.pem

Argo Tunnel token (-----BEGIN ARGO TUNNEL TOKEN-----).

Manager
cloudflared tunnel login
Consumers
  • cloudflared tunnel create/delete commands
Rotation procedure
  1. Only needed if you manage tunnels regularly; can be regenerated with cloudflared tunnel login.
critical~/.claude/.credentials.json

Anthropic OAuth access token (sk-ant-oat01-*) in plaintext.

Manager
claude /login
Consumers
  • this CLI session
  • every future Claude Code launch
Rotation procedure
  1. Do this LAST — risks interrupting in-flight remediation.
  2. claude /logout then claude /login.
  3. The old access_token is short-lived; the refresh path is re-established.
critical~/.codex/auth.json

ChatGPT OAuth id_token (JWT) + tokens object in plaintext.

Manager
codex login
Consumers
  • codex CLI
  • codex-screenshot-bridge.service
  • codex-project-screenshot-sync.service
Rotation procedure
  1. codex logout && codex login.
critical~/.openclaw/auth-profiles.json (+ agents/krishna/agent/auth-profiles.json + backups/pre-update-*/)

Anthropic OAuth token (sk-ant-oat01-*) in plaintext for openclaw.

Manager
openclaw auth
Consumers
  • openclaw-gateway.service
  • krishna-proxy.service
  • krishna-ask wrapper
Rotation procedure
  1. systemctl --user stop openclaw-gateway krishna-proxy.
  2. openclaw auth login (writes new auth-profiles.json).
  3. systemctl --user start krishna-proxy openclaw-gateway.
  4. Verify: krishna-ask 'hello' replies normally.
critical~/.antigravity-server/.15487b30…token

Numeric session token in plaintext.

Manager
antigravity bootstrap
Consumers
  • antigravity runtime
Rotation procedure
  1. Re-bootstrap antigravity to issue a new token.

SSH key hygiene

3 findings
critical~/.ssh/id_ed25519

Unencrypted private key (no passphrase). Same key authenticates to sbl0, sbl1, sbl2, sbl3, sbl4, sbl10, vast-2x5090, and vast.

Manager
ssh-keygen / manual
Consumers
  • vault SSH calls (sbl-secret get/put → curious@sbl0)
  • every fleet shell + git remote (git@github.com:…)
  • sbl-vault-store
Rotation procedure
  1. Generate ~/.ssh/id_ed25519.new with a passphrase.
  2. ssh-copy-id -i id_ed25519.new.pub to every fleet host while old key still works.
  3. Test new key end-to-end against each host before swapping.
  4. Atomic swap; ssh-add for gcr-ssh-agent.
  5. Remove old pubkey from each host (sed -i on authorized_keys).
  6. sbl-secret put sbl0-curious-ssh-ed25519-private < id_ed25519; shred old key.

Reverse order = locks you out of sbl0 = loses vault access.

critical~/.ollama/id_ed25519

Unencrypted private key for ollama identity.

Manager
ollama install (legacy)
Consumers
  • likely unused — verify with grep -r '.ollama/id_ed25519' ~
Rotation procedure
  1. If unused: shred and remove.
  2. If used: regenerate with passphrase.
critical~/.local/dev-tls/key.pem

Unencrypted OpenSSH-format private key (dev TLS).

Manager
manual / mkcert?
Consumers
  • verify with grep -r 'dev-tls' ~
Rotation procedure
  1. If mkcert-managed: re-issue.
  2. If hand-rolled: regenerate or remove if unused.

Active agent session logs

2 findings
high~/.claude/history.jsonl + projects/-home-curious/*.jsonl

Multiple Gemini AIza* keys and Anthropic sk-ant-oat01-* tokens appear verbatim inside Claude session transcripts (the agent streamed credential files into its context).

Manager
claude harness (append-only)
Consumers
  • none for auth — these are transcripts
Rotation procedure
  1. Only after the underlying keys are rotated and revoked.
  2. Per-token sed scrub (use the helper script scrub-history).
  3. Or, rotate the relevant Claude project IDs and discard the older jsonl files.
high~/.codex/sessions/2026/*/*/*.jsonl

Older Codex session rollouts (Feb-Apr 2026) contain hf_*, gsk_*, xai-*, sk-ant-*, AIza* fragments.

Manager
codex harness
Consumers
  • none for auth
Rotation procedure
  1. Older rollouts are no longer needed for replay — safe to delete after rotation.
  2. rm -rf ~/.codex/sessions/2026/0[2-4] (after confirming nothing depends on them).

Stale credential dump on disk

2 findings
high~/workspace/antimony-labs-org-nondashboard-20260423-185551/backups/sbl2-migration/sbl2-backup-wipe-prep-20260404/

82 GB full disk dump of sbl2. Contains an unencrypted id_ed25519, .bashrc exporting ANTHROPIC_API_KEY=sk-ant-api03-…, an older .claude/.credentials.json, Codex shell snapshots, 20+ openclaw agent auth-profiles.json files, and Downloads/Chase exports with hf_* tokens.

Manager
(snapshot — no live owner)
Consumers
  • none
Rotation procedure
  1. Pure dead data — safe to delete.
  2. Move to a quarantine dir first if you want to inspect: mv …/sbl2-backup-wipe-prep-20260404 /tmp/quarantine/.
  3. If you must keep, re-archive as a single age-encrypted blob on sbl0.
high…/sbl2-migration/sbl2-backup-20260418-133603/ (70 MB) + …-133643/ (116 MB)

Sibling sbl2 backups; each contains .bash_history + .bashrc with sk-ant-api03-* tokens.

Manager
(snapshot)
Consumers
  • none
Rotation procedure
  1. Same as above — safe to delete.

Vault hygiene

3 findings
mediumsbl0 vault: github-token, discord-bot-token, cloudflare-global-api-key, contabo-deploy-ssh-key, ssh-ed25519-private

Five unprefixed entries violate the sbl0-* convention. Wrapper scripts already use `sbl0-…|legacy` fallback chains, so these are alive on purpose.

Manager
sbl-secret put / sbl-secret delete
Consumers
  • openclaw-discord-gateway (discord-bot-token fallback)
  • repo_policy.py (github-token)
  • any future deploy script (contabo-deploy-ssh-key, ssh-ed25519-private)
Rotation procedure
  1. Migrate each: sbl-secret get <legacy> | sbl-secret put sbl0-<scope>-<purpose>.
  2. Edit wrappers to drop the |legacy fallback once consumers verified.
  3. sbl-secret delete <legacy>.
medium~/console/registry/secrets.tsv (5 rows with name starting `sbl1-`)

Names 5 sbl1-* secrets (krishna groq/xai, openclaw discord/gateway/ollama). None of these exist in the live vault, but the file documents the forbidden prefix.

Manager
manual TSV edit
Consumers
  • dashboard data — informational only
Rotation procedure
  1. Delete the 5 sbl1-* rows from secrets.tsv.
medium~/bin/openclaw-discord-status

References sbl1-openclaw-discord-token + sbl1-openclaw-gateway-token. Both keys are absent from vault, so the script fails at runtime.

Manager
manual script
Consumers
  • interactive use only
Rotation procedure
  1. Rewrite to use the sbl0-…|fallback pattern used by openclaw-discord-gateway, or delete.

Trust boundary

1 finding
low~/.ssh/config (vast-2x5090, vast)

The same SSH key that authenticates to sbl0 (vault host) is also used to log into rented vast.ai root-on-public-IP instances.

Manager
ssh-keygen / manual
Consumers
  • see ssh-id-ed25519
Rotation procedure
  1. Use a separate key (vast_id_ed25519) when renting vast.ai compute.
  2. Configure ssh-config with IdentityFile per Host.

Dashboard drift

1 finding
low~/console/src/data/platform.ts (line 491)

platform.ts asserts 'local OpenClaw/Kaggle/GitHub token files removed'. Audit shows ~/.kaggle/access_token, ~/.config/gh/hosts.yml, and ~/.openclaw/auth-profiles.json still exist.

Manager
dashboard data
Consumers
  • dashboard home view
Rotation procedure
  1. After the underlying files are removed or migrated, update the claim or replace it with a live-derived check.
§ 03

Staged remediation

Lowest blast-radius first. Each stage is reversible up to the moment a secret is revoked at the provider.
  1. Free wins — no rotation needed

    blast: none

    Pure dead data and dead references. No live consumer reads any of these.

    • Delete sbl2-backup-wipe-prep-20260404/ (82 GB).
    • Delete the two smaller sbl2-migration backups.
    • Delete the 5 `sbl1-*` rows from registry/secrets.tsv.
    • Fix or delete ~/bin/openclaw-discord-status.
  2. Low-risk API key rotations

    blast: single tool

    Verify the rotation methodology on credentials with one consumer before touching anything cross-cutting.

    • Kaggle KGAT_* — only the dashboard wrapper reads it.
    • Gemini AIza* — confirm consumers first; replace api_key.txt content via vault-fetched env.
    • HuggingFace hf_* if any are live (most appearances were in old backups).
  3. Medium-risk: Cloudflare + Google

    blast: deploy scripts; rclone copy

    Cloudflared tunnel rotation is online; gcloud/rclone re-auth is one-shot.

    • sbl-secret put new sbl0-cloudflare-api-token, then revoke old in CF dashboard.
    • cloudflared tunnel rotate <name> — daemon picks up new secret automatically.
    • gcloud auth revoke --all && gcloud auth login.
    • rclone config reconnect gdrive:.
  4. GitHub PATs

    blast: git push/pull until refresh completes

    Most painful daily-use disruption; do it deliberately, not at end of day.

    • Revoke leaked PATs visible in bash_history (newest first).
    • gh auth refresh -h github.com.
    • Verify: gh auth status; gh repo list -L 1.
  5. Anthropic / Codex / OpenClaw OAuth

    blast: Krishna offline for the openclaw step

    Each manager owns its own file; re-auth swaps the token without disrupting other agents.

    • claude /logout && claude /login (do this last in the session).
    • codex logout && codex login.
    • systemctl --user stop openclaw-gateway krishna-proxy → openclaw auth login → restart.
    • Re-bootstrap antigravity.
  6. SSH key replacement

    blast: one wrong step = locked out of sbl0

    The vault depends on SSH to sbl0. Deploy new key first, test on every host, then swap.

    • ssh-keygen new key with passphrase under ~/.ssh/id_ed25519.new.
    • ssh-copy-id new pubkey to sbl0, sbl1, sbl2, sbl3, sbl4, sbl10, vast hosts.
    • Verify new key works against every host.
    • Atomic swap locally; ssh-add to gcr-ssh-agent.
    • Remove old pubkey from every host's authorized_keys.
    • sbl-secret put sbl0-curious-ssh-ed25519-private and shred old key.
  7. Vault hygiene

    blast: wrapper falls back to legacy until updated

    Migrate the 5 unprefixed entries to sbl0-*. Wrappers already have fallback chains, so this is safe iff scripts are updated first.

    • Migrate github-token → sbl0-github-pat; update repo_policy.py.
    • discord-bot-token already has sbl0-openclaw-discord-token — drop the |fallback in openclaw-discord-gateway.
    • Same pattern for cloudflare-global-api-key and contabo-deploy-ssh-key.
    • After confirmation: sbl-secret delete <legacy> for each.
  8. Prevention

    blast: none

    Stop the next leak before it happens.

    • Add HISTIGNORE='*ghp_*:*sk-ant-*:*AIza*:*hf_*:*gsk_*:*xai-*:*KGAT_*' to .bashrc.
    • Add a DEBUG/PROMPT_COMMAND trap that refuses `export *_TOKEN=…` and `gh auth login --with-token <literal>`.
    • Audit every project .gitignore for .env, .dev.vars, credentials.json.
    • Optional: install gitleaks or pre-commit secret-detection.

Operating principle

The file is the credential, not the master copy. Revoke at provider → re-auth via the manager → verify → delete prior copies and history. The vault is a backup of the rotated value, not the live source.