Skip to content

fix(inference): validate OpenRouter API keys#2372

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
sunilkumarvalmiki:fix/openrouter-key-validation-2366
May 20, 2026
Merged

fix(inference): validate OpenRouter API keys#2372
senamakel merged 1 commit into
tinyhumansai:mainfrom
sunilkumarvalmiki:fix/openrouter-key-validation-2366

Conversation

@sunilkumarvalmiki
Copy link
Copy Markdown
Contributor

@sunilkumarvalmiki sunilkumarvalmiki commented May 20, 2026

Summary

  • Fixes OpenRouter provider setup so an invalid API key no longer passes validation just because /models is public.
  • Adds an OpenRouter-specific GET /key credential check before the existing model catalog probe.
  • Keeps /models as the compatibility/catalog probe after credentials are accepted.
  • Adds regression coverage for invalid-key failure, valid-key success, key normalization, and OpenRouter slug/host detection.

Problem

OpenRouter's GET /api/v1/models endpoint returns 200 even when the request has no bearer token or an invalid bearer token. The AI settings flow uses openhuman.inference_list_models to verify a newly configured provider, and that controller previously treated the public catalog response as proof that the API key was usable.

Reproduction evidence from the current behavior:

curl.exe -sS -o NUL -w "%{http_code}" https://openrouter.ai/api/v1/models
# 200

curl.exe -sS -o NUL -w "%{http_code}" -H "Authorization: Bearer definitely-invalid-openrouter-key" https://openrouter.ai/api/v1/models
# 200

curl.exe -sS -o NUL -w "%{http_code}" -H "Authorization: Bearer definitely-invalid-openrouter-key" https://openrouter.ai/api/v1/key
# 401

Solution

  • Detect OpenRouter providers by the built-in slug or an openrouter.ai endpoint host.
  • Normalize the configured key once, then reuse the normalized value for both /key and /models requests.
  • Validate configured OpenRouter credentials with GET {endpoint}/key before calling GET {endpoint}/models.
  • Return sanitized, truncated validation errors and debug breadcrumbs without logging or exposing the key.
  • Leave non-OpenRouter OpenAI-compatible providers on the existing /models probe path.
  • Cover the fix with an Axum mock server so the invalid-key case proves /models is never called after /key rejects the credential.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy: added valid, invalid, and key-normalization OpenRouter credential-path tests.
  • Diff coverage >= 80%: full merged coverage was not generated locally; focused Rust tests cover the changed branches and CI remains the merge gate for the final percentage.
  • Coverage matrix updated: N/A, no feature row was added, removed, or renamed.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related: N/A, no matrix feature ID applies to this provider-validation fix.
  • No new external network dependencies introduced (mock backend used per Testing Strategy): tests use a local Axum mock server; runtime uses OpenRouter's existing API base.
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md): N/A, not a release-cut smoke surface.
  • Linked issue closed via Closes #NNN in the ## Related section.

Impact

Invalid OpenRouter API keys now fail during provider setup instead of enabling the provider. Valid OpenRouter keys still proceed to the existing model catalog probe. Other providers keep their existing model-list validation behavior.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A, GitHub issue only.
  • URL: N/A, GitHub issue only.

Commit & Branch

  • Branch: fix/openrouter-key-validation-2366
  • Commit SHA: 43957d83

Validation Run

  • pnpm --filter openhuman-app format:check: blocked locally; see Validation Blocked.
  • pnpm typecheck: passed.
  • Focused tests: cargo test --manifest-path Cargo.toml openrouter_ --lib -- --nocapture passed, 5 tests; pnpm --filter openhuman-app test -- src/components/settings/panels/__tests__/AIPanel.test.tsx src/services/api/__tests__/aiSettingsApi.test.ts passed, 2 files / 60 tests.
  • Rust fmt/check (if changed): cargo fmt --manifest-path Cargo.toml --all --check passed; cargo check --manifest-path Cargo.toml --lib passed with existing warnings.
  • Tauri fmt/check (if changed): N/A, no Tauri shell files changed.

Validation Blocked

  • command: pnpm format:check / pre-push hook running pnpm --filter openhuman-app format:check
  • error: on this Windows checkout, Prettier reports 902 untouched app files due CRLF working-tree line endings. The pre-push hook then tried pnpm format and failed in Rust formatting because app/src-tauri/vendor/tauri-cef/crates/tauri/Cargo.toml is missing locally.
  • impact: the hook-generated app rewrites were restored and are not in this commit. The committed Rust change passed cargo fmt --manifest-path Cargo.toml --all --check, git diff --check, focused Rust tests, TypeScript typecheck, and the related AI settings Vitest tests.

Behavior Changes

  • Intended behavior change: OpenRouter credentials are validated with /key before the provider is accepted.
  • User-visible effect: invalid OpenRouter keys stay rejected and the existing UI rollback path keeps the provider disabled.

