fix(providers): use Bearer auth for Gemini CLI OAuth tokens

* fix(providers): use Bearer auth for Gemini CLI OAuth tokens

When credentials come from ~/.gemini/oauth_creds.json (Gemini CLI),
send them as Authorization: Bearer header instead of ?key= query
parameter. API keys from env vars or config continue using ?key=.

Fixes #194

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(gemini): harden OAuth bearer auth flow and tests

* fix(gemini): granular auth source tracking and review fixes

Build on chumyin's auth model refactor with:
- Expand GeminiAuth to 4 variants (ExplicitKey/EnvGeminiKey/EnvGoogleKey/
  OAuthToken) so auth_source() uses stored discriminant without re-reading
  env vars at call time
- Add is_api_key()/credential() helpers on the enum
- Upgrade expired OAuth token log from debug to warn
- Add tests: provider_rejects_empty_key, auth_source_explicit_key,
  auth_source_none_without_credentials

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: apply rustfmt to fix CI lint failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: root <root@instance-20220913-1738.vcn09131738.oraclevcn.com>
Co-authored-by: argenis de la rosa <theonlyhennygod@gmail.com>
This commit is contained in:
Edvard Schøyen 2026-02-15 14:32:33 -05:00 committed by GitHub
parent e057bf4128
commit 49bb20f961
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 358 additions and 148 deletions

View file

@ -74,11 +74,10 @@ const BAD_PATTERNS: &[&str] = &[
/// Check if `haystack` contains `word` as a whole word (bounded by non-alphanumeric chars).
fn contains_word(haystack: &str, word: &str) -> bool {
for (i, _) in haystack.match_indices(word) {
let before_ok = i == 0
|| !haystack.as_bytes()[i - 1].is_ascii_alphanumeric();
let before_ok = i == 0 || !haystack.as_bytes()[i - 1].is_ascii_alphanumeric();
let after = i + word.len();
let after_ok = after >= haystack.len()
|| !haystack.as_bytes()[after].is_ascii_alphanumeric();
let after_ok =
after >= haystack.len() || !haystack.as_bytes()[after].is_ascii_alphanumeric();
if before_ok && after_ok {
return true;
}
@ -217,7 +216,11 @@ mod tests {
c.name = "malware-skill".into();
let res = eval.evaluate(c);
// 0.5 base + 0.3 license - 0.5 bad_pattern + 0.2 recency = 0.5
assert!(res.scores.security <= 0.5, "security: {}", res.scores.security);
assert!(
res.scores.security <= 0.5,
"security: {}",
res.scores.security
);
}
#[test]
@ -245,7 +248,11 @@ mod tests {
c.description = "Tools for hackathons and lifehacks".into();
let res = eval.evaluate(c);
// "hack" should NOT match "hackathon" or "lifehacks"
assert!(res.scores.security >= 0.5, "security: {}", res.scores.security);
assert!(
res.scores.security >= 0.5,
"security: {}",
res.scores.security
);
}
#[test]
@ -256,6 +263,10 @@ mod tests {
c.updated_at = None;
let res = eval.evaluate(c);
// 0.5 base + 0.0 license - 0.5 bad_pattern + 0.0 recency = 0.0
assert!(res.scores.security < 0.5, "security: {}", res.scores.security);
assert!(
res.scores.security < 0.5,
"security: {}",
res.scores.security
);
}
}