Skip to content

fix(embeddings): classify 403 'not an embeddings model' as model-incompatible + redact API key from errors (#5116) - #5117

Merged
senamakel merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-5116-embed-classify-403-redact
Jul 22, 2026
Merged

fix(embeddings): classify 403 'not an embeddings model' as model-incompatible + redact API key from errors (#5116)#5117
senamakel merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-5116-embed-classify-403-redact

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Classify OpenAI's HTTP 403 "You are not allowed to generate embeddings from this model" as model-incompatible, not an auth failure — a chat model (e.g. gpt-4o-mini) used as an embeddings model no longer misreports "enter a valid key".
  • Detect model-incompatibility from the error message (case-insensitive, status-independent) before the 401/403 auth branch; a genuine 401/403 auth still classifies as auth.
  • Redact sk-…/bearer-token material from any probe detail surfaced to the UI and logs — a partial API key can no longer leak (no sk- substring survives).
  • Fix the doubled "API API key" label → "API key" via a dedicated apiKeyLabelGeneric i18n key across en + all 13 locales.

Problem

Follow-up to #5017 / #5064. Manual QA of the merged embedding-endpoint verification surfaced three bugs:

  1. [blocker] classify_embed_probe (src/openhuman/embeddings/rpc.rs) checked only HTTP status (401 || 403 → AUTH). OpenAI returns HTTP 403 "You are not allowed to generate embeddings from this model" when a chat model is used as an embeddings model — the key is valid, the model is wrong — but it fell through to the auth branch and told the user to "enter a valid key". This is exactly the scenario Custom OpenAI-compatible embedding endpoint fails verification even with valid endpoint and key #5017 targeted.
  2. [security] The 401 case surfaced the entire raw backend JSON in the UI, including the key prefix (Incorrect API key provided: sk-proj-…). The frontend appends result.detail to the message, so the leak reached the dialog.
  3. [typo] The setup dialog field read "API API key (optional)" (the custom-endpoint branch substituted 'API' into the {provider} API key template).

Solution

  • is_embedding_model_incompatible now matches strong, status-independent model-rejection phrasings ("not allowed to generate embeddings", "does not support embeddings", "not an embedding model", "unsupported … embedding") that never appear in a genuine auth rejection, so they classify as EMBEDDINGS_MODEL_INCOMPATIBLE ahead of the auth branch. Weak phrasings ("does not exist", odd model-name format) stay gated behind a 400/422 bad-request so a genuine 5xx/oversized-input 400 still falls through (preserving Custom OpenAI-compatible embedding endpoint fails verification even with valid endpoint and key #5017 behaviour).
  • New redact_secrets helper strips whole sk-[A-Za-z0-9_-]+ keys (incl. modern sk-proj-…) and Bearer <token> headers, replacing each with a marker that contains no sk- substring. Applied to the probe detail before it enters the RpcOutcome body, so both the UI message and logs are sanitized; the clean classified message remains the primary user-facing text.
  • The custom-endpoint API-key label now uses a dedicated generic i18n key ("API key") instead of substituting 'API' into the provider template.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/ci-lite.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not merge.
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change (or N/A: behaviour-only change) — N/A: behaviour-only change (hardens existing embeddings-verification classification; no new feature row).
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md) — N/A: no release-cut surface changed (error-message classification + redaction + a label string only).
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Platform: desktop (settings → embeddings custom-endpoint setup). No mobile/web/CLI impact.
  • Security: removes an API-key leak (partial sk-…) from a user-facing error and from logs.
  • Behaviour: a chat model used as an embeddings model now shows an actionable model-incompatible message instead of a misleading auth prompt. Genuine bad-key (401) behaviour is unchanged except the key is redacted. No migration/compatibility concerns; no new dependencies (regex/once_cell already vendored).

Related


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

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

Validation Run

  • pnpm --filter openhuman-app format:check — Prettier clean on touched files
  • pnpm typecheck — pass
  • Focused tests: GGML_NATIVE=OFF cargo test -p openhuman --lib embeddings::rpc — 22 passed (incl. 3 new)
  • Rust fmt/check (if changed): cargo fmt --check on rpc.rs — clean
  • Tauri fmt/check (if changed): N/A — no Tauri/Rust-shell code changed
  • Also ran: pnpm i18n:check and pnpm i18n:english:check — both pass (0 missing / 0 extra / 0 unexpected English)

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: 403 "not an embeddings model" → model-incompatible (not auth); API key redacted from surfaced errors/logs; "API API key" label fixed.
  • User-visible effect: correct, actionable error for a chat-model-as-embeddings mistake; no API key ever shown in the dialog; corrected label.

Parity Contract

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • Bug Fixes

    • Improved embeddings setup errors by correctly identifying model compatibility failures.
    • Prevented API keys and bearer tokens from appearing in displayed error details or logs.
    • Preserved accurate authentication error reporting for invalid credentials.
  • Localization

    • Added generic “API key” labels for embeddings settings across supported languages.
    • Updated custom endpoint setup to display a provider-neutral API key label.

…mpatible + redact API key from errors (tinyhumansai#5116)

- classify_embed_probe: detect model-incompatibility from strong error-message
  phrasings (e.g. OpenAI 403 'not allowed to generate embeddings from this model')
  BEFORE the 401/403 auth branch, so a chat model used as an embeddings model no
  longer misreports as an auth failure. Genuine 401/403 auth still classifies as auth.
- Redact sk-*/bearer-token material from the probe detail surfaced to the UI and
  logs so a partial API key can never leak (no 'sk-' substring survives).
- Fix doubled 'API API key' label: use a dedicated apiKeyLabelGeneric i18n key
  across en + all 13 locales.
- Unit tests: 403 model-incompat -> MODEL_INCOMPATIBLE; 401 bad key -> AUTH with
  key redacted; redaction helper coverage.
@M3gA-Mind
M3gA-Mind requested a review from a team July 22, 2026 09:27
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Embeddings verification now identifies model-incompatible 403 responses, redacts API keys and bearer tokens from surfaced details, and preserves authentication classification for genuine 401 failures. The settings UI also replaces the duplicated API label with localized generic API key text.

Changes

Embeddings verification and settings UI

Layer / File(s) Summary
Verification classification and secret redaction
src/openhuman/embeddings/rpc.rs
Model-rejection messages are classified before authentication errors, and surfaced probe details redact OpenAI-style keys and bearer tokens.
Verification regression coverage
src/openhuman/embeddings/rpc.rs
Tests cover 403 model incompatibility, redacted 401 authentication failures, and direct secret removal.
Generic API key labels
app/src/components/settings/panels/EmbeddingsPanel.tsx, app/src/lib/i18n/*.ts
The custom endpoint field uses a generic translation key with entries across supported locale files.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EmbeddingsVerifier
  participant classify_embed_probe
  participant redact_secrets
  participant SettingsUI
  EmbeddingsVerifier->>classify_embed_probe: classify probe status and error message
  classify_embed_probe-->>EmbeddingsVerifier: model incompatibility or auth failure
  EmbeddingsVerifier->>redact_secrets: sanitize failure detail
  redact_secrets-->>SettingsUI: redacted verification result
Loading

Suggested labels: bug

Suggested reviewers: senamakel, sanil-23

Poem

A rabbit checks the model’s name,
And hides the keys from public view.
“No doubled API words!” it claims,
With local tongues made fresh and new.
Hop, hop—safe embeddings bloom!

🚥 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 accurately summarizes the main embeddings fix: 403 model-incompatible classification and API key redaction.
Linked Issues check ✅ Passed The PR addresses #5116 by reclassifying model-incompatibility before auth, redacting secrets, fixing the label, and adding tests.
Out of Scope Changes check ✅ Passed The locale string updates and panel label change are directly tied to the requested API key label fix and are in scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 130d01ec90

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

|| lower.contains("not an embedding model")
|| lower.contains("is not an embedding")
|| lower.contains("not supported for embeddings")
|| (lower.contains("unsupported") && lower.contains("embedding"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Constrain unsupported matcher to model rejections

For custom providers the error detail is wrapped with text like openai embeddings returned ... / Embedding API error ..., so this unsupported && embedding check effectively matches any provider failure containing the word unsupported, such as 415 Unsupported Media Type or unsupported parameter, even when the model is not the problem. In those cases the save dialog now reports EMBEDDINGS_MODEL_INCOMPATIBLE and tells the user to change the model instead of fixing the endpoint/request; please anchor this to a model-specific phrase (for example unsupported model ... embedding) or keep it status/body scoped.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 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/embeddings/rpc.rs`:
- Around line 1291-1293: Replace the realistic-looking API key literal in
classify_embed_probe_401_bad_key_is_auth_and_redacts_key with an obviously
synthetic fixture such as sk-test-fixture. Preserve the surrounding 401 error
structure and redaction assertion so the test continues exercising the same
behavior without triggering secret scanners.
- Around line 817-830: Update the strong_model_rejection logic in the embedding
error classifier so generic “unsupported” messages only match model-specific
wording, not unsupported embedding dimensions or request options. Preserve the
existing explicit model-rejection phrases, and add a negative test covering an
unsupported embeddings parameter that must not be classified as a bad model.
🪄 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: 40f9878a-4032-49e2-939c-d4a1e026c82f

📥 Commits

Reviewing files that changed from the base of the PR and between 0eeee12 and 130d01e.

📒 Files selected for processing (16)
  • app/src/components/settings/panels/EmbeddingsPanel.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • src/openhuman/embeddings/rpc.rs

Comment on lines +817 to +830
let strong_model_rejection = lower.contains("not allowed to generate embeddings")
|| lower.contains("does not support embeddings")
|| lower.contains("not an embedding model")
|| lower.contains("is not an embedding")
|| lower.contains("not supported for embeddings")
|| (lower.contains("unsupported") && lower.contains("embedding"));
if strong_model_rejection {
return true;
}
let bad_request =
embed_error_mentions_status(lower, 400) || embed_error_mentions_status(lower, 422);
bad_request
&& (lower.contains("does not support embeddings")
|| lower.contains("not an embedding model")
|| lower.contains("is not an embedding")
|| lower.contains("does not exist")
|| lower.contains("not supported for embeddings")
|| lower.contains("unexpected model name format"))
&& (lower.contains("does not exist") || lower.contains("unexpected model name format"))
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Narrow the broad unsupported classifier.

unsupported embedding dimensions or another unsupported embeddings request option matches line 822 and is incorrectly surfaced as a bad model. Match model-specific wording only.

Proposed fix
-        || (lower.contains("unsupported") && lower.contains("embedding"));
+        || lower.contains("unsupported model for embedding")
+        || lower.contains("unsupported embedding model");

Add a negative test for an unsupported embeddings parameter.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let strong_model_rejection = lower.contains("not allowed to generate embeddings")
|| lower.contains("does not support embeddings")
|| lower.contains("not an embedding model")
|| lower.contains("is not an embedding")
|| lower.contains("not supported for embeddings")
|| (lower.contains("unsupported") && lower.contains("embedding"));
if strong_model_rejection {
return true;
}
let bad_request =
embed_error_mentions_status(lower, 400) || embed_error_mentions_status(lower, 422);
bad_request
&& (lower.contains("does not support embeddings")
|| lower.contains("not an embedding model")
|| lower.contains("is not an embedding")
|| lower.contains("does not exist")
|| lower.contains("not supported for embeddings")
|| lower.contains("unexpected model name format"))
&& (lower.contains("does not exist") || lower.contains("unexpected model name format"))
}
let strong_model_rejection = lower.contains("not allowed to generate embeddings")
|| lower.contains("does not support embeddings")
|| lower.contains("not an embedding model")
|| lower.contains("is not an embedding")
|| lower.contains("not supported for embeddings")
|| lower.contains("unsupported model for embedding")
|| lower.contains("unsupported embedding model");
if strong_model_rejection {
return true;
}
let bad_request =
embed_error_mentions_status(lower, 400) || embed_error_mentions_status(lower, 422);
bad_request
&& (lower.contains("does not exist") || lower.contains("unexpected model name format"))
🤖 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/embeddings/rpc.rs` around lines 817 - 830, Update the
strong_model_rejection logic in the embedding error classifier so generic
“unsupported” messages only match model-specific wording, not unsupported
embedding dimensions or request options. Preserve the existing explicit
model-rejection phrases, and add a negative test covering an unsupported
embeddings parameter that must not be classified as a bad model.

Comment on lines +1291 to +1293
fn classify_embed_probe_401_bad_key_is_auth_and_redacts_key() {
let detail = r#"openai embeddings returned HTTP 401 Unauthorized: {"error":{"message":"Incorrect API key provided: sk-proj-ABC123def456GHI789jkl012MNO. You can find your API key at https://platform.openai.com/account/api-keys.","type":"invalid_request_error","param":null,"code":"invalid_api_key"}}"#;
let rpc = classify_embed_probe(EmbedProbe::Failed(detail.into()))

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.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use an obviously synthetic key fixture.

Betterleaks flags the realistic-looking literal as a generic API key. A short fixture such as sk-test-fixture still exercises redaction without creating secret-scanner noise.

🧰 Tools
🪛 Betterleaks (1.6.1)

[high] 1292-1292: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 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/embeddings/rpc.rs` around lines 1291 - 1293, Replace the
realistic-looking API key literal in
classify_embed_probe_401_bad_key_is_auth_and_redacts_key with an obviously
synthetic fixture such as sk-test-fixture. Preserve the surrounding 401 error
structure and redaction assertion so the test continues exercising the same
behavior without triggering secret scanners.

Source: Linters/SAST tools

@senamakel
senamakel merged commit 353e07c into tinyhumansai:main Jul 22, 2026
24 of 28 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Embedding verification: 403 'not an embeddings model' misclassified as auth failure + API key leaked into error

2 participants