Skip to content

Bitwarden environment audit — LastVet, Angel, Sovereign (full)

Date: 2026-08-10
Companion: BITWARDEN_NAMING_CONVENTION.md, BITWARDEN_HYGIENE_PLAN_FULL.md
Live inventory: bws secret list per project (read-only audit)

Planning only. No secrets were modified.


Executive inventory

Project Secret count Runtime repo Primary deploy target today
lastvet 32 last.vet-api, last.vet-ios, edge scripts AWS ECS (+ R940 in progress)
angel 43 angel (Workers + Pages) Cloudflare Workers / Neon
sovereign 0 Sovereign (Tauri web/iOS) Local .env files (no BWS yet)
proxmox (adjacent) 2 infra automation Not product runtime
last1-platform (planned) 0 → ~6 cross-product N/A
Metric Count
Total secrets in product projects (lastvet + angel + sovereign) 75
Meridian BWS secrets 0 (local .env only)
Distinct runtime env names — Meridian harness 10
Distinct runtime env names — LastVet API 92
Distinct runtime env names — Angel API Worker ~45
Distinct build env names — Sovereign client 4
Cross-product secrets identified 4–6
Legacy *.railway.internal pointers (lastvet) 6
Legacy unicorn.*:7000 pointers (sovereign docs/examples) 4 files
Legacy neon.tech pointer (angel DATABASE_URL) 1

Ryan decisions applied in this audit (2026-08-10)

Topic Decision
Staging crypto Copy from AWS (no rotation until prod cutover)
Cross-product keys New last1-platform project for LAST1_TOKEN_ENCRYPTION_KEY, ONSET_IDENTITY_ASSERTION_SECRET
Imaging / MinIO Defer to Task 2 — no MinIO/ZFS env vars in LastVet API yet
R940 migration DB URL Store full URLs: LASTVET_R940_STAGING_MIGRATION_DATABASE_URL, LASTVET_R940_PROD_MIGRATION_DATABASE_URL (lastvet_master)
APNS Skip for staging cutover; follow-up when Ryan scopes iOS push
Staging JWTs Explicit _STAGING and _PROD BWS entries, values copied from AWS

Part 1 — LastVet (last.vet-api)

Detailed tables were first delivered in PR #8 (docs/lastvet/BITWARDEN_ENV_AUDIT.md). Summary below.

Sources

  • src/config/environment.ts, src/config/database.ts, src/lib/secureLinkTokens.ts
  • infra/modules/compute/main.tf — 49 ECS-wired keys
  • .env.example, docs/deployment/DEV_STAGING.md
  • BWS project lastvet — 32 keys

Startup requirements

Severity Vars
Hard fail UPLOAD_DIR when NODE_ENV=production
Required DATABASE_APP_URL (or DATABASE_URL fallback) for DB routes
Feature degrade SendGrid, Stripe, APNS, proof drainer, Google OAuth, waitlist (503 or skip)

BWS ↔ runtime mapping (live 32 keys)

BWS key API consumes? Issue
DATABASE_APP_URL_STAGING / _PRODUCTION Maps to DATABASE_APP_URL Legacy Railway *.railway.internal
DATABASE_APP_URL_R940_STAGING / _PROD Maps to DATABASE_APP_URL Correct host; rename to convention
MIGRATION_DATABASE_URL_* (×2) Maps to MIGRATION_DATABASE_URL Legacy Railway
LASTVET_APP_PASSWORD_* (×2) Provision scripts only Ambiguous; duplicates URL password
LASTVET_R940_POSTGRES_* (×2) Provision only Need composed migration URLs
LASTVET_R940_MINIO_*, ZFS_IMAGING_KEY Infra / ZFS layer Task 2 infra
LASTVET_R940_{STAGING,PROD}_IMAGING_MASTER_KEK API envelope KEK (IMAGING_MASTER_KEK) Envelope encryption v1
LASTVET_R940_{STAGING,PROD}_MINIO_IMAGING_{ACCESS,SECRET}_KEY MinIO bucket-scoped SA → IMAGING_S3_* via restore alias Envelope encryption v1
LASTVET_R940_{STAGING,PROD}_IMAGING_STORAGE_MODE Opt-in minio_envelope Staging first
LASTVET_R940_CLOUDFLARED_* (×2) Edge systemd Not API
VETERAN_JWT_SECRET, SECURE_LINK_HMAC_SECRET, OAUTH_JWT_SECRET, PROVIDER_JWT_SECRET Yes Single undifferentiated copy; need _STAGING/_PROD
LAST1_TOKEN_ENCRYPTION_KEY Yes Move to last1-platform
ONSET_IDENTITY_ASSERTION_SECRET Yes Move to last1-platform
LAST1_WEBHOOK_SECRET, LAST1_PROOF_DRAINER_SECRET, LASTVET_* auth secrets Yes Need env + target in name
VETERAN_SESSION_EXCHANGE_SECRET Yes Shared with iOS
VA_OPEN_DATA_API_KEY, VA_FORMS_API_KEY No (API) iOS / parked
E2E_OTP_IMAP_* (×3) No (API runtime) Used by last.vet-api/scripts/e2e-roi-gate0-full.sh harness
CLOUDFLARE_ACCOUNT_ID No (API) Shared CI; move to last1-platform

Missing from BWS (AWS SM only, ~30 keys)

Terraform app_secret_keys includes SendGrid, Stripe, Google OAuth, portal URLs, waitlist, LAST1_ID_OAUTH_*, etc. ECS is authoritative; BWS cannot reconstruct a full staging/prod env today.

LastVet env var count by category

Category Count
Infrastructure / process 10
Database 4
Auth / Last1 / OAuth 28
Email (SendGrid) 5
Stripe 7
APNS 5
Secure access 10
Waitlist / alerts 18
Imaging (AWS + reaper) 5
Total distinct names 92

Part 2 — Angel (angel)

Sources

  • apps/api/src/worker.ts — Worker env interface
  • packages/ai/src/config.ts — Ollama lane
  • config/secrets.manifest.yaml, .env.example
  • scripts/secrets-push-wrangler.sh — production allowlist
  • BWS project angel — 43 keys

Runtime env vars (Angel API Worker + local Hono)

Required for Worker boot

Var Purpose BWS key today Required
DATABASE_URL Neon Postgres (Hyperdrive in prod) DATABASE_URL Yes (503 if missing)
SESSION_SECRET Cookie/session signing SESSION_SECRET Yes

Storage (R2 / S3)

Var Purpose BWS key Notes
R2_ACCESS_KEY_ID Cloudflare R2 R2_ACCESS_KEY_ID Prod Worker
R2_SECRET_ACCESS_KEY R2 secret R2_SECRET_ACCESS_KEY Prod Worker
R2_ENDPOINT R2 endpoint URL R2_ENDPOINT Prod Worker
R2_API_TOKEN R2 API token R2_API_TOKEN Manifest; not in wrangler allowlist
S3_BUCKET Bucket name (mapped to R2) S3_BUCKET Wrangler allowlist uses S3_BUCKET
S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY Local MinIO dev Same names in BWS Should not be in prod BWS
MINIO_ROOT_USER, MINIO_ROOT_PASSWORD Local compose Same names Local-only pollution in BWS
VAULT R2 binding alias Worker binding, not BWS

Ollama local lane (Phase 2+ research)

Var Purpose BWS key Notes
OLLAMA_BASE_URL Ollama HTTP base OLLAMA_BASE_URL Value is pending in BWS today; docs referenced unicorn + CF Access
OLLAMA_MODEL Model tag OLLAMA_MODEL e.g. qwen2.5:7b
OLLAMA_TIMEOUT_MS Request timeout OLLAMA_TIMEOUT_MS
OLLAMA_EMBED_MODEL Embedding model Code default nomic-embed-text; not in BWS
CF_ACCESS_CLIENT_ID Cloudflare Access (tunnel) CF_ACCESS_CLIENT_ID May become optional when using Tailscale inference-01
CF_ACCESS_CLIENT_SECRET CF Access secret CF_ACCESS_CLIENT_SECRET Same

External APIs

Var BWS In wrangler allowlist
ANTHROPIC_API_KEY Yes Yes
NCBI_API_KEY Yes Yes
SEMANTIC_SCHOLAR_API_KEY Yes Yes
SENDGRID_API_KEY Yes Yes

Billing

Var BWS Notes
STRIPE_SECRET_KEY Yes
STRIPE_WEBHOOK_SECRET Yes
STRIPE_PRICE_ID Yes
STRIPE_PORTAL_CONFIGURATION_ID Code reads; not in BWS
STRIPE_SUCCESS_PATH, STRIPE_CANCEL_PATH, STRIPE_PORTAL_RETURN_PATH Defaults in code

Google / Apple auth

Var BWS
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI Yes
APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID, APPLE_PRIVATE_KEY, APPLE_REDIRECT_URI Yes
APPLE_IOS_CLIENT_ID — (default report.angel.app)
WEB_ORIGIN, WEBAUTHN_RP_ID Yes

Admin / misc

Var BWS Notes
ANGEL_INTERNAL_ADMIN_SECRET Yes last1-admin BFF only
COUNCIL_PANEL_JSON Wrangler allowlist; optional runtime override
NEXT_PUBLIC_API_URL Yes Public URL stored as secret (unusual)
CLOUDFLARE_API_TOKEN, CF_ACCOUNT_ID Yes Cross-project naming drift vs CLOUDFLARE_ACCOUNT_ID in lastvet
PORT, POSTGRES_*, REDIS_URL Yes Local dev keys in BWS

Web client (build-time, Cloudflare Pages)

Var Source
NEXT_PUBLIC_API_URL Pages env / BWS

Test / dev only

Var Source
ANGEL_LIVE_EVAL Eval harness (packages/ai/evals)

Angel BWS hygiene issues (43 keys)

Category Keys Detail
Legacy cloud pointer DATABASE_URL Host on neon.tech (pre-R940; Angel prod still Cloudflare)
Legacy Ollama path OLLAMA_BASE_URL Placeholder pending; historical docs used unicorn + CF Access
Local dev in prod vault POSTGRES_*, MINIO_*, REDIS_URL, PORT, S3_* Docker-compose keys should not sync to Worker
Ambiguous scope Most keys lack ANGEL_CLOUDFLARE_PROD_ prefix Cannot grep by target
Wrangler/BWS drift COUNCIL_PANEL_JSON, R2_API_TOKEN, STRIPE_PORTAL_*, APPLE_IOS_CLIENT_ID In code or allowlist, missing from BWS
Public value as secret NEXT_PUBLIC_API_URL Low sensitivity but clutters vault
Cross-project duplicate naming CF_ACCOUNT_ID vs lastvet CLOUDFLARE_ACCOUNT_ID Same account, two names
Orphan in allowlist push S3_* pushed if present Script deletes stale Worker secrets but BWS still holds them

Angel runtime env count

Category Count
Required core 2
Storage 8
Ollama + CF Access 6
External APIs 4
Billing 6
Auth (Google + Apple + WebAuthn) 10
Admin / council 2
Local dev (should not be prod) 9
Total distinct names ~45

Part 3 — Sovereign (Sovereign)

Sources

  • src/lib/engine-connection.ts, vite.config.ts, src/vite-env.d.ts
  • .env.example, .env.mac.example, .env.ios.example
  • BWS project sovereignempty

Build-time env vars (Vite / Tauri)

Var Purpose BWS today Required for client
VITE_ENGINE_URL Remote Odysseus engine HTTPS base — (.env files) Yes for Mac/iOS remote
VITE_ENGINE_URL_LOCAL Local engine fallback Yes
VITE_BASE_PATH Vite base path Optional (default /)
TAURI_DEV_HOST Tauri dev server host Dev only

Legacy host pointers (not in BWS)

Location Value pattern Status
.env.mac.example, .env.ios.example https://unicorn.tailc3bbdc.ts.net:7000 Retired host per R940 plan
docs/ios-signing.md, README.md Same unicorn references Docs drift
src/data/ecosystem-registry.json unicorn_ryda entity Product metadata, not runtime secret

Target (Ryan plan): R940 engine at https://<sovereign-engine-host>:7000 (exact hostname TBD; not yet in BWS). Ollama for Sovereign lane: http://inference-01.tailc3bbdc.ts.net:11434 as SOVEREIGN_R940_SHARED_OLLAMA_BASE_URL when wired.

