fix(oauth): require management auth on the OAuth login and connection routes - #15044
Merged
diegosouzapw merged 1 commit intoSep 29, 2026
Conversation
… routes /api/oauth/ is on the public route list so the authz pipeline leaves the decision to each handler. Several handlers checked isAuthenticated(), and for a public path that helper does not treat the request as a management call, so any valid client API key passes, with no scope check and even when the key is sent in the query string. A key issued only for inference could then start device and browser logins, import tokens, paste credentials and poll flows to completion, which creates provider connections or overwrites an existing one by connectionId or matching email. That lets the key holder put an account they control into the provider pool, or replace the operator's credentials. The import routes in the same directory (codex, cursor, kiro, trae, cliproxy) already call requireManagementAuth(). Use it in the remaining handlers too, so they accept the same credentials as the rest of the connection API: a dashboard session, the local CLI token, a scoped access token, or an API key with the manage scope. An invalid key still gets 401 as before; a valid key without the manage scope now gets 403. requireManagementAuth also stopped short on a public path before a password exists: isAuthRequired() answers "no auth needed" for any public path in that window, so a remote caller passed the check on an unconfigured instance. It now judges a public path as a management path, which leaves the window open to the local operator only. The modals that call these routes read the error through a shared helper, since the 401 and 403 bodies are objects. The static guard test now covers every route in the directory and rejects isAuthenticated(). Signed-off-by: Minxi Hou <houminxi@gmail.com>
diegosouzapw
merged commit Sep 29, 2026
aa75ac1
into
diegosouzapw:release/v3.8.51
9 of 16 checks passed
diegosouzapw
added a commit
that referenced
this pull request
Sep 29, 2026
… 4) (#15109) Release-captain base-red fix (v3.8.51 release PR #11442, unit shards 3-4): one production defect (proxyLogger pulled into every settings→proxies import and queried the DB at import time; helper extracted to src/lib/proxyLogHost.ts) and seven contract propagations from #14732, #15044, #15067, #13548, #14117×#14844, #12810. 74/74 across the seven files, 176/176 proxy-log neighbours, typecheck clean.
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.
Summary
/api/oauth/ is on the public route list so the authz pipeline leaves the
decision to each handler. Several handlers checked isAuthenticated(), and for
a public path that helper does not treat the request as a management call, so
any valid client API key passes, with no scope check and even when the key is
sent in the query string. A key issued only for inference could then start
device and browser logins, import tokens, paste credentials and poll flows to
completion, which creates provider connections or overwrites an existing one
by connectionId or matching email. That lets the key holder put an account
they control into the provider pool, or replace the operator's credentials.
The import routes in the same directory (codex, cursor, kiro, trae,
cliproxy) already call requireManagementAuth(). Use it in the remaining
handlers too, so they accept the same credentials as the rest of the
connection API: a dashboard session, the local CLI token, a scoped access
token, or an API key with the manage scope. An invalid key still gets 401 as
before; a valid key without the manage scope now gets 403.
requireManagementAuth also stopped short on a public path before a password
exists: isAuthRequired() answers "no auth needed" for any public path in that
window, so a remote caller passed the check on an unconfigured instance. It
now judges a public path as a management path, which leaves the window open
to the local operator only. The modals that call these routes read the error
through a shared helper, since the 401 and 403 bodies are objects. The static
guard test now covers every route in the directory and rejects isAuthenticated().
Related Issues
Validation
tests/integration/security-hardening.test.ts,tests/unit/oauth-routes-manage-scope.test.tsnpm run lintTests Added Or Updated
tests/integration/security-hardening.test.tstests/unit/oauth-routes-manage-scope.test.tsCoverage Notes
Reviewer Notes