Repository navigation
fix(oauth): guard GHE Copilot device-flow requests to the caller's gheUrl - #15046
Merged
diegosouzapw merged 1 commit intoSep 29, 2026
Merged
diegosouzapw merged 1 commit into
diegosouzapw merged 1 commit into
Conversation
…eUrl The ghe-copilot device flow takes gheUrl from the request (the query string for device-code, extraData for poll) and builds four outbound requests from it. The only check was that the URL is https, and the requests used plain fetch, which follows redirects. An https host the caller controls can answer with a redirect to a plain-http internal address or to the cloud metadata service, and the device-code route hands the JSON it gets back to the caller. /api/oauth/ is on the public route list and the handler accepts any valid API key, so an ordinary client key is enough to do this. Send those requests through safeOutboundFetch with the provider outbound guard, the same policy other operator-supplied provider URLs use: private hosts stay reachable for on-premises GitHub Enterprise Server, metadata and link-local addresses are refused, and a redirect ends the request instead of being followed. GitHub Enterprise Server serves these paths on the configured host itself, so the normal flow does not depend on following a redirect. Metadata addresses stay blocked when the operator has allowed private provider URLs, since a GHE host is never one. What the host answers is no longer relayed as is: the device-code response is cut down to the fields the flow uses, upstream error bodies are not put into error messages, and poll answers only carry the token fields and a known device-flow error code. The poll handler also read the response body twice on the non-JSON path, which threw instead of reporting the bad answer. The two lookups after login now leave their fields empty when the host redirects them, like they already did for a non-2xx answer. Signed-off-by: Minxi Hou <houminxi@gmail.com>
diegosouzapw
merged commit Sep 29, 2026
e201782
into
diegosouzapw:release/v3.8.51
9 of 16 checks passed
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
The ghe-copilot device flow takes gheUrl from the request (the query string
for device-code, extraData for poll) and builds four outbound requests from
it. The only check was that the URL is https, and the requests used plain
fetch, which follows redirects. An https host the caller controls can answer
with a redirect to a plain-http internal address or to the cloud metadata
service, and the device-code route hands the JSON it gets back to the caller.
/api/oauth/ is on the public route list and the handler accepts any valid
API key, so an ordinary client key is enough to do this.
Send those requests through safeOutboundFetch with the provider outbound
guard, the same policy other operator-supplied provider URLs use: private
hosts stay reachable for on-premises GitHub Enterprise Server, metadata and
link-local addresses are refused, and a redirect ends the request instead of
being followed. GitHub Enterprise Server serves these paths on the configured
host itself, so the normal flow does not depend on following a redirect.
Metadata addresses stay blocked when the operator has allowed private
provider URLs, since a GHE host is never one. What the host answers is no
longer relayed as is: the device-code response is cut down to the fields the
flow uses, upstream error bodies are not put into error messages, and poll
answers only carry the token fields and a known device-flow error code. The
poll handler also read the response body twice on the non-JSON path, which
threw instead of reporting the bad answer. The two lookups after login now
leave their fields empty when the host redirects them, like they already did
for a non-2xx answer.
Related Issues
Validation
tests/unit/oauth-ghe-url-ssrf.test.tsnpm run lintTests Added Or Updated
tests/unit/oauth-ghe-url-ssrf.test.tsCoverage Notes
Reviewer Notes