Sovereign BWS hygiene

Issue Detail
Empty project All secrets live in local gitignored .env on operator machines
No naming convention .env uses raw VITE_* without product prefix in storage
Legacy unicorn Examples point at decommissioned Windows PC stack

Part 4 — Cross-product secrets

Current location Secret Recommendation Rationale
lastvet LAST1_TOKEN_ENCRYPTION_KEY Movelast1-platform as LAST1_PLATFORM_STAGING_* + LAST1_PLATFORM_PROD_* Shared platform key; per-env copies (copy AWS values)
lastvet ONSET_IDENTITY_ASSERTION_SECRET MoveLAST1_PLATFORM_SHARED_ONSET_IDENTITY_ASSERTION_SECRET Shared with Onset Supabase
lastvet CLOUDFLARE_ACCOUNT_ID MoveLAST1_PLATFORM_SHARED_CLOUDFLARE_ACCOUNT_ID Used by multiple web repos
angel CLOUDFLARE_API_TOKEN Move or duplicatelast1-platform Shared deploy token candidate
angel CF_ACCOUNT_ID Merge with above account ID Duplicate naming
angel OLLAMA_BASE_URL RenameANGEL_R940_SHARED_OLLAMA_BASE_URL; set R940 inference URL Product-scoped Ollama
(none) LASTVET_R940_SHARED_OLLAMA_BASE_URL Create in lastvet Reserved; LastVet API does not consume yet
(none) SOVEREIGN_R940_SHARED_OLLAMA_BASE_URL Create in sovereign Future Sovereign / Odysseus lane
proxmox ANTHROPIC_API_KEY Keep separate Infra automation; not same lifecycle as Angel Worker key

Ollama endpoint policy (Ryan)

Product BWS key Value
Angel ANGEL_R940_SHARED_OLLAMA_BASE_URL http://inference-01.tailc3bbdc.ts.net:11434
LastVet LASTVET_R940_SHARED_OLLAMA_BASE_URL Same (reserved; Meridian/Ollama not in LastVet API Phase 1)
Sovereign SOVEREIGN_R940_SHARED_OLLAMA_BASE_URL Same

Remove dependency on unicorn for Ollama. CF Access pair (CF_ACCESS_CLIENT_*) becomes optional when Tailscale reaches inference-01 directly.


Part 5 — E2E OTP IMAP secrets (E2E_OTP_IMAP_*)

Question Answer
Used by iOS? No — no references in last.vet-ios
Used by LastVet API runtime? No — not in environment.ts
Used by automation? Yeslast.vet-api/scripts/e2e-roi-gate0-full.sh, scripts/lib/fetch-e2e-otp-inbox.sh
Mailbox Dedicated e2e-harness@last1.enterprises (harness-only, not prod OTP logging)

Recommendation: Rename to LASTVET_TEST_E2E_OTP_IMAP_* short-term; move to new last1-test BWS project when created. Not blocking LastVet API deploy if harness scripts updated to new names.


Part 6 — PHI / PII in secret names and notes

Audit of all 75 product-project secret names and notes (not values):

Finding Count
PHI in names/notes 0
PII in names/notes 0
Operator email in notes 1 (E2E_OTP_IMAP_USER note: "Not ryan@" — acceptable)

Secret values contain database URLs and credentials (expected). Values must never appear in git docs.


Part 7 — Meridian (last1-meridian)

Audit boundary

In scope Out of scope (this pass)
Python eval harness (src/meridian/), CLI, providers Hosted Meridian API on LastVet staging/prod (parked until PHI gate)
Fixture scripts (scripts/fetch_sandbox_fixtures.py) VA production credentials
.env.example local dev pattern CI (mock provider — no secrets in GitHub Actions)

Bitwarden today: No meridian project and no keys in any BWS project. Secrets are local .env only (gitignored).

Runtime env vars

