From 61b4be517ee8fc8c5761c41c45908fc0afe961ab Mon Sep 17 00:00:00 2001 From: Risu <79110363+risu729@users.noreply.github.com> Date: Sun, 12 Apr 2026 14:19:21 +1000 Subject: [PATCH] fix(github): avoid auth on release asset downloads --- src/github.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++++---- src/http.rs | 2 +- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/github.rs b/src/github.rs index b8bdcd7644..cfb8b3a5ea 100644 --- a/src/github.rs +++ b/src/github.rs @@ -369,15 +369,28 @@ impl fmt::Display for TokenSource { } /// Normalize a URL hostname to the canonical host used for token lookups. -/// Maps "api.github.com" and "*.githubusercontent.com" to "github.com". +/// Maps "api.github.com" and supported "*.githubusercontent.com" hosts to "github.com". fn canonical_host(host: Option<&str>) -> Option<&str> { match host { Some("api.github.com") => Some("github.com"), - Some(h) if h.ends_with(".githubusercontent.com") => Some("github.com"), + Some(h) if is_githubusercontent_auth_host(h) => Some("github.com"), other => other, } } +pub fn is_githubusercontent_auth_host(host: &str) -> bool { + host.ends_with(".githubusercontent.com") && !is_github_release_asset_host(host) +} + +fn is_github_release_asset_host(host: &str) -> bool { + matches!( + host, + "objects.githubusercontent.com" + | "objects-origin.githubusercontent.com" + | "release-assets.githubusercontent.com" + ) +} + /// Resolve the GitHub token for the given hostname, returning the token and its source. /// /// Priority: @@ -390,10 +403,13 @@ fn canonical_host(host: Option<&str>) -> Option<&str> { pub fn resolve_token(host: &str) -> Option<(String, TokenSource)> { let settings = Settings::get(); - let is_ghcom = host == "github.com" - || host == "api.github.com" - || host.ends_with(".githubusercontent.com"); - let lookup_host = if host == "api.github.com" || host.ends_with(".githubusercontent.com") { + if is_github_release_asset_host(host) { + return None; + } + + let is_ghcom = + host == "github.com" || host == "api.github.com" || is_githubusercontent_auth_host(host); + let lookup_host = if host == "api.github.com" || is_githubusercontent_auth_host(host) { "github.com" } else { host @@ -568,6 +584,39 @@ struct GhHostEntry { mod tests { use super::*; + static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + fn with_github_token(test_fn: F) -> R + where + F: FnOnce() -> R, + { + let _guard = TEST_ENV_LOCK.lock().unwrap(); + let orig_mise = std::env::var("MISE_GITHUB_TOKEN").ok(); + let orig_api = std::env::var("GITHUB_API_TOKEN").ok(); + let orig_gh = std::env::var("GITHUB_TOKEN").ok(); + + env::remove_var("MISE_GITHUB_TOKEN"); + env::remove_var("GITHUB_API_TOKEN"); + env::set_var("GITHUB_TOKEN", "ghp_test"); + + let result = test_fn(); + + match orig_mise { + Some(v) => env::set_var("MISE_GITHUB_TOKEN", v), + None => env::remove_var("MISE_GITHUB_TOKEN"), + } + match orig_api { + Some(v) => env::set_var("GITHUB_API_TOKEN", v), + None => env::remove_var("GITHUB_API_TOKEN"), + } + match orig_gh { + Some(v) => env::set_var("GITHUB_TOKEN", v), + None => env::remove_var("GITHUB_TOKEN"), + } + + result + } + #[test] fn test_parse_github_tokens() { let toml = r#" @@ -604,6 +653,41 @@ something_else = "value" assert!(result.is_empty()); } + #[test] + fn test_githubusercontent_auth_hosts_exclude_release_assets() { + assert!(is_githubusercontent_auth_host("raw.githubusercontent.com")); + assert!(!is_githubusercontent_auth_host( + "objects.githubusercontent.com" + )); + assert!(!is_githubusercontent_auth_host( + "objects-origin.githubusercontent.com" + )); + assert!(!is_githubusercontent_auth_host( + "release-assets.githubusercontent.com" + )); + } + + #[test] + fn test_release_asset_hosts_do_not_use_github_token() { + with_github_token(|| { + for host in [ + "objects.githubusercontent.com", + "objects-origin.githubusercontent.com", + "release-assets.githubusercontent.com", + ] { + let headers = + get_headers(format!("https://{host}/github-production-release-asset")); + assert!( + !headers.contains_key(reqwest::header::AUTHORIZATION), + "{host} should not use GitHub auth" + ); + } + + let headers = get_headers("https://raw.githubusercontent.com/owner/repo/main/file.txt"); + assert!(headers.contains_key(reqwest::header::AUTHORIZATION)); + }); + } + fn make_release(tag: &str) -> GithubRelease { GithubRelease { tag_name: tag.to_string(), diff --git a/src/http.rs b/src/http.rs index 9230356d20..3c62c30c13 100644 --- a/src/http.rs +++ b/src/http.rs @@ -440,7 +440,7 @@ fn host_auth_headers(url: &Url) -> HeaderMap { let is_github = host == "api.github.com" || host == "github.com" - || host.ends_with(".githubusercontent.com") + || crate::github::is_githubusercontent_auth_host(host) || crate::github::is_gh_host(host); if is_github { return crate::github::get_headers(url.as_str());