mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 20:21:11 +00:00
* fix(github-copilot): reject unsupported OAuth enterprise domain before refresh and model routing Legacy github-copilot OAuth credentials can carry a non-github.com enterpriseUrl. The token-refresh path templated it into the endpoint and sent the bearer refresh token there with no allowlist, and the model routing path derived a base URL from the same credential, so an unexpired credential kept routing its access token to that origin without ever reaching a refresh. A persisted credential origin that is not on the Copilot host allowlist is now rejected before any request is issued and before any model URL is produced. Because an off-allowlist origin means the stored access token may itself have been minted by that origin, the credential is refused as a whole rather than coerced to github.com: modifyModels withholds the github-copilot models so the token has no route, and its proxy-ep is never trusted. Login rejects an unsupported host up front, so every path that turns a domain into a bearer-bearing URL validates first. formatAuthDoctorHint guides affected users to re-authenticate, mirroring the qwen-portal hint. Supported hosts are unchanged: github.com and *.ghe.com data-residency tenants still route and refresh as before. The allowlist plus a new isSupportedGithubCopilotDomain predicate move to a dependency-free plugin-sdk leaf so the core OAuth runtime can share them without closing a module cycle; plugin-sdk/provider-auth re-exports normalizeGithubCopilotDomain, so the plugin-owned GitHub Copilot provider keeps its existing import. Fixes #103078. * fix(github-copilot): resolve provider auth base conflict * fix(github-copilot): resolve provider auth base conflict * fix(github-copilot): validate token proxy endpoint * fix(github-copilot): remove stale helper import --------- Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Peter Steinberger <peter@steipete.me>
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import {
|
|
DEFAULT_GITHUB_COPILOT_DOMAIN,
|
|
isSupportedGithubCopilotDomain,
|
|
normalizeGithubCopilotDomain,
|
|
} from "./github-copilot-domain.js";
|
|
|
|
export type GithubCopilotTokenEndpointResolution = {
|
|
hasProxyEndpoint: boolean;
|
|
baseUrl: string | null;
|
|
};
|
|
|
|
function isSupportedGithubCopilotApiHost(host: string, enterpriseDomain?: string): boolean {
|
|
if (host === "copilot-proxy.githubusercontent.com" || host.endsWith(".githubcopilot.com")) {
|
|
return true;
|
|
}
|
|
if (
|
|
!enterpriseDomain ||
|
|
!isSupportedGithubCopilotDomain(enterpriseDomain) ||
|
|
normalizeGithubCopilotDomain(enterpriseDomain) === DEFAULT_GITHUB_COPILOT_DOMAIN
|
|
) {
|
|
return false;
|
|
}
|
|
const tenant = normalizeGithubCopilotDomain(enterpriseDomain);
|
|
return host === tenant || host.endsWith(`.${tenant}`);
|
|
}
|
|
|
|
/**
|
|
* Resolves the optional `proxy-ep` hint embedded in a Copilot API token.
|
|
* The hint is untrusted credential data: only GitHub-owned Copilot hosts, or
|
|
* service hosts below the credential's validated GHE.com tenant, may receive it.
|
|
*/
|
|
export function resolveGithubCopilotTokenEndpoint(
|
|
token: string,
|
|
enterpriseDomain?: string,
|
|
): GithubCopilotTokenEndpointResolution {
|
|
const match = token.trim().match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i);
|
|
const proxyEndpoint = match?.[1]?.trim();
|
|
if (!proxyEndpoint) {
|
|
return { hasProxyEndpoint: false, baseUrl: null };
|
|
}
|
|
|
|
const urlText = /^https?:\/\//i.test(proxyEndpoint) ? proxyEndpoint : `https://${proxyEndpoint}`;
|
|
try {
|
|
const url = new URL(urlText);
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
return { hasProxyEndpoint: true, baseUrl: null };
|
|
}
|
|
const apiHost = url.hostname.toLowerCase().replace(/^proxy\./, "api.");
|
|
return {
|
|
hasProxyEndpoint: true,
|
|
baseUrl: isSupportedGithubCopilotApiHost(apiHost, enterpriseDomain)
|
|
? `https://${apiHost}`
|
|
: null,
|
|
};
|
|
} catch {
|
|
return { hasProxyEndpoint: true, baseUrl: null };
|
|
}
|
|
}
|