ops: record the LibreChat key-feed design decision (audit M2) — kept with monitoring; per-member fetch rejected as the riskier path

This commit is contained in:
inference-bot committed 2026-11-05 16:21:52 +00:00
1 parent b1a1c70e8a
commit 3bf152b58a
1 file changed
+52
+52
View File
@@ -0,0 +1,52 @@
# LibreChat key feed (`/admin/librechat/keys`) — design decision (audit M2)
Audited 2026-11-05. Status: **kept as-is, with monitoring**; a per-member
fetch was considered and deliberately rejected.
## What the route is
`GET https://portal.inference.coop/admin/librechat/keys`, authed by a single
static token (`LIBRECHAT_SYNC_TOKEN`, held by the portal and the LibreChat
sandbox app). It returns every ACTIVE member's decrypted chat key so the
sandbox's in-app key-sync (`librechat_key_sync`, every 10 min inside the
LibreChat app; `sync-keys.js`) can seed LibreChat's per-user key store —
members never see an API-key prompt, and chat bills to each member's own
LiteLLM team/budget.
## Blast radius (why the audit flagged it)
The sync token unlocks every member's chat key in one response. Anyone holding
the token (sandbox compromise, token leak from the sandbox env) reads the whole
membership's keys. It is a deliberate exception to the admin token — members'
key storage is decrypted in the portal only for this feed and for serving a
member's own dashboard.
## Why a per-member fetch was rejected (the riskier path)
The sandbox is the ONLY consumer, and it does not know which member it is
seeding at the API layer — it seeds ALL members in one pass from one identity.
Scoping the feed per member means changing the sandbox's identity model
(per-member credentials or a member list with per-member auth), touching
`sync-keys.js` and the seeding loop. A wrong change there stops key seeding
for every member at once — the exact regression the audit order forbids
("nothing breaks"). The feed also runs on a fixed cadence, so the practical
exposure window is bounded by how fast misuse would show.
## What is in place instead (2026-11-05)
1. **Access logging on the route**: every fetch logs a WARNING line
`librechat key feed served: N member key(s) to <ip> (ua=…)`. Count only —
never key material, never member emails (the portal's log redactor strips
emails anyway).
2. **Daily-digest watch** (cron job 95e5b8fece86, §7 anomaly list): the
sandbox polls ~every 10 min (~144 serves/day expected). The digest counts
`librechat key feed served` lines and flags (a) a rate far above that
cadence, or (b) any 401 burst on the route — either is a suspected token
misuse and becomes a decision item.
## What a real fix looks like (if ever needed)
Move the sandbox to fetch per member: a new portal route keyed by the member
identity the sandbox already knows at seed time, plus a sandbox-side change to
seed from that route. Only worth doing when the sandbox is already being
modified for another reason — never as a standalone risky change.