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:
1 parent
b1a1c70e8a
commit
3bf152b58a
1 file changed
+52
@@ -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.
|
||||||
Reference in new issue
Block a user