fix(executor): add User-Agent header and handle auth redirects properly

The action resolver was making HTTP requests to raw.githubusercontent.com
with no User-Agent header, which is the kind of thing that gets you
silently rate-limited by GitHub's CDN. Not great when your whole
resolution strategy depends on those requests actually succeeding.

While at it, the no-redirect policy on the authenticated retry path was
*correct* for preventing token leakage to non-GitHub hosts, but it also
meant that legitimate CDN redirects (3xx) would fall through to the
success check and produce a misleading "HTTP 301 fetching..." error.
Fix this by following the redirect with HTTP_CLIENT (no auth header)
when we get a 3xx, so we get the content without leaking the token.

Also add a note on the SHA-1 detection in shallow_clone — it only
matches 40-char hex strings, which will need updating if GitHub ever
adopts SHA-256 refs.
This commit is contained in:
bahdotsh
2026-03-28 13:07:51 +05:30
parent 3ee75e6aa8
commit ce3099d757
2 changed files with 30 additions and 2 deletions

View File

@@ -76,6 +76,7 @@ static ACTION_CACHE: Lazy<RwLock<BoundedCache>> = Lazy::new(|| RwLock::new(Bound
static HTTP_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.user_agent("wrkflw")
.build()
.expect("Failed to create HTTP client")
});
@@ -133,15 +134,40 @@ async fn fetch_and_parse(
if let Ok(token) = std::env::var("GITHUB_TOKEN") {
let no_redirect_client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.user_agent("wrkflw")
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
no_redirect_client
let auth_response = no_redirect_client
.get(&url)
.header("Authorization", format!("token {}", token))
.send()
.await
.map_err(|e| format!("Failed to fetch {}: {}", url, e))?
.map_err(|e| format!("Failed to fetch {}: {}", url, e))?;
// The no-redirect policy prevents token leakage, but the server may
// legitimately redirect (CDN routing). If we get a 3xx, follow it
// without the auth header to avoid leaking the token.
if auth_response.status().is_redirection() {
if let Some(location) = auth_response.headers().get(reqwest::header::LOCATION) {
let redirect_url = location
.to_str()
.map_err(|_| "Invalid redirect URL encoding".to_string())?;
HTTP_CLIENT
.get(redirect_url)
.send()
.await
.map_err(|e| format!("Failed to follow redirect {}: {}", redirect_url, e))?
} else {
return Err(format!(
"HTTP {} (redirect with no Location header) fetching {}",
auth_response.status(),
url
));
}
} else {
auth_response
}
} else {
response
}

View File

@@ -642,6 +642,8 @@ async fn shallow_clone(
git_ref: &str,
target_dir: &Path,
) -> Result<(), ExecutionError> {
// NOTE: This only detects SHA-1 (40 hex chars). Git's SHA-256 transition uses
// 64-char hashes — update this check if/when GitHub adopts SHA-256 refs.
let is_sha = git_ref.len() == 40 && git_ref.chars().all(|c| c.is_ascii_hexdigit());
if is_sha {