fix(copilot): validate supported token prefixes

This commit is contained in:
zhao
2026-04-21 21:32:48 +08:00
committed by Teknium
parent fd15cd003e
commit c5cdd92254
2 changed files with 22 additions and 3 deletions
+9 -2
View File
@@ -25,7 +25,10 @@ logger = logging.getLogger(__name__)
# VS Code's GitHub App client ID: mints ghu_* tokens exchangeable for Copilot API JWTs (needed for
# internal-only models / enterprise endpoints). The opencode App ID mints gho_* tokens that 404.
COPILOT_OAUTH_CLIENT_ID = "Iv1.b507a08c87ecfe98"
_CLASSIC_PAT_PREFIX = "ghp_" # rejected by the Copilot API (gho_ / github_pat_ / ghu_ work)
_CLASSIC_PAT_PREFIX = "ghp_" # rejected by the Copilot API
# Token families the Copilot API exchanges. Anything else (a random string in GITHUB_TOKEN) used
# to pass validation and then fail downstream with an opaque auth error (#12650).
_SUPPORTED_PREFIXES = ("gho_", "github_pat_", "ghu_")
COPILOT_ENV_VARS = ("COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN")
_DEVICE_CODE_POLL_INTERVAL = 5 # seconds
_DEVICE_CODE_POLL_SAFETY_MARGIN = 3 # seconds
@@ -43,6 +46,10 @@ def validate_copilot_token(token: str) -> tuple[bool, str]:
" → `copilot login` or `hermes model` to authenticate via OAuth\n"
" → A fine-grained PAT (github_pat_*) with Copilot Requests permission\n"
" → `gh auth login` with the default device code flow (produces gho_* tokens)")
if not token.startswith(_SUPPORTED_PREFIXES):
return False, (
"Unsupported GitHub token format for the Copilot API. "
f"Supported token prefixes: {', '.join(_SUPPORTED_PREFIXES)}.")
return True, "OK"
@@ -73,7 +80,7 @@ def resolve_copilot_token() -> tuple[str, str]:
if token:
valid, msg = validate_copilot_token(token)
if not valid:
raise ValueError(f"Token from `gh auth token` is a classic PAT (ghp_*). {msg}")
raise ValueError(f"Token from `gh auth token` is not usable with Copilot. {msg}")
return token, "gh auth token"
return "", ""
+13 -1
View File
@@ -14,6 +14,18 @@ class TestTokenValidation:
assert "Classic Personal Access Tokens" in msg
assert "ghp_" in msg
@pytest.mark.parametrize("token", ["gho_abcdefghijklmnop1234", "github_pat_abcdefghijklmnop1234", "ghu_abcdefghijklmnop1234"])
def test_supported_token_families_accepted(self, token):
from hermes_cli.copilot_auth import validate_copilot_token
assert validate_copilot_token(token) == (True, "OK")
def test_arbitrary_string_rejected(self):
"""A non-GitHub value in GITHUB_TOKEN must fail validation instead of reaching the API (#12650)."""
from hermes_cli.copilot_auth import validate_copilot_token
valid, msg = validate_copilot_token("not_a_github_token")
assert valid is False
assert "Supported token prefixes" in msg
class TestResolveToken:
"""Token resolution with env var priority."""
@@ -37,7 +49,7 @@ class TestResolveToken:
monkeypatch.delenv("GH_TOKEN", raising=False)
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
with patch("hermes_cli.copilot_auth._try_gh_cli_token", return_value="ghp_classic"):
with pytest.raises(ValueError, match="classic PAT"):
with pytest.raises(ValueError, match="Classic Personal Access Tokens"):
resolve_copilot_token()
def test_invalid_env_var_skips_gh_cli_fallback(self, monkeypatch):