Skip to content

Unauthenticated /oauth/callback forwards the platform's internal error text in its JSON body #110

Description

@tonychang04

Unauthenticated GET /oauth/callback echoes the platform's error field verbatim in its JSON body. The HTML page is safe; the JSON path is not, and it has never had a guard.

Found by @qa-bot from @blog-bot's handler read. Reproduced on feat/refresh-token-grant with a stubbed platform, driving the real flow (register → authorize → callback):

platform returns  error: "PostgresError: connection to 10.0.0.5:5432 refused (role=oauth_svc)"

Accept: application/json  -> 400  leaks "PostgresError": true
Accept: text/html         -> 400  leaks "PostgresError": false

Why

server.ts   error_description: tokens.error_description || tokens.error   <- master:792
oauth-error-response.ts
            if (prefersHtml(req)) …render with {...humanFormOf(body), ...human}
            return res.status(status).json(body);      <- `human` applies to HTML ONLY

The token_exchange_failed branch passes a static human override, so the page never renders the value. The JSON branch returns the raw body and never did. So the page is safe by accident — remove that override in a future tidy-up and the same string lands on the repair page too.

Why the content is unbounded

insforge-cloud-backend/src/controllers/oauth.controller.ts:447-452:

} catch (error: any) {
  error:   error.message || OAuthErrorCode.INVALID_GRANT,   // whatever threw
  message: 'Failed to exchange authorization code',         // a per-site literal
}

Every deliberate throw inside exchangeCodeForTokens is new Error(OAuthErrorCode.*), which is why this field measures as a clean invalid_grant and looks safe. Any unintended exception — a driver error, a TypeError — puts its message straight into the field we forward. @blog-bot's formulation: message is known and useless, error is unknown and unsafe — and error is the one our code reads.

Scope and severity

Fix

Stop forwarding it. error_description should be our own literal on this branch, with the platform's value logged server-side where operators can see it and strangers cannot. That also removes the trap noted on #109: "render the description we already have" is the obvious-looking fix for the empty server_error card, and it would publish this field to a browser.

Does not change @manager's rotation baseline, which asserts 400 + error == "token_exchange_failed" + error_description != "invalid_client" — our own literal and the credential marker, not the variable text.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions