eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– Secrets inventory
2
3The authoritative list of every server-side secret in the system (issue #550
4Part 12, operability item 11), with what each one authorizes, where it lives,
5its blast radius, and how to rotate it. Update this doc in the same PR that
6adds or retires a secret.
7
8**The boundary that matters most is what's *not* here.** User keypairs, thread
9keys, and the instance key are client-side only β€” the server never holds them,
10so they can't appear in this inventory. Every secret below is *auth plumbing*:
11compromising it lets an attacker impersonate infrastructure or vandalize
12state, but never decrypt a message. (The two scoped exceptions are bot
13identity keys and the Anthropic key, held by sidecars β€” machines that are
14deliberately *members*, never the server.)
15
16Operator-specific locations (which box, which env file, which 1Password item)
17live in `CLAUDE.local.md` and 1Password, not here. The storage and recovery
18scheme for these secrets β€” the sealed vault repo, and why it is deliberately
19private β€” is `docs/secrets-vault.md`.
20
21## Per-instance
22
23- **Admin token** (`LCHAT_ADMIN_TOKEN`) β€” authorizes roster management and the
24 admin console; explicitly not a decryption key. Server stores only its
25 hash. Lives in the instance's deployment env + 1Password. *Blast radius*:
26 one instance's membership (add/remove members, invites) β€” not its content.
27 *Rotate*: set a new token in the env and restart; saved admin links die,
28 recover with `lchat admin-link`.
29- **Push relay token** (`LCHAT_PUSH_RELAY_TOKEN`) β€” bearer auth to the APNs
30 relay. *Blast radius*: can send junk pushes as that instance. *Rotate*:
31 mint a replacement via the relay's admin API, swap the env, restart, revoke
32 the old β€” no Apple interaction needed.
33- **VAPID keypair** β€” Web Push self-identification, generated on first use
34 and stored in a 0600 sidecar keyfile next to the DB (`<db>.vapid`) β€”
35 deliberately *not* in the DB, so hibernation's S3 replica of `lchat.db`
36 never carries a private key (docs/no-plaintext.md Β§ 2; pre-keyfile
37 instances migrate the legacy `config` rows into the file on first boot).
38 Corollary: a hibernated instance restored onto a *different* box loses the
39 file and re-mints on wake β€” see *Rotate* for what that costs. *Blast
40 radius*: junk web pushes to that instance's subscribers. *Rotate*: delete
41 the keyfile and restart to regenerate; existing push subscriptions break
42 and members re-subscribe on next visit.
43
44## Home
45
46- **Session key** (`LCHAT_HOME_SESSION_KEY`) β€” HMAC ceremony proofs + sealed
47 WebAuthn session params (both keys derived from it with distinct labels).
48 Cannot open any vault. *Blast radius*: forge a 5-minute ceremony proof β€”
49 i.e. the destructive-op gate (device revocation, blob delete, sign-in-method
50 add/remove) for a user whose uid you already know; vault *content* stays
51 ciphertext. *Rotate*: swap the env and restart; in-flight ceremonies fail
52 once, harmless. Unset, home mints an ephemeral key per boot (self-hoster
53 default).
54- **Identity-anchor peppers** (`LCHAT_HOME_SIWA_PEPPER`,
55 `LCHAT_HOME_EMAIL_PEPPER`, per-provider `LCHAT_HOME_<PROVIDER>_PEPPER`) β€”
56 HMAC the provider's stable subject (or lowercased email) into the blind
57 index that maps a sign-in anchor to an account; identity anchors can never
58 decrypt. *Blast radius*: pepper plus a DB copy lets an attacker test
59 whether a *known* email/Apple ID has an account (membership probing) β€”
60 content stays sealed. *Rotate*: last-resort only β€” a new pepper orphans
61 every existing anchor of that kind; users re-link via a remaining sign-in
62 method.
63- **SES sender key** (`AWS_*` on home when the email door is on; IAM user
64 `ses-sender-production`, dev twin `ses-sender-dev`) β€” scoped to
65 `ses:SendEmail` from `sign-in@elevenmessenger.com` only. *Blast radius*:
66 send mail as the sign-in address (phishing paper). *Rotate*: standard IAM
67 two-key rotation. Note it shares the `AWS_*` env with the S3 store below β€”
68 an instance running both needs one IAM user carrying both scopes.
69- **GitHub OAuth client secret** (`LCHAT_HOME_GITHUB_CLIENT_SECRET`) β€” the
70 one sign-in door whose exchange home runs itself (github.go; the OIDC
71 providers verify id_tokens and need no secret). *Blast radius*: initiate
72 GitHub OAuth flows as the Eleven app β€” no stored user material to read.
73 *Rotate*: regenerate at the GitHub OAuth app, swap the env, restart; the
74 per-provider pepper (covered above) is untouched.
75- **S3 backend credentials** (`AWS_*` when `LCHAT_HOME_STORE=s3`) β€” object
76 access to the home bucket. *Blast radius*: read/overwrite vault ciphertext
77 and WebAuthn public credentials; versioning keeps the last 5, so vandalism
78 is recoverable. *Rotate*: standard IAM key rotation (mint second key, swap,
79 delete first).
80
81## Release & update chain
82
83- **Server release signing key** β€” signs the release feed the whole fleet
84 auto-updates from. **The highest-blast-radius secret in the system**: a
85 forger ships code to every instance. Kept offline (1Password), used only at
86 publish time via `LCHAT_RELEASE_KEY_FILE`. *Rotate*: instances verify
87 against a pinned public key, so rotation is a two-step β€” ship an update
88 (signed by the old key) that adds the new public key, then start signing
89 with the new one. Treat loss-of-control as an incident, not a rotation.
90- **Sparkle EdDSA key** (Mac app updates) β€” same shape and severity for the
91 Mac app population; public key pinned in `ios/project.yml`. Same two-step
92 rotation via a dual-trusted interim release.
93- **App Store Connect API key** β€” TestFlight uploads + notarization.
94 *Blast radius*: publish builds to the ASC account. *Rotate*: revoke + mint
95 in ASC β†’ Integrations.
96
97## Push relay
98
99- **APNs `.p8` signing key** β€” auth to Apple, not a decryption key; lives
100 only on the relay. *Blast radius*: send pushes as the app (Apple sees the
101 same ciphertext envelope regardless). *Rotate*: Apple allows two active
102 keys β€” mint the new one in the developer portal, swap the relay env,
103 restart, revoke the old.
104- **Relay admin token** β€” mints/revokes per-instance relay tokens. *Rotate*:
105 swap the env and restart the relay; existing instance tokens keep working.
106
107## Bots & integrations
108
109- **`ANTHROPIC_API_KEY`** β€” sidecar envs, plus (since the AI opt-in, #858)
110 `/data/control.env`: controld's attach-bot step writes it into each fresh
111 sidecar env it stages, so the provisioning daemon must hold it β€” bot code
112 still never runs in the server process, and no *instance* env ever carries
113 it. *Blast radius*: spend on the Anthropic account. *Rotate*: mint at the
114 Anthropic console, swap sidecar envs + control.env, restart sidecars and
115 `lchat-control` (note: a server release alone never restarts sidecars).
116- **Bot identity keys** β€” the deliberate exception: real E2EE member keys,
117 held in sidecar session state, able to decrypt exactly the threads the bot
118 was added to. *Rotate*: there is no key rotation β€” remove the bot member
119 (admin API) and re-provision a fresh identity via `add-bot`.
120- **Connect broker: admin token + provider OAuth client secrets + per-instance
121 broker tokens** β€” one client id/secret pair per OAuth-capable service in the
122 integrations registry (DNSimple under its legacy env names; every other
123 service β€” GitHub, Stripe, HubSpot, … β€” via `LCHAT_OAUTH_<ID>_CLIENT_ID` /
124 `_CLIENT_SECRET`). The broker persists no OAuth user material, so the client
125 secrets gate only the ability to *initiate* flows. *Rotate*: provider
126 secrets at the provider dashboard + broker env swap; instance tokens via the
127 broker admin API.
128
129## Provisioning & DNS
130
131- **DNS-01 token** (DNSimple, product account) β€” issues the wildcard cert
132 and controls product DNS. *Blast radius*: DNS takeover of product domains =
133 cert issuance for them; a top-tier secret. *Rotate*: mint a new account
134 token at DNSimple, swap the router env, restart the router, revoke the old.
135- **Cloud provisioner: admin token** (mints caller tokens), **caller tokens**
136 (e.g. home's), and **controld's root-only control token**. *Blast radius*:
137 create spaces up to quota/capacity caps (the caps are the real defense).
138 *Rotate*: admin/control tokens by env swap + restart; caller tokens via
139 the cloud admin API.
140
141## Billing (Stripe)
142
143- **Stripe secret key** (`LCHAT_STRIPE_SECRET_KEY`) β€” the **live** key the
144 cloud provisioner uses for space-upgrade Checkout, the Billing Portal, and
145 subscription webhook reconciliation. *Blast radius*: full API access to the
146 Stripe account β€” customer emails and subscription state, refunds,
147 cancellations. No message content (Stripe never sees the product), but the
148 worst non-signing secret to lose. *Rotate*: roll the key in the Stripe
149 dashboard (pick a grace window so the swap is zero-downtime), swap the cloud
150 env, restart.
151- **Stripe webhook signing secret** (`LCHAT_STRIPE_WEBHOOK_SECRET`) β€”
152 authenticates Stripe's event posts to the cloud webhook endpoint. *Blast
153 radius*: forge checkout/subscription events β†’ mint or revoke space
154 entitlements without payment; no access to the Stripe account itself.
155 *Rotate*: roll the endpoint's signing secret in the Stripe dashboard, swap
156 the cloud env, restart.
157- **Stripe Connect platform key + webhook secret**
158 (`LCHAT_STRIPE_CONNECT_KEY`, `LCHAT_STRIPE_CONNECT_WEBHOOK_SECRET`) β€”
159 per-*instance* env powering paid access levels (member subscriptions via
160 Stripe Connect). Deliberately tri-state: both unset β†’ feature off; a partial
161 config warns and stays off. *Blast radius*: the platform key can also act on
162 connected accounts (sellers' Stripe accounts) within the platform's
163 permissions; the webhook secret forges membership entitlements on that
164 instance. *Rotate*: same shapes as the cloud pair above.
165- **Enterprise Edition checkout key** (`LCHAT_STRIPE_ENTERPRISE_KEY`) β€” the
166 cloud env's *separate* Stripe key for the elevenmessenger.com/enterprise/
167 page (enterprise.go). Deliberately independent of
168 `LCHAT_STRIPE_SECRET_KEY` with no fallback: it holds the **sandbox** key
169 while real billing stays live, and the live flip is swapping this one
170 value. *Blast radius*: whatever account the key belongs to β€” sandbox β‰ˆ
171 none; if flipped live, same as the live key above. *Rotate*: roll in the
172 matching Stripe mode's dashboard, swap the cloud env, restart.
173- Kept alongside but **not secrets**: `LCHAT_STRIPE_PRICES` (the price book β€”
174 price ids are public by the time Checkout renders) and
175 `LCHAT_STRIPE_PORTAL_CONFIG` (a portal configuration id).
176
177## AWS (from the #550 build-out)
178
179- **`dev-agent` access key** β€” AdministratorAccess on the dev account, for
180 IaC; in 1Password. *Rotate*: IAM two-key rotation. To be replaced by scoped
181 roles once the OpenTofu skeleton defines them.
182- **`dev-agent-james` access key** β€” James's own AdministratorAccess user on
183 the same account, same purpose (IaC/tofu runs), so each operator has a
184 revocable identity and CloudTrail attribution. Sealed in the secrets vault
185 (`aws-dev-agent-james.env`, bundled with the DNSimple token so one file
186 covers an `infra/` run). *Rotate*: IAM two-key rotation + re-seal.
187 *Revoke*: delete the IAM user β€” independent of `dev-agent`.
188- **`litestream-fleet` access key** β€” object-rw on the fleet-backup bucket
189 only, lives in the router box's env file. *Blast radius*: read/delete
190 backup ciphertext β€” versioning (which this user cannot touch) keeps 14 days
191 of noncurrent versions, so deletion is recoverable. *Rotate*: IAM two-key
192 rotation + env swap + `systemctl restart lchat-litestream`.
193- **EC2 pool boxes carry no AWS keys at all** β€” bucket access rides instance
194 roles; there is nothing to rotate or leak. This is the pattern all later
195 boxes follow.
196