Commit Graph
81 Commits
Author SHA1 Message Date
inference-bot 452a27a778 portal: guard against an implausible member list; redact the webhook token from logs
Two fixes, both filed rather than deferred.

1. member-portal#2 — the sweep deactivates every member whose email is missing
   from the Open Collective member list, and the existing fail-safe only covers a
   fetch that RAISES. A fetch that SUCCEEDS while returning nothing or far less
   than usual is unguarded, and is what a narrowed token scope, an expired
   credential with partial access, or a renamed Membership tier produces (the
   fetch filters on the tier's name). That path deactivates the membership
   instead of failing. fetch_members() now checks the result against the local
   active count before any caller can act on it: empty, or at or below half of
   local, raises — so the sweep's fail-safe takes over and members stay active.
   Skipped when we have no active members of our own (fresh install).

   Correction to the issue as filed: listmonk_reconcile reads the local members
   table, not this fetch, so the newsletter path was never exposed to it. The
   sweep is the only destructive consumer.

2. ops/security#2 — OC's generic webhooks cannot send headers, so the webhook
   secret must travel in the URL path, and uvicorn wrote it into the access log
   on every delivery. A logging filter on uvicorn.access and on the root handler
   replaces the token with <redacted>. The token cannot be kept out of the URL;
   it can be kept out of the retention.

Both pieces are unit-tested in isolation before shipping: the guard's decision
function over eight cases, and the filter against a real access-log record, an
unrelated path (no over-redaction), and a missing dependency (must not break
logging).
2026-10-27 05:04:05 +00:00
inference-bot c2369034ee manifest: pin dockerImage to 20261027-5720a63 (security fixes: admin token split, credit-pack order verification, revoke on lapse) 2026-10-27 03:34:29 +00:00
inference-bot 5720a63958 security: separate admin token, verify credit-pack orders, revoke access on lapse
Member disclosure, three findings (fixes 1-3 of 5):

1. WEBHOOK_TOKEN was both the Open Collective webhook URL token and the
   X-Admin-Token for every /admin/* route, so anything that learned the webhook
   URL (OC admins, OC logs, this app's access logs) gained portal admin. The URL
   token is forced by OC's generic webhooks, which cannot set headers — the reuse
   is not. Introduce ADMIN_TOKEN, header-only. ADMIN_TOKEN_LEGACY=1 (default)
   still accepts WEBHOOK_TOKEN and logs a warning, so the admin panel and the
   scheduler keep working; set ADMIN_TOKEN everywhere, then flip it to 0 and
   rotate. sweep.sh and the digest script now send ADMIN_TOKEN.

2. The credit-pack path fell back to the webhook payload's own amount when OC
   could not resolve the order, so a forged POST naming its own totalAmount
   minted credits for any slug. The payload is now a hint only: an unresolvable
   order is refused, logged loudly, and emailed to the operator for manual
   reconciliation. Transient OC failures hold instead of granting blind.

3. Lapsing did not revoke access: both deactivation sites (lapse webhook, nightly
   sweep) discarded the key token deactivate_member() returns. Member keys carry
   no max_budget of their own, so they inherit the team's — zero the team budget
   on deactivation, which blocks every key under it. Key deletion is deliberately
   NOT used: provisioning reuses the stored sk- token on reactivation, and
   get_or_create_team does not update an existing team's budget, so the two
   renewal paths now re-sync the budget explicitly. Failures are loud (log +
   alert email); deactivation still completes.
2026-10-27 03:31:40 +00:00
inference-bot 178a4aa360 loomio: sanitize usernames instead of falling back to another invalid one
The username fix rejected a dot-containing OAuth uid and then substituted the
email local part, which carries the same dots — so Loomio answered 422
("Usernames must contain lower case letters, numbers, underscores, and internal
hyphens only") on every sweep, for the one member whose Cloudron username has a
dot. Normalize instead: disallowed characters become underscores, and leading
and trailing hyphens/underscores are stripped. Also log the Loomio user id
rather than the member's email.
2026-10-26 16:07:23 +00:00
inference-bot e24c272e2e org-link fix: INSERT bound 4 values to 3 placeholders (500 on every link); slug = org slug 2026-10-21 21:16:59 -06:00
inference-bot d8d80d9594 newsletter reconcile: Members list mirrors ACTIVE members
Root cause of test accounts receiving the newsletter: LISTMONK_API_USER was
set EMPTY in the portal env - env-present beats the code default, so every
listmonk_sync authenticated with an empty user and 403'd silently. Fixed both
the env (set to portal-sync) and the code (empty env falls back to default).

New: listmonk_reconcile() - the Members list must mirror ACTIVE members.
Stale subscribers are removed: members marked inactive are taken off the list
(record kept as suppression marker); addresses that are not members at all
(test/deleted accounts) are deleted outright. Wired as step 4 of the nightly
membership sweep + POST /admin/newsletter/reconcile for on-demand runs.
2026-10-21 16:32:36 -06:00
inference-bot e4da4da2c6 pagination fix: OC MemberCollection has NO cursor pageInfo — use offset+totalCount pagination (verified against the live API); the cursor query 400'd, taking the sweep and org endpoints down 2026-10-20 23:30:32 -06:00
inference-bot 42c664c076 OC member queries: cursor pagination via fetch_all_member_nodes() — members(limit:100) silently hid everyone past member #100 (same class as the Cloudron 25-per-page trap). Helper RAISES on any page failure so callers' fail-safes apply (never a partial list — the sweep would deactivate everyone missing from one); fetch_members returns [] on failure (sweep skips), fetch_org_backers propagates (sweep keeps linked accounts active), webhook payer lookup and /admin/org-link wrapped. 2026-10-20 23:26:18 -06:00
inference-bot fed4a7005e audit fixes: two latent NameErrors in the org code — webhook org branch referenced undefined org_name (crashed on an unlinked org's first payment), org-links GET referenced undefined name (crashed when a linked org's dues show inactive) 2026-10-20 23:21:51 -06:00
inference-bot d87cbf9650 admin overview: expose matrix_mxid (dashboard-set external handle) per member 2026-10-20 23:01:24 -06:00
inference-bot dcffbb1b98 org-links GET: remaining name-column reference 2026-10-20 22:57:40 -06:00
inference-bot 394cccfbcb org links: members table has no name column — drop it from org SQL/unpackings (names come from OC/Cloudron) 2026-10-20 22:55:55 -06:00
inference-bot 72614f8f19 Organizational memberships: OC org contributors (type ORGANIZATION) never expose an email, so all three automated paths missed them (webhook email lookup, nightly sweep, /join). Add: members.org_slug link column + fetch_org_backers() (org BACKER/Membership nodes); sweep keeps linked accounts active while the org's dues are active and deactivates on lapse (fail-safe keeps active if the org fetch errors); webhook re-activates linked accounts on org dues and emails the operator for unlinked orgs; POST /admin/org-link (link + full provision incl. Cloudron signup link) and GET /admin/org-links (live dues status); admin overview gains an 'org' field. 2026-10-20 22:45:37 -06:00
inference-bot 5425efee1f overview: normalize float-residue spend (e.g. -5.55e-17 after cycle reset) to 0 so the panel never renders -0.00 2026-10-19 11:42:58 -06:00
inference-bot fd13bc6ea2 broker/matrix-mxid GET: local_mxid_for_email was async-def but never awaited — response contained a coroutine and FastAPI 500'd ('coroutine object is not iterable'), so the dashboard silently showed no registered Matrix ID. Make it a plain sync def (blocking urllib). 2026-10-19 10:47:49 -06:00
inference-bot 97ebe69553 portal: /admin/librechat/keys — dedicated-token feed of member chat keys for the sandbox's key auto-load 2026-10-18 23:15:38 -06:00
inference-bot 7f04920327 broker: retire the old LiteLLM key before re-creating a same-name key
LiteLLM requires unique key aliases, so re-creating a key with an existing
(email, name) pair 400s ('alias already exists') — breaking key rotation
from the dashboard. The stored LiteLLM hash lets us retire the old key
first; the ON CONFLICT upsert then tracks the new key.

Surfaced by the LibreChat sandbox work: the portal chat-key rotation for
an SSO member failed with exactly this 400.
2026-10-17 18:00:49 -06:00
Inferencebot 003689868f Remove PublicAI; Regolo becomes the Apertus provider; emoji = feature tier (🌱 green for greenpt+regolo), qwen-image via image feature only 2026-10-16 10:48:10 -06:00
Inferencebot ced82b09d0 portal: filter image-gen models from chat picker by name (gateway strips mode from /v1/models) 2026-10-16 10:26:04 -06:00
Inferencebot 5091c9e644 portal: add Regolo models to picker (Apertus 70B, Qwen 3.5 122B, Qwen Image); exclude image-gen from chat list 2026-10-16 10:24:27 -06:00
Inferencebot 106b563748 sync: invite members to MATRIX_EXTRA_ROOMS too (restricted rooms aren't federation-discoverable until joined) 2026-10-14 16:40:35 -06:00
Inferencebot 24f0c0f745 fix: import urllib.parse on the retire path (NameError broke the sweep) 2026-10-14 10:55:30 -06:00
Inferencebot b2689d9cd1 Matrix sync: revoke stale local-account invites too (directory hygiene + one-identity completeness) 2026-10-14 10:54:24 -06:00
Inferencebot b342ca0ee3 README: document membership sweep + rag refresh scheduled tasks 2026-10-12 19:33:29 -06:00
Inferencebot 49638ba36a rag_refresh: retry attach up to 3x (transient 400 while file processes) 2026-10-12 19:29:17 -06:00
Inferencebot 49376813ae rag_refresh: parse OWUI files-list {items:[...]} shape; unfiltered list + client-side name filter 2026-10-12 19:26:06 -06:00
Inferencebot bca11a1657 Move docs-RAG refresh into the portal container (Gitea raw API + scheduler addon, 3:30am daily) 2026-10-12 19:23:00 -06:00
Inferencebot cb04958460 scheduler addon belongs under addons (manifest schema), not top level 2026-10-12 18:23:50 -06:00
Inferencebot b58929076e Restore nightly membership sweep scheduler (3am daily) — was silently missing from manifest 2026-10-12 18:21:27 -06:00
Inferencebot 81b640ba74 matrix-mxid: return invite_sent so the dashboard can confirm the invite is on its way 2026-10-12 09:01:22 -06:00
Inferencebot e56d0456bb Chat models: silent auto-RAG via legacy FC (no knowledge tool, no narration) 2026-10-10 13:41:20 -06:00
Inferencebot 92e7ddec05 Matrix sync: retire local @username:inference.coop accounts when a member sets a dashboard MXID (one identity per member) 2026-10-10 13:29:26 -06:00
Inferencebot 05feed5f24 Strengthen docs-KB steering: firm negative instruction after soft hint proved insufficient 2026-10-09 22:44:00 -06:00
Inferencebot 2eab2abb0d Overview: separate admin/operator accounts into their own category (excluded from member counts) 2026-10-08 15:08:21 -06:00
Inferencebot a7a283d552 Steer docs-KB tool usage: params.system hint to query only co-op-relevant questions 2026-10-08 09:38:05 -06:00
Inferencebot 32420f60f4 Friendly names: tinfoil/glm-5.3 (replaces deprecated flash variant) 2026-10-07 22:07:44 -06:00
Inferencebot 1a227642d9 Attach co-op docs knowledge collection to every model (auto-RAG, no KB browsing wandering) 2026-10-07 10:52:08 -06:00
Inferencebot e18cd4b703 Provisioning: add new members to Open WebUI docs-RAG access group at signup 2026-10-07 10:38:32 -06:00
Inferencebot 2a5277007e Model list: also filter embedding models out of the chat picker 2026-10-07 09:44:15 -06:00
Inferencebot 3d258e9a2d Loomio sync: only invite activated members (avoid invite email before account setup) 2026-10-07 09:19:24 -06:00
inference-bot 38005127cb Expose credits_remaining (purchased minus cycle overspend) in broker usage + admin overview 2026-10-03 13:59:29 -06:00
inference-bot 052a109040 Fix credit model: allowance is a floor, credits add on top (max_budget = allowance + credits, not reduced by cycle overspend) 2026-10-03 13:06:02 -06:00
inference-bot 563881d63c Matrix: member-set extra MXID (matrix_mxid column + broker get/set + sync prefers it) 2026-10-03 10:48:13 -06:00
inference-bot 3cdc250d52 Chat model picker: value-tier emoji + friendly names in /v1/models (🔒 private / 🌱 green / 📖 public), id stays canonical 2026-10-02 22:30:11 -06:00
inference-bot 1fe0e47802 Update code-comment example (green-r -> glm-5.3-flash) 2026-10-02 22:04:54 -06:00
inference-bot 29823e87d5 Matrix mxid lookup: paginate Cloudron users API (25/page) — was missing page 2+ 2026-10-02 13:03:14 -06:00
inference-bot f86ead8653 Matrix invite: honor 429 retry_after_ms (3 attempts) 2026-10-02 12:29:41 -06:00
inference-bot 07a1f2ad4e Fix Cloudron users API response shape ({users: [...]}) 2026-10-02 12:28:39 -06:00
inference-bot 82ada8c95b Matrix: invite all active members to co-op Space + General room on every sync (SSO usernames via Cloudron lookup; MATRIX_EXTRA_MXIDS for federated accounts) 2026-10-02 12:27:45 -06:00
inference-bot cddd9b6e3c Loomio username fix: allow hyphens in uids (was falling through to email local-part for inference-admin/bot) 2026-10-02 08:41:03 -06:00
inference-bot ef253d01a0 Loomio: fix auto-generated usernames after sync (b3 server API; prefer Cloudron uid, fall back to email local-part) 2026-10-02 08:36:10 -06:00
inference-bot 070c6525cd Broker usage: expose all_time_spend (member's lifetime total from SpendLogs) in both DB and HTTP paths 2026-10-01 16:07:10 -06:00
inference-bot 7af02f6466 Model aggregation: order by requests (usage) not spend — matches leaderboard bars and member-facing intent 2026-10-01 15:56:53 -06:00
inference-bot 19c04f37fa Fix days-filter in aggregate_model_usage: COALESCE(startTime,'') is invalid SQL (empty-string not a timestamp) — windowed queries always failed to empty 2026-10-01 15:54:58 -06:00
inference-bot 69beb9c002 Broker: co-op-wide model leaderboard endpoint for member dashboard (aggregate, anonymized) 2026-10-01 15:35:57 -06:00
inference-bot 778a7f8fd2 Rate-limit the key-injector proxy path (120/min) — only unthrottled sensitive route left 2026-10-01 09:51:57 -06:00
inference-bot 6f0c728513 Overview: expose all_time_spend per member + total_all_time_spend (SpendLogs aggregate) 2026-09-30 22:22:17 -06:00
inference-bot e00da486b5 Fix broker 403 for members beyond Cloudron's default 25-user page: paginate all /users reads (per_page=100) 2026-09-30 07:46:03 -06:00
inference-bot 64d3c6aa83 Broker: resolve uid- identities (OIDC sub) to email — email-invited users have no username 2026-09-29 21:53:46 -06:00
inference-bot b93b208659 Broker usage: expose allowance and credits buckets to dashboard 2026-09-27 22:39:54 -06:00
inference-bot b7d239d9b6 Reset dates follow real billing: webhook re-pins budget_reset_at on every Membership payment (+30d); DB write (API drops the field) 2026-09-27 22:38:05 -06:00
inference-bot 9b1d9edde4 Variable-amount credit pack: grant tip-free contribution amount via public OC order API 2026-09-27 22:17:48 -06:00
inference-bot 315247e5db Tier resolution via OC tiers API (TierId->face value+name, cached); credits at face value, not total-with-tip; remove debug payload log 2026-09-27 22:14:56 -06:00
inference-bot f163a5b375 Fix tier detection: OC webhook carries TierId only; parse tier name from order description 2026-09-27 22:04:35 -06:00
inference-bot a85369341c TEMP: log full OC webhook payload to diagnose tier field shape 2026-09-27 21:58:25 -06:00
inference-bot 8a320bfed7 Held credit packs: record non-member purchases, notify buyer, auto-grant on join, admin endpoints 2026-09-27 21:54:34 -06:00
inference-bot df740db283 Expose manual-member flag in admin overview (uses existing MANUAL_MEMBERS set) 2026-09-27 21:18:43 -06:00
inference-bot 5fa2449037 Membership tied to Membership tier: sweep+webhook filter, credit packs HELD for non-members, MANUAL_MEMBERS for legacy 2026-09-27 21:17:03 -06:00
inference-bot 63acea289f Non-expiring credits: two-bucket budget model, ledger, OC credit-pack webhook, admin endpoints 2026-09-27 19:10:50 -06:00
inference-bot 0c3c12999c Remove tracked bytecode; ignore __pycache__ 2026-09-27 11:15:02 -06:00
inference-bot 54fa4d3fc2 Filter audio models out of chat-facing /v1/models (visible to API users only) 2026-09-27 11:00:53 -06:00
inference-bot c7ed245939 Read usage aggregations from LiteLLM Postgres directly (HTTP fallback kept) 2026-09-26 16:39:23 -06:00
inference-bot 4600a69cac Fix dashboard 500s: raise httpx read timeout on /team/list + /spend/logs calls 2026-09-26 16:21:11 -06:00
inference-bot 2ff8193c55 Dashboard usage: prefer model_group alias (provider/model names) — fixes support #5 2026-09-26 16:14:33 -06:00
inference-bot 5c269bf243 Support ?days=N filter on /admin/model-usage 2026-09-26 16:09:38 -06:00
inference-bot a48f11c654 Prefer model_group alias in model-usage aggregation 2026-09-26 15:33:24 -06:00
inference-bot 810b878014 Add /admin/model-usage endpoint (co-op per-model spend/tokens/requests) 2026-09-26 15:31:07 -06:00
inference-bot e92e29bac0 Commit key-purge migration DML (backfill fingerprint, purge plaintext idempotently) 2026-09-24 08:02:39 -06:00
inference-bot c213a489e9 API keys revealed once: store fingerprint (last-4) + hash, purge plaintext 2026-09-24 08:00:02 -06:00
inference-bot 6564508e1d Remove per-member model allowlist: members access all gateway models (empty models = all allowed) 2026-09-23 20:10:23 -06:00
inference-bot fd7442e1a4 Idempotent provisioning + auto re-invite of never-activated members
- provision_member reuses the stored LiteLLM key instead of minting a duplicate
  alias (LiteLLM 400s on member:<email>); fixes re-provisioning of existing members
- store_member preserves an existing slug when none is passed
- add welcome_sent_at + REINVITE_AFTER_DAYS; nightly sweep re-invites
  active members who never completed account setup, throttled to 3 days
2026-09-22 16:07:00 -06:00