Var Purpose BWS today Required when
OLLAMA_BASE_URL Ollama native provider meridian run --provider ollama
OLLAMA_MODEL Model tag Same
MERIDIAN_API_BASE OpenAI-compat shim base --provider api
MERIDIAN_API_KEY API key for shim Same
MERIDIAN_API_MODEL Model name override Same (default mistral-small)
VA_SANDBOX_ACCESS_TOKEN Fixture fetch fetch_sandbox_fixtures.py only
VA_SANDBOX_PERSONA_ID Persona slug Same
VA_SANDBOX_FHIR_BASE FHIR base URL Same (default sandbox URL)
VA_SANDBOX_PATIENT_ID Patient id override Optional
MERIDIAN_TELEMETRY_URL Telemetry POST endpoint Optional
MERIDIAN_TELEMETRY_TOKEN Telemetry auth Optional

Count: 10 distinct env names (11 with optional patient id default).

Legacy pointers

Location Pattern Issue
.env.example OLLAMA_BASE_URL http://192.168.1.40:11434 LAN IP; align to inference-01.tailc3bbdc.ts.net:11434 per platform plan

Convention alignment

Current Proposed BWS key Maps to runtime
(none) MERIDIAN_R940_SHARED_OLLAMA_BASE_URL OLLAMA_BASE_URL
(none) MERIDIAN_R940_SHARED_OLLAMA_MODEL OLLAMA_MODEL
(none) MERIDIAN_R940_DEV_API_BASE MERIDIAN_API_BASE (local shim)
(none) MERIDIAN_TEST_VA_SANDBOX_ACCESS_TOKEN VA_SANDBOX_ACCESS_TOKEN (short-lived; rotate often)

Recommend new Bitwarden project meridian (not last1-platform) because VA sandbox tokens and eval telemetry are Meridian-owned lifecycle.


Part 8 — Audit scope boundaries (Sub-task F)

This section confirms what was audited on agent-01 vs what is in scope by architecture but not cloned in the workspace.

Summary matrix

Product Repo audited Sub-components in repo Sub-components out-of-repo (documented only) BWS project
LastVet last.vet-api (+ manifest targets ios/web) API runtime, E2E harness scripts last.vet-ios, last.vet-web, last.vet-provider via manifest only lastvet (32)
Angel angel (full monorepo) API Worker, local Hono, @angel/ai, web Pages, iOS app, operator scripts Cloudflare Hyperdrive/R2 bindings (platform, not env vars) angel (43)
Sovereign Sovereign Tauri/Vite client (VITE_* build vars) Odysseus (FastAPI engine), MemPalace (pgvector data plane) — separate repos, not on agent-01 sovereign (0)
Meridian last1-meridian CLI, eval harness, providers, fixture scripts None (none)

Angel — sub-component coverage

