From ea25ee6eb4153a8ace5c785cf3e7c6154d8aed6f Mon Sep 17 00:00:00 2001 From: Juber Shaikh <40266375+CodeWithJuber@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:25:34 +0400 Subject: [PATCH] fix: send a User-Agent on every outbound request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit reqwest sends no User-Agent by default. GitHub rejects such requests outright: 403 Request forbidden by administrative rules. Please make sure your request has a User-Agent header That is every one of the 25 github operations failing before authentication is even considered — the error looks like a credential problem and is not one. Several WAF-fronted provider APIs behave the same way, so this is not cosmetic. Both client builders now go through one `build_client`, which sets `connector-hub/ (+repo url)`. Verified against the live API: the same call that returned 403 now returns 401 Bad credentials, which is the expired token doing its job. --- crates/hub-net/src/client.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/crates/hub-net/src/client.rs b/crates/hub-net/src/client.rs index 3fbe114..f7d220a 100644 --- a/crates/hub-net/src/client.rs +++ b/crates/hub-net/src/client.rs @@ -5,6 +5,27 @@ use std::time::Duration; use crate::NetError; use crate::ssrf::SsrfPolicy; +/// The User-Agent every outbound request carries. +/// +/// reqwest sends no User-Agent by default. GitHub rejects such requests +/// outright with `403 Request forbidden by administrative rules`, and several +/// WAF-fronted provider APIs do the same, so this is not cosmetic — without it +/// entire providers are unreachable no matter how good the credentials are. +const USER_AGENT: &str = concat!( + "connector-hub/", + env!("CARGO_PKG_VERSION"), + " (+https://github.com/CodeWithJuber/connector-hub)" +); + +fn build_client(timeout_secs: u64) -> reqwest::Client { + reqwest::Client::builder() + .timeout(Duration::from_secs(timeout_secs)) + .redirect(reqwest::redirect::Policy::none()) + .user_agent(USER_AGENT) + .build() + .expect("failed to build HTTP client") +} + pub struct NetClient { policy: SsrfPolicy, timeout_secs: u64, @@ -13,11 +34,7 @@ pub struct NetClient { impl NetClient { pub fn new(policy: SsrfPolicy) -> Self { - let client = reqwest::Client::builder() - .timeout(Duration::from_secs(30)) - .redirect(reqwest::redirect::Policy::none()) - .build() - .expect("failed to build HTTP client"); + let client = build_client(30); Self { policy, @@ -28,11 +45,7 @@ impl NetClient { pub fn with_timeout(mut self, secs: u64) -> Self { self.timeout_secs = secs; - self.client = reqwest::Client::builder() - .timeout(Duration::from_secs(secs)) - .redirect(reqwest::redirect::Policy::none()) - .build() - .expect("failed to build HTTP client"); + self.client = build_client(secs); self }