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

2.6 KiB

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.