Skip to content

fix(express): encode the provider id on the oauth provider admin routes - #131

Merged
Bccorb merged 1 commit into
mainfrom
fix/encode-oauth-provider-id
Jul 30, 2026
Merged

fix(express): encode the provider id on the oauth provider admin routes#131
Bccorb merged 1 commit into
mainfrom
fix/encode-oauth-provider-id

Conversation

@Bccorb

@Bccorb Bccorb commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Problem

PATCH and DELETE /system-config/oauth-providers/:id interpolated the route param straight into the upstream URL:

(req) => `system-config/oauth-providers/${req.params.id}`

Every other proxied route param goes through encodeURIComponent(routeParam(...)). An audit of the interpolations shows these two are the only exceptions:

  12  ${encodeURIComponent(routeParam(req, "organizationId"))}
   4  ${encodeURIComponent(routeParam(req, "userId"))}
   1  ${encodeURIComponent(routeParam(req, "token"))}
   2  ${req.params.id}                                  <-- these

This is the same class of bug #65 fixed elsewhere; these two were missed by that pass.

Impact

Confirmed against a running adapter. An id of abc?admin=1 reaches the auth API as a query parameter rather than a path segment:

oauth-providers/:id  -> https://auth.example.com/system-config/oauth-providers/abc?admin=1
admin/sessions/by-id -> https://auth.example.com/admin/sessions/by-id/abc%3Fadmin%3D1

The second line is a correctly-encoded route for comparison. An id carrying an encoded / can reshape the upstream path the same way.

Both routes are behind proxyWithIdentity(..., "access"), so an attacker needs a valid access session. That bounds the severity but does not remove it: it lets an authenticated caller reach upstream paths and parameters the route was not meant to expose.

Fix

Route both through encodeURIComponent(routeParam(req, "id")), matching every other proxied param. routeParam also rejects a missing or non-string param rather than interpolating undefined.

Tests

Four cases added to proxyQueryForwarding.test.js covering ?, #, and ../../ on both methods, asserting the exact upstream URL. They are not vacuous: reverting only the source change fails 4 of 149 and passes again once restored.

How this was found

Porting the route table to a second adapter. Writing the 27 proxy routes as a declarative table meant centralizing the encoding, which made the two hand-written exceptions visible. Filed separately from that work so it can land on its own.

PATCH and DELETE /system-config/oauth-providers/:id interpolated
req.params.id straight into the upstream URL. Every other proxied route
param goes through encodeURIComponent, and these two were missed when that
pass landed.

An id of `abc?admin=1` was forwarded as
/system-config/oauth-providers/abc?admin=1, turning attacker-controlled
input into an upstream query parameter. A param carrying an encoded `/`
could reshape the upstream path the same way.

Found while porting the route table to a second adapter: the other 17
interpolations encode, these 2 did not.

The routes require an authenticated access session, so this is not
reachable anonymously.
@Bccorb
Bccorb merged commit 1d0c45c into main Jul 30, 2026
2 checks passed
@Bccorb
Bccorb deleted the fix/encode-oauth-provider-id branch July 30, 2026 01:52
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.

1 participant