Skip to content

fix(dashboard): media mini-playgrounds send masked API key as Bearer (401 under REQUIRE_API_KEY) - #10449

Open
diegosouzapw wants to merge 1 commit into
release/v3.8.50from
fix/9935-media-playground-masked-bearer
Open

fix(dashboard): media mini-playgrounds send masked API key as Bearer (401 under REQUIRE_API_KEY)#10449
diegosouzapw wants to merge 1 commit into
release/v3.8.50from
fix/9935-media-playground-masked-bearer

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Refs #9935

Root cause

useApiKey() (src/app/(dashboard)/dashboard/providers/hooks/useApiKey.ts) fetches gateway
keys from GET /api/keys, which returns the masked value
(maskStoredApiKey()sk-xxxx****yyyy, src/lib/apiKeyExposure.ts). The 9 media
mini-playground *ExampleCard components under
src/app/(dashboard)/dashboard/media-providers/components/ sent that masked string as a real
Authorization: Bearer header on Run. A masked string is never a valid credential, so the
gateway returns AUTH_002 Invalid API key under REQUIRE_API_KEY=true — and because a Bearer
header is present, the request never falls through to dashboard session auth.

LlmChatCard.tsx (#3503) already got this right: authenticate via the dashboard session
(credentials: "same-origin", no Bearer) and, when a specific key is selected, forward only
its id via x-omniroute-playground-key-id so the gateway applies that key's policy
server-side without the secret touching the wire.

Fix

Mirror the LlmChatCard pattern in all 9 affected cards (WebSearch, WebFetch, Image, TTS,
STT, OCR, Music, Embedding, Video):

  • Drop the Authorization: Bearer ${apiKey} header from the fetch() call; add
    credentials: "same-origin" so the dashboard session cookie authenticates the request.
  • Add a shared helper, resolvePlaygroundKeyId() /
    PLAYGROUND_KEY_ID_HEADER in
    src/app/(dashboard)/dashboard/providers/utils/playgroundAuth.ts (mirrors the private
    helper already inlined in LlmChatCard.tsx), and send
    x-omniroute-playground-key-id: <id> when a specific key is selected.
  • Update the buildCurl snippet in each card to show Authorization: Bearer <your-api-key>
    (a placeholder) instead of the masked value.

Regression test

tests/unit/bug-9935-masked-bearer.test.ts — asserts none of the 9 *ExampleCard components
(nor the reference LlmChatCard.tsx) embed apiKey in a raw Authorization: Bearer header.

node --import tsx/esm --test tests/unit/bug-9935-masked-bearer.test.ts
✔ every media ExampleCard avoids sending a masked apiKey as Bearer (#9935)
✔ masked value is never a real API key (repro of the 401 trigger)
ℹ pass 2, fail 0

Fail→pass evidence: on the unfixed tree (git show origin/release/v3.8.50:...) the
WebSearchExampleCard.tsx (and the other 8 cards) still contain
Authorization: `Bearer ${apiKey}` — the regression test's MASKED_BEARER regex matches
and the test fails RED; after the fix it is green.

Gates run

  • npm run typecheck:core — clean
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> — clean
  • node scripts/check/check-file-size.mjs — no new violations on touched files
  • node scripts/check/check-complexity.mjs — OK (2456 vs baseline 2774)
  • node scripts/check/check-cognitive-complexity.mjs — OK (1104 vs baseline 1223)
  • node scripts/check/check-test-discovery.mjs — OK, new test discovered
  • npm run test:unit — started locally; the shared devbox is running many concurrent
    full-suite jobs from parallel sessions and the run did not finish within this session's
    window (progressing normally, no failures observed in the portion that completed). This
    PR's change is frontend-only (9 React components + one pure TS helper, no DB/server code),
    so CI's test-unit job is the authoritative full-suite gate here.

Scope

Touched only the 9 *ExampleCard components, the new shared
src/app/(dashboard)/dashboard/providers/utils/playgroundAuth.ts helper, and the regression
test. No changes to LlmChatCard.tsx (already correct) or buildCurl.ts (generic
string-builder, unaffected).

⚠️ base-red inherited: #9985 — ESLint errors (2) from #10250 (unrelated i18n PT-PT commit on
the release tip; not touched by this PR).

Note: issue #9935 shows as already CLOSED on GitHub (closed 2026-08-14, no linked PR/commit),
but the bug is still present in origin/release/v3.8.50 HEAD — verified via
git show origin/release/v3.8.50:.../WebSearchExampleCard.tsx, which still sends the masked
key as Bearer. This PR lands the actual fix regardless of the issue's closed state.

… Bearer

The 9 media *ExampleCard components under media-providers/components used
the masked value from useApiKey() (sk-xxxx****yyyy) as an Authorization:
Bearer header, which the gateway always rejects (AUTH_002) once
REQUIRE_API_KEY is enabled. Mirror the LlmChatCard fix (#3503): authenticate
via the dashboard session (credentials: "same-origin") and forward the
selected key's id via x-omniroute-playground-key-id instead of its secret.
buildCurl now keeps the <your-api-key> placeholder instead of the masked
value.

Adds tests/unit/bug-9935-masked-bearer.test.ts as the permanent regression
guard (asserts none of the 9 cards embed apiKey as a raw Bearer token).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants