diff --git a/src/utils/api.rs b/src/utils/api.rs index 1226f36..bb040f3 100644 --- a/src/utils/api.rs +++ b/src/utils/api.rs @@ -2,7 +2,7 @@ use crate::incremental::{BaselineRef, IncrementalPlan}; use crate::log::debug; use crate::manifest::EncodedManifest; use crate::utils; -use corgea::vuln_api::{auth_header, source}; +use corgea::vuln_api::{auth_header, default_headers, source}; use reqwest::header::HeaderMap; use reqwest::{ blocking::multipart, @@ -53,6 +53,7 @@ static SHARED_CLIENT: std::sync::LazyLock = std::sync::LazyLock::new(|| { let mut builder = reqwest::blocking::Client::builder() .timeout(REQUEST_TIMEOUT) + .default_headers(default_headers()) .cookie_provider(COOKIE_JAR.clone()); if let Ok(https_proxy) = std::env::var("https_proxy") { @@ -1770,10 +1771,9 @@ pub fn exchange_code_for_token( base_url: &str, code: &str, ) -> Result> { - let client = reqwest::blocking::Client::new(); let exchange_url = format!("{}{}/authorize", base_url, API_BASE); - let response = client + let response = SHARED_CLIENT .get(&exchange_url) .header("CORGEA-SOURCE", source()) .query(&[("code", code)]) @@ -2807,6 +2807,19 @@ mod tests { base } + #[test] + fn unauthenticated_requests_send_cli_version() { + let (base, requests) = corgea::vuln_api_stub::spawn_capturing_vuln_api_stub(); + let _ = exchange_code_for_token(&base, "code"); + + let requests = requests.lock().unwrap(); + assert_eq!( + corgea::vuln_api_stub::header_value(&requests[0], corgea::vuln_api::CLI_VERSION_HEADER) + .as_deref(), + Some(env!("CARGO_PKG_VERSION")) + ); + } + #[test] fn repo_url_matches_path_compares_the_whole_path() { // Same repo across scheme/.git/trailing-slash/port variants. diff --git a/src/vuln_api/mod.rs b/src/vuln_api/mod.rs index 7b714a1..8d6ce02 100644 --- a/src/vuln_api/mod.rs +++ b/src/vuln_api/mod.rs @@ -154,6 +154,21 @@ pub(crate) fn user_agent(label: &str) -> String { format!("corgea-cli/{} ({label})", env!("CARGO_PKG_VERSION")) } +/// Header carrying the CLI release on every request to Corgea, so the server +/// can record which version triggered a scan. +pub const CLI_VERSION_HEADER: &str = "CORGEA-CLI-VERSION"; + +/// Headers every Corgea client sends regardless of auth state. Installed as +/// client defaults so no call site can forget them. +pub fn default_headers() -> reqwest::header::HeaderMap { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + CLI_VERSION_HEADER, + reqwest::header::HeaderValue::from_static(env!("CARGO_PKG_VERSION")), + ); + headers +} + /// Build (once) and clone the shared vuln-api client. A blocking reqwest /// client owns a runtime thread, and a gate run checks many packages — /// cache it. `Client` clones share the same pool, so the clone is cheap. @@ -164,6 +179,7 @@ pub fn http_client() -> Result { reqwest::blocking::Client::builder() .timeout(REQUEST_TIMEOUT) .user_agent(user_agent("vuln-api")) + .default_headers(default_headers()) .redirect(reqwest::redirect::Policy::none()) .build() .map_err(|e| format!("failed to build vuln-api http client: {}", e)) @@ -797,6 +813,10 @@ mod tests { header_value(&request, "CORGEA-SOURCE").as_deref(), Some("cli") ); + assert_eq!( + header_value(&request, CLI_VERSION_HEADER).as_deref(), + Some(env!("CARGO_PKG_VERSION")) + ); } #[test]