Files
ops-docs/maintenance/librechat-key-feed.md
T

53 lines
2.6 KiB
Markdown

# 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.