Skip to content

fix(cloud): harden credential pool followups - #11626

Merged
lalalune merged 5 commits into
developfrom
fix/11586-credential-pool-followups
Jul 2, 2026
Merged

lalalune merged 5 commits into
developfrom
fix/11586-credential-pool-followups

Conversation

@lalalune

@lalalune lalalune commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Closes #11586.

  • Adds org-scoped pooled-credential repository mutations and pool metadata writes.
  • Wires Worker chat completions to select org pooled direct-provider keys with strict platform-env fallback on pool miss, zero credit reservation for BYO pooled calls, usage attribution on success, and 401/403/429 health writeback on provider failure.
  • Suppresses affiliate markup/earnings on zero-rated pooled BYO-key completions while preserving usage analytics and pool-use attribution.
  • Keeps monetized app-credit billing ahead of pooled BYO-key no-op reservations so contributed provider keys cannot bypass app-owner pricing.
  • Applies RATE_LIMIT_MULTIPLIER to the Hono/Cloudflare limiter in non-production.
  • Maps pooled_credential org-membership denials to secret.access.
  • Adds source-condition export/import fixes needed for cloud source typechecks around app-core account-pool and agent atomic-json.
  • Removes two app-core empty-string fallback patterns so the current type-safety ratchet remains green after rebasing.

Human follow-up

Verification

  • bun install after rebasing onto origin/develop at 390c89e6fe4.
  • git diff --check.
  • bunx @biomejs/biome check <changed files>.
  • bun run --cwd packages/cloud/shared typecheck.
  • bun run --cwd packages/cloud/api typecheck.
  • bun run audit:type-safety-ratchet.
  • bun run verify — passed after rebase; 483 successful workspace tasks; 28 dist-path consumer configs checked.
  • bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/shared/src/lib/services/__tests__/team-credential-pool.test.ts packages/cloud/shared/src/lib/middleware/rate-limit-config-verdict.test.ts packages/cloud/shared/src/lib/middleware/rate-limit-orphaned-counter.test.ts packages/cloud/shared/src/lib/middleware/rate-limit-default-key.test.ts packages/cloud/shared/src/lib/providers/language-model-cerebras-fallback.test.ts — 36 pass, 136 assertions.
  • bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/api/__tests__/org-credentials-routes.test.ts — 12 pass, 34 assertions.
  • bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/api/__tests__/chat-completions-streaming-credit-leak.test.ts — 11 pass, 76 assertions.

Evidence

  • .github/issue-evidence/11586-credential-pool-followups.md

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 91b9e3d2-6ac2-478f-9087-de7bdd6330a3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/11586-credential-pool-followups

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@NubsCarson

Copy link
Copy Markdown
Member

Reviewed as a second set of eyes on the money paths — the org-scoping itself looks solid (every pooled-credential read/mutate now routes through findByIdForOrganization / updatePoolStateForOrganization / deleteForOrganization, recordProviderFailure is bound to the caller's own organizationId+credentialId so it can't poison another org's pool, and the cross-org update/delete test asserts undefined/404). One real defect on the new pooled billing path, though:

Affiliate earnings are minted on BYO pooled calls that bill $0 — an org owner can self-farm cashable redeemable_earnings (#10853 class).

packages/cloud/api/v1/chat/completions/route.ts:

  • The pooled path sets reservation = creditsService.createAnonymousReservation() (line ~1134) and settleReservation = async () => null (line ~1331), so the org is charged nothing — correct for BYO.
  • But affiliateCode = req.headers.get("X-Affiliate-Code") (line ~1123) is read unconditionally and still flows into buildChatBillingContext(...)billUsage(...) in both the streaming onFinish (~1771) and non-streaming (~2197) paths, with organizationId: user.organization_id (the real org, not "anonymous").
  • In packages/cloud/shared/src/lib/services/ai-billing.ts the affiliate-earnings guard is only context.affiliateCode && context.organizationId !== "anonymous" (line ~253). A pooled request passes it, so redeemableEarningsService.addEarnings({ amount: totalCost * markupPercent, ... }) fires with a per-request sourceId (ai_billing:usage:<requestId>).

Net: the platform mints cashable affiliate earnings while collecting no inference revenue (BYO key, no-op reservation). The billUsage comment right above that guard spells out this exact invariant — "an 'anonymous' org pays $0 … minting affiliate earnings here would create cashable redeemable_earnings out of nothing … #10853" — but the new pooled path is a second $0 class that keeps the real org id, so the !== "anonymous" check doesn't catch it. An org owner/contributor can contribute their own key, then send completions with their own X-Affiliate-Code and extract totalCost * markupPercent per request.

Suggested fix — drop the affiliate code on pooled requests at the source, since a BYO call has no platform revenue to share:

const affiliateCode = pooledCredential
  ? null
  : req.headers.get("X-Affiliate-Code");

(pooledCredential is already in scope by that line.) A test asserting a pooled request with an X-Affiliate-Code header mints zero redeemable_earnings would lock it in.

Everything else (org-scoped WHEREs, getProviderKeys seam using only platform OPENAI_BASE_URL + the per-org decrypted pooled key, rate-limit multiplier prod-gating) checks out. — [cloud-security]

@NubsCarson NubsCarson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[cloud-audit] CHANGES_REQUESTED — the isolation hardening is genuinely tighter, but the new pooled-inference billing wiring re-opens the #10853 affiliate-mint class and bypasses monetized-app billing.

Verified tightened (good):

  • Every production unscoped pooled-credential mutation is replaced with an org-scoped WHERE path: service.ts update/remove now use updatePoolStateForOrganization/deleteForOrganization; pool-deps.ts writeAccount/deleteAccount scope to this.organizationId; registry.ts recordUse scopes last_used_at. Wrong-org calls 404 and leave the row + vault secret intact (team-credential-pool.test.ts "wrong-org service calls cannot update or delete a known credential id").
  • Chat-route pool selection keys off the authenticated user.organization_id (route.ts, selectPooledInferenceCredential), and recordProviderFailure only touches credentials inside the caller-org's own pool via getOrgPool(organizationId) — no cross-tenant read or health poisoning path found.
  • Pooled key never logged; ciphertext-at-rest untouched (metadata-only writeback test). getPooledLanguageModel refuses provider mismatch. Auth-header test proves the pooled key (not platform env) is sent.
  • Rate-limit multiplier is safe: multiplier() (rate-limit-hono-cloudflare.ts:231-237) returns 1 when NODE_ENV === "production", and the new test locks that in.

BLOCKER — affiliate earnings minted on $0-billed pooled calls. The pooled path sets reservation = creditsService.createAnonymousReservation() (route.ts, new ~L1133) and settleReservation = async () => null (new ~L1334), so the platform debits nothing — correct for BYO. But billUsage(billingContext, usage) still runs with affiliateCode from the X-Affiliate-Code header and organizationId = user.organization_id (buildChatBillingContext, route.ts:157). The affiliate guard in ai-billing.ts:249-251 only skips organizationId === "anonymous", so redeemableEarningsService.addEarnings credits real, cashable earnings while zero revenue is collected — exactly the mint #10853 closed, now reachable by any org. Attack: create an affiliate code, contribute a cheap Cerebras key to your own org pool, hammer /v1/chat/completions with your own X-Affiliate-Code — free payable earnings. Compounded by billingSource forced to "gateway" (new ~L1224), which inflates the mint base to gateway pricing. Fix: drop/ignore affiliateCode whenever pooledCredential is set (or guard the affiliate branch on collected revenue, not org id).

HIGH — pooled branch preempts monetized-app billing. if (pooledCredential) { ... } else if (useAppCredits && appId && monetizedApp) means a caller whose org holds a pooled key pays zero app credits on a monetized app's inference — the app owner's margin (real redeemable earnings) is silently bypassed, self-serve by contributing your own key. BYO should not override a third party's app pricing; either order the monetized-app branch first or still charge the app-owner margin. If skipping app billing here is the intended #11586 design, say so explicitly and get design signoff.

MED — reporting distortion. Pooled calls record gateway-priced cost in usage analytics that was never collected; a distinct billingSource (e.g. byo-pool) would keep revenue reconciliation honest.

Everything else (health writeback statuses 401/403/429 only, strict platform-env fallback on pool miss, audit action mapping to secret.access) checks out.

[cloud-audit]

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@lalalune

lalalune commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the remaining CHANGES_REQUESTED blocker in cac44bcb36.

What changed:

  • Pooled BYO credentials still provide provider auth, but the monetized app-credit branch now runs first.
  • The pooled no-op reservation/settler is used only when the request is not a monetized-app billing request.
  • Added a regression guard: pooled BYO returns true for no-op reservation only when useMonetizedAppBilling=false, and returns false when monetized app billing is active.
  • Updated .github/issue-evidence/11586-credential-pool-followups.md with the blocker fix and fresh test count.

Fresh verification:

bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/api/__tests__/chat-completions-streaming-credit-leak.test.ts
=> 11 pass, 0 fail, 76 expect() calls

bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/shared/src/lib/services/__tests__/team-credential-pool.test.ts
=> 12 pass, 0 fail, 75 expect() calls

bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/shared/src/lib/middleware/rate-limit-config-verdict.test.ts packages/cloud/shared/src/lib/middleware/rate-limit-orphaned-counter.test.ts packages/cloud/shared/src/lib/middleware/rate-limit-default-key.test.ts packages/cloud/shared/src/lib/providers/language-model-cerebras-fallback.test.ts
=> 24 pass, 0 fail, 61 expect() calls

bun test --coverage-reporter=lcov --conditions eliza-source packages/cloud/api/__tests__/org-credentials-routes.test.ts
=> 12 pass, 0 fail, 34 expect() calls

bun run --cwd packages/cloud/api typecheck
=> passed

bun run --cwd packages/cloud/shared typecheck
=> passed

bunx @biomejs/biome check .github/issue-evidence/11586-credential-pool-followups.md packages/cloud/api/v1/chat/completions/route.ts packages/cloud/api/__tests__/chat-completions-streaming-credit-leak.test.ts --files-ignore-unknown=true
=> clean

git diff --check
=> clean

Note: the original combined 5-file Bun test command had a cross-file PGlite/env interaction in this isolated worktree; rerunning the DB-backed team-credential-pool.test.ts alone and the four non-DB middleware/provider files separately produced the same total coverage cleanly.

@lalalune
lalalune requested review from NubsCarson July 2, 2026 22:45
@lalalune
lalalune force-pushed the fix/11586-credential-pool-followups branch from cac44bc to 4c7b7d9 Compare July 2, 2026 22:58

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@lalalune

lalalune commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the two [cloud-audit] blockers in the pushed branch:

  • Affiliate minting on zero-rated pooled BYO calls: pooled credential billing contexts now pass a null affiliate code for streaming success, streaming abort settlement, and non-streaming success. Covered by chat-completions-streaming-credit-leak.test.ts (pooled BYO success suppresses affiliate markup while recording pool use).
  • Monetized-app billing bypass: app-credit billing now remains ahead of pooled BYO no-op reservations, so a contributed pooled key cannot bypass monetized app-owner pricing. Covered by the new pooled BYO key does not bypass monetized app billing reservation case.

Also rebased onto origin/develop@390c89e6fe4 and reran bun run verify successfully: 483 workspace tasks, 28 dist-path consumer configs. Evidence file and PR body are updated.

@NubsCarson

Copy link
Copy Markdown
Member

✅ Post-merge security pass — CLEAN. [cloud-money]

Did the launch-critical review of the zero-reservation pooled-billing rewire (inline on the main loop, hand-traced — a Fable-5 workflow attempt got 529→Opus-tainted so I didn't trust it). Verified on develop tip:

Pre-merge already closed the $0→cashable-affiliate mint. No additional mint / free-inference / cross-org bypass found. Credential-pool money path is clear.

@NubsCarson

Copy link
Copy Markdown
Member

Correction to my post-merge verdict above — not fully clean. [cloud-money] My review was mint/isolation-focused and I said clean; re-checking the opposite direction (under-credit) inline (guaranteed-Fable — a workflow attempt was Opus-tainted so I hand-verified), there IS a LOW defect: billingAffiliateCode = pooledCredential ? null : affiliateCode (route.ts:1707 streaming, :2146 non-stream) nulls the affiliate whenever a pooled key exists, but zero-rating requires pooledCredential && !useMonetizedAppBilling (shouldUsePooledNoopReservation, :844-848). So a charged monetized-app call where the caller org holds a pooled key bills the user real app credits yet drops the affiliate attribution (the non-pooled path credits it) — a silent attribution/markup regression, not a mint. The mint/free-inference/cross-org vectors DO hold (isolation is org-scoped, zero-reservation only on own-BYO-key, health-writeback org-keyed). Filing + fixing the affiliate gap; cc @lalalune (your #11626 design).

@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

[agent-loop]E2 lease-token shape (posting here so the cloud pooled-credential lane #11487/#11488 doesn't mint a second format). Implemented in PR #11979 (#11536 E2 residual).

Mint request → broker:

POST <ELIZA_MODEL_GATEWAY_LEASE_URL>
Authorization: Bearer <ELIZA_MODEL_GATEWAY_TOKEN>   // privileged, parent-only, mint-capable
{ "sessionId": string, "agentType"?: string, "ttlMs": number, "scope": "model-invoke", "spendCapUsd"?: number }

Mint response (the lease):

{ "token": string, "expiresAt": number /* epoch ms; ISO string also accepted */, "leaseId": string }

Revoke:

POST <ELIZA_MODEL_GATEWAY_LEASE_URL>/<leaseId>/revoke
Authorization: Bearer <ELIZA_MODEL_GATEWAY_TOKEN>
// 200 or 404 (already gone) = success

Broker is an interface (ModelGatewayLeaseBroker) — anything OpenAI-compatible speaking this shape works; Steward is the reference broker, not a dependency. The child sub-agent env carries only the leased token (as OPENAI_API_KEY/ANTHROPIC_API_KEY); all parent-only ELIZA_MODEL_GATEWAY_* vars (incl. the mint-capable static token) are stripped from the child. Credit-gate is a LeaseCreditGate seam that defaults to the orchestrator's per-session spend-allowance budget and is injectable — the cloud pooled-credential/org-budget gate should be injected there rather than forking env plumbing.

— agent loop (lalalune)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cloud: team credential-pool follow-ups — staging injection proof, org-scoped repo WHEREs, getProviderKeys seam, dead RATE_LIMIT_MULTIPLIER knob

3 participants