Skip to content

CWE-209: /api/transcribe returns caller-derived probe status and key-configuration state #1441

Description

@groupthinking

Summary

/api/transcribe's !result.success branches return fetchTranscript's error message to the caller verbatim. That is safe only while every value it can carry is a fixed, app-authored literal. Two are not, and both are reachable by a caller who controls the request body.

This is the residue of #1383 / #1381. #1381 sanitized the thrown and JSON-parse paths and argued the remaining branches were safe because "every one of those values is an app-authored literal, a numeric HTTP status, or our own SSRF-guard message, so none is upstream text." That reasoning is correct about upstream text and correct about most branches — but it scopes the problem to the wrong category. The two values below are not upstream text; they are caller-derived, which is a different leak and is why they survived the first pass.

The two values

1. Failed to fetch audio: ${audioResponse.status}apps/web/src/lib/transcription-service.ts

The status belongs to the caller-supplied audioUrl. The SSRF guard admits public hosts, so this returns a cross-origin read — 401 vs 403 vs 404 vs 500 for any public host the caller names — that the browser's same-origin policy would otherwise deny. A probe oracle built out of an error string.

2. The all-strategies-failed message — same file

error: hasKeys
  ? 'Could not transcribe video — all strategies failed'
  : 'No AI API key configured. Set OPENAI_API_KEY or GEMINI_API_KEY in Vercel environment variables.',

Branching the client-facing message on hasKeys discloses whether provider keys are configured, and names both the environment variables and the hosting platform.

Also: the billing_not_configured branch in apps/web/src/app/api/transcribe/route.ts still carries details: 'Configure billing in Google Cloud and OpenAI console', naming this deployment's cloud and model vendors. #1381's description states details was removed from this route; this instance was left behind.

Severity

Moderate, not critical. /api/transcribe is not on the public allowlist in apps/web/src/lib/auth-paths.ts — it requires a session when NEXTAUTH_SECRET is configured — so unlike the billing routes #1381 patched, this is authenticated-only. It is a defense-in-depth CWE-209 gap rather than an anonymous-reachable one.

Acceptance criteria

  • No fetchTranscript error value interpolates caller-supplied input or server configuration state.
  • Every distinct upstream status collapses to a single caller-facing message, so no oracle survives.
  • The real status and the real key state remain available to operators in logs.
  • Fixed at the source in transcription-service.ts, not masked at the route, so other callers of fetchTranscript inherit it.
  • Regression tests that fail against the unfixed service.

Notes

Found while driving #1381 through the PR remediation runbook; it is the part of CodeRabbit's CHANGES_REQUESTED on that PR (review 4863120464) that is still live on its current head. The rest of that review is addressed by #1381's later commits.

Fixed by #1440, which stacks onto #1381 rather than competing with it.

Activity

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

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions