diff --git a/crates/ai-comfy-env/src/source/hosts.rs b/crates/ai-comfy-env/src/source/hosts.rs
--- a/crates/ai-comfy-env/src/source/hosts.rs
+++ b/crates/ai-comfy-env/src/source/hosts.rs
@@ -41,9 +41,10 @@ impl SourceKind {
                 download: &["huggingface.co", ".huggingface.co", ".hf.co"],
                 token_host: Some("huggingface.co"),
             },
-            // Release assets of public repositories only: no token is ever sent to GitHub.
+            // The resolver reads release metadata and licences from the API before downloading
+            // public assets from the web/content hosts. No token is ever sent to GitHub.
             Self::GitHubRelease => HostRules {
-                download: &["github.com", ".githubusercontent.com"],
+                download: &["api.github.com", "github.com", ".githubusercontent.com"],
                 token_host: None,
             },
             Self::Civitai => HostRules {
diff --git a/crates/ai-comfy-env/src/source/resolve/api.rs b/crates/ai-comfy-env/src/source/resolve/api.rs
--- a/crates/ai-comfy-env/src/source/resolve/api.rs
+++ b/crates/ai-comfy-env/src/source/resolve/api.rs
@@ -195,3 +195,50 @@ pub(in crate::source) fn tidy(text: &str) -> String {
         line.trim().to_owned()
     }
 }
+
+#[cfg(test)]
+#[allow(clippy::unwrap_used)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn github_release_api_uses_the_default_host_without_a_token() {
+        let settings = SourceSettings::default();
+        let api = Api::new(&settings);
+        let token = SecretString::new("unused-token");
+        for pieces in [
+            &["repos", "owner", "repo", "releases", "tags", "v1"][..],
+            &["repos", "owner", "repo"][..],
+        ] {
+            let address = join(&settings.bases.github_api, pieces).unwrap();
+            let (url, forwarded) = api
+                .checked(SourceKind::GitHubRelease, &address, Some(&token))
+                .unwrap();
+            assert_eq!(url.as_str(), address);
+            assert!(forwarded.is_none(), "public release queries need no token");
+        }
+    }
+
+    #[test]
+    fn github_release_api_does_not_trust_other_github_hosts_or_lookalikes() {
+        let settings = SourceSettings::default();
+        let api = Api::new(&settings);
+        for host in [
+            "uploads.github.com",
+            "evilapi.github.com",
+            "sub.api.github.com",
+            "api.github.com.example.com",
+            "api.github.com.",
+        ] {
+            assert!(
+                api.checked(
+                    SourceKind::GitHubRelease,
+                    &format!("https://{host}/repos/owner/repo"),
+                    None,
+                )
+                .is_err(),
+                "unexpectedly allowed {host}",
+            );
+        }
+    }
+}
diff --git a/tools/mutation/mutation_check.py b/tools/mutation/mutation_check.py
--- a/tools/mutation/mutation_check.py
+++ b/tools/mutation/mutation_check.py
@@ -116,7 +116,9 @@ U3 = [
     ('host-suffix-by-text', HOSTS, 'host.len() > rest.len() + 1 && host.ends_with(&format!(".{rest}"))', 'host.ends_with(rest)', E, 'source_basics', 'host_pattern_matches'),
     ('host-exact-by-suffix', HOSTS, 'None => host == pattern,', 'None => host.ends_with(&pattern),', E, 'source_basics', 'host_pattern_matches'),
     ('subdomain-pattern-matches-apex', HOSTS, 'host.len() > rest.len() + 1 && host.ends_with(&format!(".{rest}"))', 'host == rest || host.ends_with(&format!(".{rest}"))', E, 'source_basics', 'host_pattern_matches'),
-    ('github-gets-a-token-host', HOSTS, 'download: &["github.com", ".githubusercontent.com"],\n                token_host: None,', 'download: &["github.com", ".githubusercontent.com"],\n                token_host: Some("github.com"),', E, 'source_basics', 'names_its_own_hosts'),
+    ('github-gets-a-token-host', HOSTS, 'download: &["api.github.com", "github.com", ".githubusercontent.com"],\n                token_host: None,', 'download: &["api.github.com", "github.com", ".githubusercontent.com"],\n                token_host: Some("github.com"),', E, 'source_basics', 'names_its_own_hosts'),
+    ('github-release-api-host-missing', HOSTS, 'download: &["api.github.com", "github.com", ".githubusercontent.com"],', 'download: &["github.com", ".githubusercontent.com"],', E, 'lib', 'github_release_api_uses_the_default_host'),
+    ('github-release-api-host-widened', HOSTS, 'download: &["api.github.com", "github.com", ".githubusercontent.com"],', 'download: &[".github.com", "github.com", ".githubusercontent.com"],', E, 'lib', 'github_release_api_does_not_trust'),
     ('hugging-face-token-host-widened', HOSTS, 'download: &["huggingface.co", ".huggingface.co", ".hf.co"],\n                token_host: Some("huggingface.co"),', 'download: &["huggingface.co", ".huggingface.co", ".hf.co"],\n                token_host: Some(".hf.co"),', E, 'source_basics', 'names_its_own_hosts'),
     ('pickle-runs-no-code', FORMAT, 'self == Self::Pickle', 'false', E, 'source_basics', 'extension_and_pickle'),
     ('bin-not-pickle', FORMAT, '"ckpt" | "pt" | "pth" | "bin" | "pkl" | "pickle"', '"ckpt" | "pt" | "pth" | "pkl" | "pickle"', E, 'source_basics', 'extension_and_pickle'),