Sub-component Path Env mechanism Audited vars
API Worker (prod) apps/api/src/worker.ts, routes Wrangler secrets (pushed from BWS) DATABASE_URL, SESSION_SECRET, R2, Stripe, Apple, Google, Ollama, SendGrid, etc.
API local (dev) apps/api/src/index.ts .env via secrets-pull.sh Same runtime names as Worker
@angel/ai / Ollama lane packages/ai/src/config.ts Worker secrets + secrets-run.sh for batch OLLAMA_*, CF_ACCESS_*, OLLAMA_EMBED_MODEL (code only)
Digest cron apps/api/src/digest/cron.ts Worker env ANTHROPIC_API_KEY, NCBI_API_KEY, DATABASE_URL
Web (Pages) apps/web/, deploy-web-pages.sh Build-time NEXT_PUBLIC_* from BWS or default NEXT_PUBLIC_API_URL, deploy tokens
Web Worker (deprecated) wrangler.web.toml Legacy; superseded by Pages Not primary
iOS app apps/ios/Angel/Config/AngelConfig.swift Hardcoded prod URLs; scheme override ANGEL_API_URL No BWS sync today
Operator batch / QA scripts/*.ts, secrets-run.sh bws run --project-id angel Inherits full BWS .env dump
Live eval harness packages/ai/evals/live-harness.test.ts ANGEL_LIVE_EVAL=1 + ANTHROPIC_API_KEY Test-only

Angel-local (R940 VM, planned): Same angel BWS project; LAN Ollama URL should live in non-secret /etc/angel/local.env while Worker keeps tunnel URL in BWS (see ANGEL_ARCHITECTURE_V2.md).

Sovereign — sub-component coverage

In workspace: Sovereign Tauri client (Sovereign repo)

Surface Env vars Runtime vs build
Vite/Tauri build VITE_ENGINE_URL, VITE_ENGINE_URL_LOCAL, VITE_BASE_PATH Build-time (baked into client bundle)
Tauri dev TAURI_DEV_HOST Dev only
Runtime None — client reads import.meta.env.VITE_* only HTTPS calls to Odysseus engine

No server-side secrets in the Sovereign UI repo. Session token is localStorage (sov_odysseus_session), not BWS.

Out of workspace: Odysseus + MemPalace (in scope by architecture, not code-audited here)

Repos: Referenced as thelast1org/odysseus and MemPalace tooling in ecosystem docs; not present on agent-01. Env inventory below is from SOVEREIGN_ARCHITECTURE_V2.md and Sovereign UI references to odysseus/.env.

Sub-component Expected env source Key vars (planned) BWS project
Odysseus engine bws run → systemd on sovereign-app SOVEREIGN_DB_URL, OLLAMA_BASE_URL, OLLAMA_MODEL, ANTHROPIC_API_KEY, MONDAY_API_TOKEN, GOOGLE_*, ODYSSEUS_ADMIN_PASSWORD sovereign
MemPalace data plane sovereign-mem Postgres SOVEREIGN_DB_URL / MEMPALACE_PGVECTOR_DSN, import: MEMPALACE_IMPORT_SOURCE sovereign
MCP host subprocess on app VM Same Odysseus env + tool-specific tokens sovereign
Sovereign client build script maps BWS → VITE_* SOVEREIGN_R940_SHARED_ENGINE_URL sovereign

Follow-up: Re-run env grep on Odysseus and MemPalace repos when cloned on agent-01 before sovereign-app cutover. This audit treats architecture docs as provisional until code confirms.

Meridian — convention vs current pattern

Aspect Current Target
Secret storage Local .env only New BWS project meridian
Key naming Raw runtime names in .env.example MERIDIAN_R940_* / MERIDIAN_TEST_* in BWS
Shared Ollama Same LAN IP as other products MERIDIAN_R940_SHARED_OLLAMA_BASE_URL → maps to OLLAMA_BASE_URL at run time
CI mock provider, no secrets Unchanged

Meridian does not share LastVet's lastvet BWS project. Cross-product overlap is inference-01 Ollama URL only (same host, product-prefixed keys).


Part 9 — Remaining ambiguities (Ryan input before execution)

# Topic Options
1 CLOUDFLARE_API_TOKEN Single token in last1-platform vs per-product deploy tokens
2 Sovereign engine URL on R940 Exact hostname/port for Odysseus engine replacing unicorn:7000
3 LAST1_TOKEN_ENCRYPTION_KEY per env Confirm staging and prod values differ (AWS SM suggests yes) — both copied to platform project
4 Angel DATABASE_URL on R940 Angel stays on Cloudflare/Neon for now, or future ANGEL_R940_* Postgres?
5 COUNCIL_PANEL_JSON Add to BWS vs keep as Wrangler-only secret
6 CF Access for Ollama Retain CF_ACCESS_CLIENT_* for defense-in-depth, or drop when Tailscale-only
7 last1-test project Create now vs keep LASTVET_TEST_* in lastvet until Gate 0.5 E2E exit
8 Odysseus repo audit Clone on agent-01 before sovereign-app deploy to validate env list
9 Meridian BWS project New meridian project vs subset of keys under last1-platform
10 Angel iOS BWS Keep hardcoded URLs vs add ANGEL_IOS_* keys to BWS

References

  • last.vet-api/src/config/environment.ts
  • last.vet-api/infra/modules/compute/main.tf
  • angel/config/secrets.manifest.yaml
  • angel/scripts/secrets-push-wrangler.sh
  • Sovereign/src/lib/engine-connection.ts
  • last1-meridian/.env.example, src/meridian/provider/*.py
  • SOVEREIGN_ARCHITECTURE_V2.md, ANGEL_ARCHITECTURE_V2.md
  • PR #8: LastVet-only predecessor on branch bitwarden-hygiene-plan