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
Open
Conversation
… 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #9935
Root cause
useApiKey()(src/app/(dashboard)/dashboard/providers/hooks/useApiKey.ts) fetches gatewaykeys from
GET /api/keys, which returns the masked value(
maskStoredApiKey()→sk-xxxx****yyyy,src/lib/apiKeyExposure.ts). The 9 mediamini-playground
*ExampleCardcomponents undersrc/app/(dashboard)/dashboard/media-providers/components/sent that masked string as a realAuthorization: Bearerheader on Run. A masked string is never a valid credential, so thegateway returns
AUTH_002 Invalid API keyunderREQUIRE_API_KEY=true— and because a Bearerheader 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 onlyits id via
x-omniroute-playground-key-idso the gateway applies that key's policyserver-side without the secret touching the wire.
Fix
Mirror the
LlmChatCardpattern in all 9 affected cards (WebSearch, WebFetch, Image, TTS,STT, OCR, Music, Embedding, Video):
Authorization: Bearer ${apiKey}header from thefetch()call; addcredentials: "same-origin"so the dashboard session cookie authenticates the request.resolvePlaygroundKeyId()/PLAYGROUND_KEY_ID_HEADERinsrc/app/(dashboard)/dashboard/providers/utils/playgroundAuth.ts(mirrors the privatehelper already inlined in
LlmChatCard.tsx), and sendx-omniroute-playground-key-id: <id>when a specific key is selected.buildCurlsnippet in each card to showAuthorization: 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*ExampleCardcomponents(nor the reference
LlmChatCard.tsx) embedapiKeyin a rawAuthorization: Bearerheader.Fail→pass evidence: on the unfixed tree (
git show origin/release/v3.8.50:...) theWebSearchExampleCard.tsx(and the other 8 cards) still containAuthorization: `Bearer ${apiKey}`— the regression test'sMASKED_BEARERregex matchesand the test fails RED; after the fix it is green.
Gates run
npm run typecheck:core— cleannpx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>— cleannode scripts/check/check-file-size.mjs— no new violations on touched filesnode 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 discoverednpm run test:unit— started locally; the shared devbox is running many concurrentfull-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-unitjob is the authoritative full-suite gate here.Scope
Touched only the 9
*ExampleCardcomponents, the new sharedsrc/app/(dashboard)/dashboard/providers/utils/playgroundAuth.tshelper, and the regressiontest. No changes to
LlmChatCard.tsx(already correct) orbuildCurl.ts(genericstring-builder, unaffected).
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.50HEAD — verified viagit show origin/release/v3.8.50:.../WebSearchExampleCard.tsx, which still sends the maskedkey as Bearer. This PR lands the actual fix regardless of the issue's closed state.