Parity Contract

  • Legacy behavior preserved: the /models catalog probe still runs after OpenRouter credentials pass, and non-OpenRouter providers keep the prior /models probe behavior.
  • Guard/fallback/dispatch parity checks: provider lookup by id/slug is preserved; validation errors are sanitized/truncated; no API key is logged.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none found for #2366 via open PR search and issue closing-reference check.
  • Canonical PR: this PR.
  • Resolution (closed/superseded/updated): N/A.

Summary by CodeRabbit

  • Bug Fixes

    • Detects OpenRouter endpoints and validates API keys with a pre-check call; invalid keys now stop early with clearer, sanitized error messages.
    • Trims whitespace from configured API keys before use so credentials with extra spaces are accepted.
  • Tests

    • Added tests covering OpenRouter detection, key validation behavior, and that trimmed keys are used for validation and subsequent requests.

Review Change Stack

@sunilkumarvalmiki sunilkumarvalmiki requested a review from a team May 20, 2026 20:42
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 71d45e40-3b3b-4151-8eda-7ea2d8789ff3

📥 Commits

Reviewing files that changed from the base of the PR and between 8f394f7 and 43957d8.

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

📝 Walkthrough

Walkthrough

OpenRouter API key trimming and a pre-flight validation (GET {base}/key with Bearer token) were added; list_configured_models calls this validation for OpenRouter providers and aborts before requesting /models on validation failure. Tests and a local probe server exercise detection, trimming, and success/failure flows.

Changes

OpenRouter API Key Validation

Layer / File(s) Summary
OpenRouter detection and validation helpers
src/openhuman/inference/provider/ops.rs
is_openrouter_provider identifies OpenRouter by slug or endpoint host; validate_openrouter_api_key calls GET {base}/key with a bearer token and returns sanitized/truncated error messages on empty key, non-success responses, or JSON error payloads.
Integration into list_configured_models
src/openhuman/inference/provider/ops.rs
list_configured_models trims the resolved API key and conditionally invokes validate_openrouter_api_key when an OpenRouter provider is detected, preventing the /models probe on validation failure.
Test probe server and OpenRouter tests
src/openhuman/inference/provider/ops.rs
Adds an Axum probe server implementing /key and /models, workspace env helpers, request Authorization capture, and tests verifying detection, invalid-key early rejection, successful model listing with valid key, and that trimming affects both /key and /models Authorization headers.

Sequence Diagram

sequenceDiagram
  participant Client
  participant list_configured_models
  participant is_openrouter_provider
  participant validate_openrouter_api_key
  participant OpenRouter_API

  Client->>list_configured_models: request configured models
  list_configured_models->>is_openrouter_provider: check provider slug/host
  is_openrouter_provider-->>list_configured_models: true
  list_configured_models->>validate_openrouter_api_key: GET {base}/key (Bearer trimmed-token)
  validate_openrouter_api_key->>OpenRouter_API: GET /key (Authorization: Bearer ...)
  alt Key Valid
    OpenRouter_API-->>validate_openrouter_api_key: 200 OK (no error)
    validate_openrouter_api_key-->>list_configured_models: success
    list_configured_models->>OpenRouter_API: GET /models (Authorization: Bearer trimmed-token)
    OpenRouter_API-->>list_configured_models: models list
    list_configured_models-->>Client: models
  else Key Invalid
    OpenRouter_API-->>validate_openrouter_api_key: 4xx / error JSON
    validate_openrouter_api_key-->>list_configured_models: sanitized error
    list_configured_models-->>Client: early error (no /models request)
  end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A trim, a ping, a careful try,

We check the key before models fly,
No phantom provider left to stay,
The bunny bounces bugs away,
Hooray for probes and tests that pry!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(inference): validate OpenRouter API keys' clearly and concisely summarizes the main change: adding validation for OpenRouter API keys before marking the provider as usable.
Linked Issues check ✅ Passed The PR implementation addresses all coding-related requirements from issue #2366: validates OpenRouter API keys via GET /key endpoint before use, detects OpenRouter by slug or endpoint host, sanitizes error messages, adds comprehensive test coverage with Axum mock server, and maintains non-OpenRouter provider behavior.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing OpenRouter API key validation as specified in #2366; there are no unrelated modifications to other providers, logic, or functionality outside the stated objectives.

✏️ 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 20, 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

🧹 Nitpick comments (1)
src/openhuman/inference/provider/ops.rs (1)

223-231: ⚡ Quick win

Add debug logs on OpenRouter key-validation failure branches.

You already log validation start, but non-2xx and error-payload exits return without a debug/trace breadcrumb. Add sanitized status/message logs before returning.

Suggested refactor
     if !status.is_success() {
         let sanitized = sanitize_api_error(&text);
         let truncated = crate::openhuman::util::truncate_with_ellipsis(&sanitized, 300);
+        log::debug!(
+            "[providers][list_models] OpenRouter key validation failed status={} body={}",
+            status.as_u16(),
+            truncated
+        );
         return Err(format!(
             "OpenRouter key validation returned {}: {}",
             status.as_u16(),
             truncated
         ));
@@
             let sanitized = sanitize_api_error(&msg);
+            log::debug!(
+                "[providers][list_models] OpenRouter key validation returned error payload={}",
+                sanitized
+            );
             return Err(format!(
                 "OpenRouter key validation returned error payload: {}",
                 sanitized
             ));

As per coding guidelines: "Use log / tracing at debug or trace level on RPC entry and exit, error paths, state transitions, and any branch that is hard to infer from tests alone."

Also applies to: 246-250

🤖 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/ops.rs` around lines 223 - 231, The non-2xx
and error-payload exit paths lack debug breadcrumbs; before returning in the
branches that use status and text (the block that calls
sanitize_api_error(&text) and
crate::openhuman::util::truncate_with_ellipsis(&sanitized, 300)), add a debug
(or tracing::debug!) log that includes the sanitized-and-truncated message and
the HTTP status (e.g., "OpenRouter key validation failed" with status.as_u16()
and truncated), and do the same for the similar branch around lines 246-250 so
both error-return paths emit a sanitized debug log to aid tracing.
🤖 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/ops.rs`:
- Around line 202-217: In validate_openrouter_api_key, normalize the API key
once and reuse that normalized value for all requests (both the "/key" and
subsequent "/models" call) to prevent whitespace-related mismatches: replace the
current trim usage with a single owned/normalized variable (e.g.,
normalized_key) and use that variable in the Authorization header(s) instead of
the original api_key parameter or the prior trimmed temporary so both requests
use the exact same credential.

---

Nitpick comments:
In `@src/openhuman/inference/provider/ops.rs`:
- Around line 223-231: The non-2xx and error-payload exit paths lack debug
breadcrumbs; before returning in the branches that use status and text (the
block that calls sanitize_api_error(&text) and
crate::openhuman::util::truncate_with_ellipsis(&sanitized, 300)), add a debug
(or tracing::debug!) log that includes the sanitized-and-truncated message and
the HTTP status (e.g., "OpenRouter key validation failed" with status.as_u16()
and truncated), and do the same for the similar branch around lines 246-250 so
both error-return paths emit a sanitized debug log to aid tracing.
🪄 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: 7b881768-60da-4254-a40c-a45b9ce559b0

📥 Commits

Reviewing files that changed from the base of the PR and between fa8d75f and 8f394f7.

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

Comment thread src/openhuman/inference/provider/ops.rs
Signed-off-by: sunilkumarvalmiki <g.sunilkumarvalmiki@gmail.com>
@sunilkumarvalmiki sunilkumarvalmiki force-pushed the fix/openrouter-key-validation-2366 branch from 8f394f7 to 43957d8 Compare May 20, 2026 21:05
@sunilkumarvalmiki
Copy link
Copy Markdown
Contributor Author

Updated the branch to address the review feedback:

  • Normalizes the stored provider key once and reuses that normalized value for both OpenRouter /key validation and the following /models request.
  • Adds sanitized debug breadcrumbs for the OpenRouter validation failure branches.
  • Adds a regression test proving a key stored with surrounding whitespace is sent trimmed to both /key and /models.

Re-run locally after the update:

cargo fmt --manifest-path Cargo.toml --all --check
# passed

cargo test --manifest-path Cargo.toml openrouter_ --lib -- --nocapture
# 5 passed; 0 failed

cargo check --manifest-path Cargo.toml --lib
# passed with existing warnings

git diff --check
# passed

@sunilkumarvalmiki
Copy link
Copy Markdown
Contributor Author

CI status after the amended head (43957d83): all checks that completed are green except Rust Tauri Coverage (cargo-llvm-cov).

The failing job is outside this PR's touched surface and fails in Tauri shell core-process tests:

core_process::tests::ensure_running_falls_back_for_unknown_listener_on_port
core_process::tests::ensure_running_falls_back_to_7789_when_7788_is_busy
core_process::tests::ensure_running_reuses_unknown_listener_when_override_set

test result: FAILED. 347 passed; 3 failed

The separate test / Rust Tauri Shell Tests job passed on the same head, along with Rust quality, Rust core tests, TypeScript typecheck, frontend tests, Linux E2E, build, installer smoke, and Rust core coverage. I tried to rerun the failed coverage job, but GitHub requires repository admin rights for reruns from this account.

@senamakel senamakel merged commit 8016f96 into tinyhumansai:main May 20, 2026
29 of 32 checks passed
mtkik pushed a commit to mtkik/openhuman-meet that referenced this pull request May 21, 2026
Signed-off-by: sunilkumarvalmiki <g.sunilkumarvalmiki@gmail.com>
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.

OpenRouter accepts invalid API keys as enabled

2 participants