Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/cat-cave_ai-usage.json
T

1 line
22 KiB
JSON

{"repo": "cat-cave/ai-usage", "files": {"rust/crates/ai-usage/src/providers/deepseek.rs": {"1ac3b34b7f5e548178a92a0ab69fd7d360f80513": {"t": 1785746254.2003632, "n": 7124, "h": "d3c23c0ef2ba9e0d", "c": "//! DeepSeek provider. See `SPEC.md §2`.\n//!\n//! `GET https://api.deepseek.com/user/balance` (Bearer) → `balance_infos[]` with\n//! `total_balance`, `granted_balance`, `topped_up_balance`. Pure-API balance;\n//! no windowed quota. Secret file: `deepseek-api-key`.\n\nuse async_trait::async_trait;\nuse rust_decimal::Decimal;\nuse serde::Deserialize;\n\nuse crate::config::Config;\nuse crate::error::{Error, Result};\nuse crate::model::{ApiBalance, Currency, Money, ProviderId, ProviderReport, ProviderStatus};\nuse crate::provider::Provider;\nuse crate::source::{Source, Sourced, UnavailableReason};\n\nconst DEFAULT_BASE: &str = \"https://api.deepseek.com\";\nconst ENDPOINT: &str = \"deepseek.balance\";\n\npub struct DeepSeekProvider {\n token: Option<String>,\n base: String,\n client: reqwest::Client,\n}\n\nimpl DeepSeekProvider {\n pub fn from_config(cfg: &Config) -> Result<Self> {\n let token = cfg.resolve_provider_key(\n \"deepseek\",\n \"deepseek\",\n cfg.providers\n .deepseek\n .as_ref()\n .and_then(|p| p.api_key_path.as_deref()),\n )?;\n let base = std::env::var(\"DEEPSEEK_API_BASE\")\n .ok()\n .filter(|s| !s.trim().is_empty())\n .map(|s| {\n crate::config::assert_https(&s)?;\n Ok::<String, Error>(s.trim_end_matches('/').to_string())\n })\n .transpose()?\n .unwrap_or_else(|| DEFAULT_BASE.to_string());\n Ok(Self {\n token,\n base,\n client: reqwest::Client::builder()\n .user_agent(\"ai-usage\")\n .build()\n .map_err(|e| Error::Http(format!(\"client build: {e}\")))?,\n })\n }\n}\n\n#[async_trait]\nimpl Provider for DeepSeekProvider {\n fn id(&self) -> ProviderId {\n ProviderId::DeepSeek\n }\n fn configured(&self) -> bool {\n self.token.is_some()\n }\n\n async fn fetch(&self) -> Result<ProviderReport> {\n let now = time::OffsetDateTime::now_utc();\n let token = match &self.token {\n Some(t) => t.clone(),\n None => return Ok(unavailable(UnavailableReason::NoCredentials, now)),\n };\n let resp = match self\n .client\n .get(format!(\"{}/user/balance\", self.base))\n .bearer_auth(token)\n .header(\"Accept\", \"application/json\")\n .send()\n .await\n {\n Ok(r) => r,\n Err(e) => {\n return Ok(error_report(\n UnavailableReason::Network,\n &format!(\n \"balance: {}\",\n crate::redact::Redactor::redact_str(&e.to_string())\n ),\n now,\n ));\n }\n };\n let status = resp.status().as_u16();\n if status == 401 || status == 403 {\n return Ok(error_report(\n UnavailableReason::AuthFailed,\n &format!(\"status {status}\"),\n now,\n ));\n }\n if !resp.status().is_success() {\n return Ok(error_report(\n UnavailableReason::UnknownEndpoint,\n &format!(\"status {status}\"),\n now,\n ));\n }\n let body: BalanceResponse = match resp.json().await {\n Ok(b) => b,\n Err(e) => return Ok(error_report(UnavailableReason::Parse, &format!(\"{e}\"), now)),\n };\n Ok(build(&body, status, now))\n }\n}\n\n#[derive(Debug, Deserialize)]\n#[allow(dead_code)]\nstruct BalanceResponse {\n #[serde(default)]\n is_available: bool,\n #[serde(default)]\n balance_infos: Vec<BalanceInfo>,\n}\n#[derive(Debug, Deserialize)]\nstruct BalanceInfo {\n #[serde(default)]\n currency: Option<String>,\n #[serde(default)]\n total_balance: Option<String>,\n #[serde(default)]\n granted_balance: Option<String>,\n #[serde(default)]\n topped_up_balance: Option<String>,\n}\n\nfn parse_money(s: Option<&str>, currency: Currency) -> Option<Money> {\n s.and_then(|x| x.parse::<f64>().ok()).map(|v| Money {\n amount: Decimal::from_f64_retain(v).unwrap_or(Decimal::ZERO),\n currency,\n })\n}\n\nfn build(body: &BalanceResponse, status: u16, now: time::OffsetDateTime) -> ProviderReport {\n use crate::source::unavailable_field;\n let ttl = Some(time::Duration::seconds(60));\n\n // Prefer USD when multiple currencies are present.\n let info = body\n .balance_infos\n .iter()\n .find(|i| i.currency.as_deref() == Some(\"USD\"))\n .or_else(|| body.balance_infos.first());\n\n let api_balance = info.map_or_else(\n || unavailable_field(UnavailableReason::Parse),\n |i| {\n let currency = match i.currency.as_deref() {\n Some(\"USD\") => Currency::Usd,\n Some(\"CNY\") => Currency::Cny,\n Some(\"EUR\") => Currency::Eur,\n _ => Currency::Unknown,\n };\n Sourced {\n value: Some(ApiBalance {\n total: parse_money(i.total_balance.as_deref(), currency).unwrap_or(Money {\n amount: Decimal::ZERO,\n currency,\n }),\n granted: parse_money(i.granted_balance.as_deref(), currency),\n paid: parse_money(i.topped_up_balance.as_deref(), currency),\n }),\n source: Source::live(ENDPOINT, Some(status)),\n observed_at: now,\n ttl,\n }\n },\n );\n\n ProviderReport {\n id: ProviderId::DeepSeek,\n account: unavailable_field(UnavailableReason::NotConfigured),\n windows: vec![],\n banked_resets: vec![],\n plan_capacity: unavailable_field(UnavailableReason::NotConfigured),\n paid_overflow: unavailable_field(UnavailableReason::NotConfigured),\n api_balance,\n local_velocity: unavailable_field(UnavailableReason::NotConfigured),\n status: if info.is_some() {\n ProviderStatus::Ok\n } else {\n ProviderStatus::Degraded {\n notes: vec![\"no balance infos\".into()],\n }\n },\n }\n}\n\nfn unavailable(reason: UnavailableReason, now: time::OffsetDateTime) -> ProviderReport {\n use crate::source::unavailable_field;\n let _ = now;\n ProviderReport {\n id: ProviderId::DeepSeek,\n account: unavailable_field(reason),\n windows: vec![],\n banked_resets: vec![],\n plan_capacity: unavailable_field(reason),\n paid_overflow: unavailable_field(reason),\n api_balance: unavailable_field(reason),\n local_velocity: unavailable_field(reason),\n status: ProviderStatus::Unavailable {\n reason: format!(\"{reason:?}\").to_lowercase(),\n },\n }\n}\n\nfn error_report(reason: UnavailableReason, msg: &str, now: time::OffsetDateTime) -> ProviderReport {\n let mut r = unavailable(reason, now);\n r.status = ProviderStatus::Unavailable {\n reason: crate::redact::Redactor::redact_str(msg),\n };\n r\n}\n"}, "b35a16a333eaa16260f985ecdd3137de32b7ce34": {"t": 1785761116.277771, "n": 7124, "h": "d3c23c0ef2ba9e0d", "c": "//! DeepSeek provider. See `SPEC.md §2`.\n//!\n//! `GET https://api.deepseek.com/user/balance` (Bearer) → `balance_infos[]` with\n//! `total_balance`, `granted_balance`, `topped_up_balance`. Pure-API balance;\n//! no windowed quota. Secret file: `deepseek-api-key`.\n\nuse async_trait::async_trait;\nuse rust_decimal::Decimal;\nuse serde::Deserialize;\n\nuse crate::config::Config;\nuse crate::error::{Error, Result};\nuse crate::model::{ApiBalance, Currency, Money, ProviderId, ProviderReport, ProviderStatus};\nuse crate::provider::Provider;\nuse crate::source::{Source, Sourced, UnavailableReason};\n\nconst DEFAULT_BASE: &str = \"https://api.deepseek.com\";\nconst ENDPOINT: &str = \"deepseek.balance\";\n\npub struct DeepSeekProvider {\n token: Option<String>,\n base: String,\n client: reqwest::Client,\n}\n\nimpl DeepSeekProvider {\n pub fn from_config(cfg: &Config) -> Result<Self> {\n let token = cfg.resolve_provider_key(\n \"deepseek\",\n \"deepseek\",\n cfg.providers\n .deepseek\n .as_ref()\n .and_then(|p| p.api_key_path.as_deref()),\n )?;\n let base = std::env::var(\"DEEPSEEK_API_BASE\")\n .ok()\n .filter(|s| !s.trim().is_empty())\n .map(|s| {\n crate::config::assert_https(&s)?;\n Ok::<String, Error>(s.trim_end_matches('/').to_string())\n })\n .transpose()?\n .unwrap_or_else(|| DEFAULT_BASE.to_string());\n Ok(Self {\n token,\n base,\n client: reqwest::Client::builder()\n .user_agent(\"ai-usage\")\n .build()\n .map_err(|e| Error::Http(format!(\"client build: {e}\")))?,\n })\n }\n}\n\n#[async_trait]\nimpl Provider for DeepSeekProvider {\n fn id(&self) -> ProviderId {\n ProviderId::DeepSeek\n }\n fn configured(&self) -> bool {\n self.token.is_some()\n }\n\n async fn fetch(&self) -> Result<ProviderReport> {\n let now = time::OffsetDateTime::now_utc();\n let token = match &self.token {\n Some(t) => t.clone(),\n None => return Ok(unavailable(UnavailableReason::NoCredentials, now)),\n };\n let resp = match self\n .client\n .get(format!(\"{}/user/balance\", self.base))\n .bearer_auth(token)\n .header(\"Accept\", \"application/json\")\n .send()\n .await\n {\n Ok(r) => r,\n Err(e) => {\n return Ok(error_report(\n UnavailableReason::Network,\n &format!(\n \"balance: {}\",\n crate::redact::Redactor::redact_str(&e.to_string())\n ),\n now,\n ));\n }\n };\n let status = resp.status().as_u16();\n if status == 401 || status == 403 {\n return Ok(error_report(\n UnavailableReason::AuthFailed,\n &format!(\"status {status}\"),\n now,\n ));\n }\n if !resp.status().is_success() {\n return Ok(error_report(\n UnavailableReason::UnknownEndpoint,\n &format!(\"status {status}\"),\n now,\n ));\n }\n let body: BalanceResponse = match resp.json().await {\n Ok(b) => b,\n Err(e) => return Ok(error_report(UnavailableReason::Parse, &format!(\"{e}\"), now)),\n };\n Ok(build(&body, status, now))\n }\n}\n\n#[derive(Debug, Deserialize)]\n#[allow(dead_code)]\nstruct BalanceResponse {\n #[serde(default)]\n is_available: bool,\n #[serde(default)]\n balance_infos: Vec<BalanceInfo>,\n}\n#[derive(Debug, Deserialize)]\nstruct BalanceInfo {\n #[serde(default)]\n currency: Option<String>,\n #[serde(default)]\n total_balance: Option<String>,\n #[serde(default)]\n granted_balance: Option<String>,\n #[serde(default)]\n topped_up_balance: Option<String>,\n}\n\nfn parse_money(s: Option<&str>, currency: Currency) -> Option<Money> {\n s.and_then(|x| x.parse::<f64>().ok()).map(|v| Money {\n amount: Decimal::from_f64_retain(v).unwrap_or(Decimal::ZERO),\n currency,\n })\n}\n\nfn build(body: &BalanceResponse, status: u16, now: time::OffsetDateTime) -> ProviderReport {\n use crate::source::unavailable_field;\n let ttl = Some(time::Duration::seconds(60));\n\n // Prefer USD when multiple currencies are present.\n let info = body\n .balance_infos\n .iter()\n .find(|i| i.currency.as_deref() == Some(\"USD\"))\n .or_else(|| body.balance_infos.first());\n\n let api_balance = info.map_or_else(\n || unavailable_field(UnavailableReason::Parse),\n |i| {\n let currency = match i.currency.as_deref() {\n Some(\"USD\") => Currency::Usd,\n Some(\"CNY\") => Currency::Cny,\n Some(\"EUR\") => Currency::Eur,\n _ => Currency::Unknown,\n };\n Sourced {\n value: Some(ApiBalance {\n total: parse_money(i.total_balance.as_deref(), currency).unwrap_or(Money {\n amount: Decimal::ZERO,\n currency,\n }),\n granted: parse_money(i.granted_balance.as_deref(), currency),\n paid: parse_money(i.topped_up_balance.as_deref(), currency),\n }),\n source: Source::live(ENDPOINT, Some(status)),\n observed_at: now,\n ttl,\n }\n },\n );\n\n ProviderReport {\n id: ProviderId::DeepSeek,\n account: unavailable_field(UnavailableReason::NotConfigured),\n windows: vec![],\n banked_resets: vec![],\n plan_capacity: unavailable_field(UnavailableReason::NotConfigured),\n paid_overflow: unavailable_field(UnavailableReason::NotConfigured),\n api_balance,\n local_velocity: unavailable_field(UnavailableReason::NotConfigured),\n status: if info.is_some() {\n ProviderStatus::Ok\n } else {\n ProviderStatus::Degraded {\n notes: vec![\"no balance infos\".into()],\n }\n },\n }\n}\n\nfn unavailable(reason: UnavailableReason, now: time::OffsetDateTime) -> ProviderReport {\n use crate::source::unavailable_field;\n let _ = now;\n ProviderReport {\n id: ProviderId::DeepSeek,\n account: unavailable_field(reason),\n windows: vec![],\n banked_resets: vec![],\n plan_capacity: unavailable_field(reason),\n paid_overflow: unavailable_field(reason),\n api_balance: unavailable_field(reason),\n local_velocity: unavailable_field(reason),\n status: ProviderStatus::Unavailable {\n reason: format!(\"{reason:?}\").to_lowercase(),\n },\n }\n}\n\nfn error_report(reason: UnavailableReason, msg: &str, now: time::OffsetDateTime) -> ProviderReport {\n let mut r = unavailable(reason, now);\n r.status = ProviderStatus::Unavailable {\n reason: crate::redact::Redactor::redact_str(msg),\n };\n r\n}\n"}}, "rust/crates/ai-usage/src/redact.rs": {"1ac3b34b7f5e548178a92a0ab69fd7d360f80513": {"t": 1785747467.4778225, "n": 3338, "h": "65745c1b6e40fdf5", "c": "//! Secret redaction. Every output and error path routes potentially-sensitive\n//! strings through `Redactor`. See `SPEC.md §10`.\n\nuse std::sync::OnceLock;\n\nuse regex::Regex;\n\npub struct Redactor;\n\nimpl Redactor {\n /// Redact any credential-looking substring in `s`. Safe to call on any string.\n pub fn redact_str(s: &str) -> String {\n let mut out = s.to_string();\n for re in patterns() {\n out = re.replace_all(&out, \"[REDACTED]\").into_owned();\n }\n out\n }\n}\n\nfn patterns() -> &'static [Regex] {\n static P: OnceLock<Vec<Regex>> = OnceLock::new();\n P.get_or_init(|| {\n vec![\n Regex::new(r\"sk-or-v1-[A-Za-z0-9_-]+\").unwrap(),\n Regex::new(r\"sk-ant-[A-Za-z0-9_-]+\").unwrap(),\n Regex::new(r\"sk-cp-[A-Za-z0-9_-]+\").unwrap(),\n Regex::new(r\"sk-api-[A-Za-z0-9_-]+\").unwrap(),\n // DeepSeek / generic hex api keys\n Regex::new(r\"sk-[a-f0-9]{32,}\").unwrap(),\n // JWT / bearer tokens: three base64url chunks separated by dots\n Regex::new(r\"eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\").unwrap(),\n // JSON credential fields\n Regex::new(r#\"\"refresh_token\"\\s*:\\s*\"[^\"]+\"\"#).unwrap(),\n Regex::new(r#\"\"access_token\"\\s*:\\s*\"[^\"]+\"\"#).unwrap(),\n Regex::new(r#\"\"id_token\"\\s*:\\s*\"[^\"]+\"\"#).unwrap(),\n // Emails → masked\n Regex::new(r\"\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b\").unwrap(),\n ]\n })\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn redacts_all_known_secret_shapes() {\n let cases = [\n \"key sk-or-v1-abcdef0123456789 here\",\n \"claude sk-ant-sid1-oat01-xyz stuff\",\n \"minimax sk-cp-123abc and sk-api-99\",\n \"deepseek sk-1234567890abcdef1234567890abcdef\",\n \"bearer eyJhbGciOi.eyJzdWIiOi.c2lnbmVk\",\n r#\"{\"access_token\":\"eyJxx.yy.zz\",\"refresh_token\":\"rt\"}\"#,\n \"contact trevor@example.com please\",\n ];\n for c in cases {\n let r = Redactor::redact_str(c);\n assert!(\n !r.contains(\"sk-or-\")\n && !r.contains(\"sk-ant-\")\n && !r.contains(\"sk-cp-\")\n && !r.contains(\"sk-api-\")\n && !r.contains(\"sk-1234\")\n && !r.contains(\"eyJxx\")\n && !r.contains(\"access_token\\\":\\\"eyJ\")\n && !r.contains(\"refresh_token\\\":\\\"rt\")\n && !r.contains(\"trevor@\"),\n \"leak in: {c} -> {r}\"\n );\n }\n }\n\n #[test]\n fn masked_email_survives_redaction() {\n // A masked email `t***@x.com` must NOT match the email pattern (the `***`\n // breaks it), so defense-in-depth redaction preserves it while a raw email\n // is scrubbed.\n let masked = crate::view::mask_email(\"trevor@example.com\");\n assert_eq!(masked, \"t***@example.com\");\n let redacted_masked = Redactor::redact_str(&masked);\n assert_eq!(redacted_masked, masked, \"masked email should survive\");\n let redacted_raw = Redactor::redact_str(\"trevor@example.com\");\n assert!(\n !redacted_raw.contains(\"trevor@\"),\n \"raw email must be redacted: {redacted_raw}\"\n );\n }\n}\n"}, "b35a16a333eaa16260f985ecdd3137de32b7ce34": {"t": 1785761119.0600882, "n": 3338, "h": "65745c1b6e40fdf5", "c": "//! Secret redaction. Every output and error path routes potentially-sensitive\n//! strings through `Redactor`. See `SPEC.md §10`.\n\nuse std::sync::OnceLock;\n\nuse regex::Regex;\n\npub struct Redactor;\n\nimpl Redactor {\n /// Redact any credential-looking substring in `s`. Safe to call on any string.\n pub fn redact_str(s: &str) -> String {\n let mut out = s.to_string();\n for re in patterns() {\n out = re.replace_all(&out, \"[REDACTED]\").into_owned();\n }\n out\n }\n}\n\nfn patterns() -> &'static [Regex] {\n static P: OnceLock<Vec<Regex>> = OnceLock::new();\n P.get_or_init(|| {\n vec![\n Regex::new(r\"sk-or-v1-[A-Za-z0-9_-]+\").unwrap(),\n Regex::new(r\"sk-ant-[A-Za-z0-9_-]+\").unwrap(),\n Regex::new(r\"sk-cp-[A-Za-z0-9_-]+\").unwrap(),\n Regex::new(r\"sk-api-[A-Za-z0-9_-]+\").unwrap(),\n // DeepSeek / generic hex api keys\n Regex::new(r\"sk-[a-f0-9]{32,}\").unwrap(),\n // JWT / bearer tokens: three base64url chunks separated by dots\n Regex::new(r\"eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\").unwrap(),\n // JSON credential fields\n Regex::new(r#\"\"refresh_token\"\\s*:\\s*\"[^\"]+\"\"#).unwrap(),\n Regex::new(r#\"\"access_token\"\\s*:\\s*\"[^\"]+\"\"#).unwrap(),\n Regex::new(r#\"\"id_token\"\\s*:\\s*\"[^\"]+\"\"#).unwrap(),\n // Emails → masked\n Regex::new(r\"\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b\").unwrap(),\n ]\n })\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn redacts_all_known_secret_shapes() {\n let cases = [\n \"key sk-or-v1-abcdef0123456789 here\",\n \"claude sk-ant-sid1-oat01-xyz stuff\",\n \"minimax sk-cp-123abc and sk-api-99\",\n \"deepseek sk-1234567890abcdef1234567890abcdef\",\n \"bearer eyJhbGciOi.eyJzdWIiOi.c2lnbmVk\",\n r#\"{\"access_token\":\"eyJxx.yy.zz\",\"refresh_token\":\"rt\"}\"#,\n \"contact trevor@example.com please\",\n ];\n for c in cases {\n let r = Redactor::redact_str(c);\n assert!(\n !r.contains(\"sk-or-\")\n && !r.contains(\"sk-ant-\")\n && !r.contains(\"sk-cp-\")\n && !r.contains(\"sk-api-\")\n && !r.contains(\"sk-1234\")\n && !r.contains(\"eyJxx\")\n && !r.contains(\"access_token\\\":\\\"eyJ\")\n && !r.contains(\"refresh_token\\\":\\\"rt\")\n && !r.contains(\"trevor@\"),\n \"leak in: {c} -> {r}\"\n );\n }\n }\n\n #[test]\n fn masked_email_survives_redaction() {\n // A masked email `t***@x.com` must NOT match the email pattern (the `***`\n // breaks it), so defense-in-depth redaction preserves it while a raw email\n // is scrubbed.\n let masked = crate::view::mask_email(\"trevor@example.com\");\n assert_eq!(masked, \"t***@example.com\");\n let redacted_masked = Redactor::redact_str(&masked);\n assert_eq!(redacted_masked, masked, \"masked email should survive\");\n let redacted_raw = Redactor::redact_str(\"trevor@example.com\");\n assert!(\n !redacted_raw.contains(\"trevor@\"),\n \"raw email must be redacted: {redacted_raw}\"\n );\n }\n}\n"}}}}