Skip to content

fix(provider): fallback to config.api_key when auth-profiles.json has no key#2724

Open
ccpty wants to merge 1 commit into
tinyhumansai:mainfrom
ccpty:fix/provider-api-key-fallback
Open

fix(provider): fallback to config.api_key when auth-profiles.json has no key#2724
ccpty wants to merge 1 commit into
tinyhumansai:mainfrom
ccpty:fix/provider-api-key-fallback

Conversation

@ccpty
Copy link
Copy Markdown

@ccpty ccpty commented May 27, 2026

When a custom provider's API key is set in config.toml but not saved through the UI into auth-profiles.json, the HTTP request would be sent without an Authorization header, causing authentication failures.

This change adds a fallback in lookup_key_for_slug() that reads from the top-level config.api_key field (which is auto-decrypted by the config loader from the enc2:-prefixed value in config.toml).

Before: api_key bytes=0 (key not found, requests fail)
After: api_key bytes=51 (key found via fallback, requests succeed)

Summary by CodeRabbit

  • New Features
    • Enhanced API key configuration resolution with additional fallback support, allowing authentication credentials to be sourced from multiple configuration locations.

Review Change Stack

… no key

When a custom provider's API key is set in config.toml but not saved
through the UI into auth-profiles.json, the HTTP request would be sent
without an Authorization header, causing authentication failures.

This change adds a fallback in lookup_key_for_slug() that reads from
the top-level config.api_key field (which is auto-decrypted by the
config loader from the enc2:-prefixed value in config.toml).

Fixes: API key bug where config.toml has api_key but HTTP requests
lack Authorization header for custom providers.
@ccpty ccpty requested a review from a team May 27, 2026 02:08
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

The PR adds a fallback authentication-key resolution mechanism in lookup_key_for_slug: when the auth-profiles.json lookup returns an empty key, the function now checks config.api_key from config.toml and returns it if non-empty, before falling back to an empty string.

Changes

Auth Key Resolution Fallback

Layer / File(s) Summary
Config fallback in key lookup
src/openhuman/inference/provider/factory.rs
The lookup_key_for_slug function now checks config.api_key when auth-profiles.json lookup yields an empty key, returning the trimmed value if non-empty.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Possibly related PRs

  • tinyhumansai/openhuman#2372: Both PRs adjust how provider API keys are resolved—main PR adds a config.api_key fallback in lookup_key_for_slug, while the retrieved PR trims and validates the resolved key via OpenRouter /key before model probing.
  • tinyhumansai/openhuman#2265: Both PRs modify lookup_key_for_slug in src/openhuman/inference/provider/factory.rs—the retrieved PR routes the openai slug to lookup_openai_bearer_token, and the main PR adds a config.api_key fallback when the prior auth-profiles lookup yields an empty key.

Suggested labels

working

Poem

🐰 A fallback path, so neat and fine,
When profiles fail, config will shine,
One trim, one check, the key is found,
Authentication logic, safe and sound! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a fallback mechanism to use config.api_key when auth-profiles.json lacks an authentication key for a provider.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/inference/provider/factory.rs`:
- Around line 641-651: The fallback returning config.api_key is currently
unconditional and can leak a global key to any provider; restrict this to only
the legacy direct-inference provider by checking that the provider endpoint for
the given slug matches config.inference_url before returning the key. In
practical terms, in the code around factory.rs where you read config.api_key and
have access to slug, resolve or compute the provider endpoint for slug (the same
logic used elsewhere in this function) and only use
config.api_key.trim().to_string() when endpoint == config.inference_url (and the
trimmed key is non-empty); otherwise skip this fallback and continue with normal
auth-profile lookup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a98ef126-9f54-4de8-a118-15e9685bb58a

📥 Commits

Reviewing files that changed from the base of the PR and between 0fddf11 and acb96fa.

📒 Files selected for processing (1)
  • src/openhuman/inference/provider/factory.rs

Comment on lines +641 to +651
// Fallback: read from top-level config.api_key (direct config.toml api_key).
// This handles the case where a key was set in config.toml but not saved
// through the UI into auth-profiles.json.
if let Some(config_key) = config.api_key.as_ref() {
if !config_key.trim().is_empty() {
log::debug!(
"[providers][chat-factory] auth lookup slug={} key_present=true (config.toml fallback)",
slug
);
return Ok(config_key.trim().to_string());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the config.api_key fallback to avoid cross-provider credential leakage.

This fallback applies to any slug when profile lookup is empty, so a global key can be sent to the wrong provider endpoint. Please gate it to the legacy direct-inference provider only (e.g., slug whose endpoint matches config.inference_url) instead of unconditional fallback.

Suggested scoped fallback
-    if let Some(config_key) = config.api_key.as_ref() {
-        if !config_key.trim().is_empty() {
-            log::debug!(
-                "[providers][chat-factory] auth lookup slug={} key_present=true (config.toml fallback)",
-                slug
-            );
-            return Ok(config_key.trim().to_string());
-        }
-    }
+    let is_legacy_inference_slug = config
+        .inference_url
+        .as_deref()
+        .map(str::trim)
+        .filter(|u| !u.is_empty())
+        .and_then(|inference_url| {
+            config
+                .cloud_providers
+                .iter()
+                .find(|e| e.slug == slug)
+                .map(|e| normalize_endpoint_for_compare(&e.endpoint)
+                    == normalize_endpoint_for_compare(inference_url))
+        })
+        .unwrap_or(false);
+
+    if is_legacy_inference_slug {
+        if let Some(config_key) = config.api_key.as_ref() {
+            if !config_key.trim().is_empty() {
+                log::debug!(
+                    "[providers][chat-factory] auth lookup slug={} key_present=true (config.toml fallback)",
+                    slug
+                );
+                return Ok(config_key.trim().to_string());
+            }
+        }
+    }

Based on learnings: src/openhuman/config/schema/types.rs documents top-level api_key as paired with inference_url for direct endpoint routing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/inference/provider/factory.rs` around lines 641 - 651, The
fallback returning config.api_key is currently unconditional and can leak a
global key to any provider; restrict this to only the legacy direct-inference
provider by checking that the provider endpoint for the given slug matches
config.inference_url before returning the key. In practical terms, in the code
around factory.rs where you read config.api_key and have access to slug, resolve
or compute the provider endpoint for slug (the same logic used elsewhere in this
function) and only use config.api_key.trim().to_string() when endpoint ==
config.inference_url (and the trimmed key is non-empty); otherwise skip this
fallback and continue with normal auth-profile lookup.

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

PR adds a fallback in lookup_key_for_slug() to read from config.api_key when a custom provider's API key isn't found in auth-profiles.json. Tests pass, CI is green.

Findings

CodeRabbit caught a major security issue — credential leakage. The fallback applies config.api_key to any provider slug when profile lookup fails. If you have multiple cloud_providers configured, they all get the same credential, even if config.api_key was only intended for the legacy inference_url provider.

Scoping required: Only apply the fallback when the provider's endpoint matches config.inference_url. CodeRabbit's suggested fix at line 651 is the right approach — gate the fallback to check if slug points to the provider entry whose endpoint matches the configured inference_url.

This is a blocker — the credential leakage needs to be closed before merge.

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a REQUEST_CHANGES is already in place for the credential leakage issue flagged by CodeRabbit. Before I can approve this, that concern needs to be addressed — the fallback should be scoped to only the legacy inference provider (where the endpoint matches config.inference_url) to avoid sending the global config.api_key to unintended providers.

Also, there's a failing CI check (test / Rust Core Tests (Windows — secrets ACL)). Once the credential leakage issue is fixed and CI passes, I'll do a full review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants