Skip to content

🛡️ fix: Filter user_provided Sentinel in Tool Credential Loading - #12840

Merged
danny-avila merged 1 commit into
LibreChat-AI:devfrom
Falenos:fix/user-provided-sentinel-in-tool-auth
Apr 29, 2026
Merged

danny-avila merged 1 commit into
LibreChat-AI:devfrom
Falenos:fix/user-provided-sentinel-in-tool-auth

Conversation

@Falenos

@Falenos Falenos commented Apr 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #12839

When GOOGLE_KEY=user_provided is set as an endpoint configuration (a documented sentinel value meaning "let users provide their own key via UI"), loadAuthValues() in credentials.js passes the literal string "user_provided" to tools through the || fallback chain.

This causes Gemini Image Tools to fail at runtime with an invalid API key error, because initializeGeminiClient() receives the sentinel value instead of a real key and attempts new GoogleGenAI({ apiKey: 'user_provided' }).

The root cause is an inconsistency: checkPluginAuth() in format.ts already correctly excludes user_provided and empty/whitespace values, but loadAuthValues() does not apply the same filtering. This fix aligns the two functions.

What changed

api/server/services/Tools/credentials.js — The findAuthValue() function now:

  • Separates env lookup (const envValue) from DB lookup (let value) to prevent the sentinel from leaking through the catch path when getUserPluginAuthValue throws
  • Skips env values that are empty, whitespace-only, or equal to the user_provided sentinel, consistent with checkPluginAuth() in format.ts
  • When a sentinel is encountered, continues to try user DB values or the next field in the || chain

api/server/services/Tools/credentials.spec.js — New test file with 9 regression tests covering: real env values, sentinel skipping, fallback chains, empty/whitespace filtering, optional fields, env short-circuit (no DB call), and the catch-path sentinel leak.

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

Steps to reproduce the bug:

  1. Set GOOGLE_KEY=user_provided in .env
  2. Create an agent with the Gemini Image Tools tool
  3. Ask the agent to generate an image
  4. Observe: runtime error — "user_provided" passed as literal API key

Steps to verify the fix:

  1. Apply the fix
  2. Same setup as above
  3. loadAuthValues() now skips the sentinel, falls through to user DB value or next field in chain
  4. If no valid key exists, the tool correctly reports missing auth instead of crashing with an opaque error

Unit tests:

PASS  server/services/Tools/credentials.spec.js
  loadAuthValues
    ✓ should return env value when set to a real key
    ✓ should skip user_provided sentinel and try user DB value
    ✓ should skip user_provided and continue to next field in fallback chain
    ✓ should skip empty and whitespace-only env values
    ✓ should not return user_provided as an auth value
    ✓ should return env value without calling DB when env is valid
    ✓ should return real env value from first matching field in fallback chain
    ✓ should return undefined for optional field when sentinel is filtered and DB throws
    ✓ should not leak sentinel through catch path when DB lookup throws

Existing test suites (148 suites, 3852 tests) continue to pass.

Test Configuration

  • Node.js v24
  • npm 11.10.0
  • Jest 30
  • Commit: 738003b (main at time of fix)

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Copilot AI review requested due to automatic review settings April 27, 2026 08:36

Copilot AI 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.

Pull request overview

Fixes a credential-loading edge case where the user_provided sentinel (and empty/whitespace env values) could be incorrectly treated as a real tool API key, aligning loadAuthValues() behavior with existing auth validation logic.

Changes:

  • Update findAuthValue() to skip empty/whitespace env values and the AuthType.USER_PROVIDED sentinel.
  • Add Jest regression tests covering sentinel/whitespace behavior and fallback-chain selection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
api/server/services/Tools/credentials.js Filters out invalid env values (whitespace / user_provided) during auth resolution.
api/server/services/Tools/credentials.spec.js Adds regression tests for env/user DB fallback behavior around the sentinel.
Comments suppressed due to low confidence (1)

api/server/services/Tools/credentials.js:39

  • findAuthValue() can still return a rejected env value (e.g., AuthType.USER_PROVIDED or whitespace) if getUserPluginAuthValue() throws while value still contains the env value. In that case the catch block doesn’t clear value, the !value check is false, and the subsequent if (value) returns the sentinel/whitespace. Consider resetting value before the try, or using a separate envValue variable and applying the same filtering to the post-DB value (and ensuring the catch/last-field logic uses the DB result, not the env value).
  const findAuthValue = async (fields) => {
    for (const field of fields) {
      let value = process.env[field];
      if (value && value.trim() !== '' && value !== AuthType.USER_PROVIDED) {
        return { authField: field, authValue: value };
      }
      try {
        value = await getUserPluginAuthValue(userId, field, throwError);
      } catch (err) {
        if (optional && optional.has(field)) {
          return { authField: field, authValue: undefined };
        }
        if (field === fields[fields.length - 1] && !value) {
          throw err;
        }
      }
      if (value) {
        return { authField: field, authValue: value };
      }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/server/services/Tools/credentials.spec.js
Comment thread api/server/services/Tools/credentials.spec.js Outdated
@Falenos
Falenos force-pushed the fix/user-provided-sentinel-in-tool-auth branch from bf60fef to 63079be Compare April 27, 2026 09:51
@Falenos
Falenos requested a review from Copilot April 27, 2026 09:54

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/server/services/Tools/credentials.spec.js
Comment thread api/server/services/Tools/credentials.spec.js Outdated
Comment thread api/server/services/Tools/credentials.spec.js
When GOOGLE_KEY=user_provided is set as an endpoint config, the
loadAuthValues() function in credentials.js would pass the literal
string 'user_provided' to tools via the || fallback chain. This caused
Gemini Image Tools to fail at runtime with an invalid API key error,
as initializeGeminiClient() received the sentinel value instead of a
real key.

The fix aligns loadAuthValues() with checkPluginAuth() in format.ts,
which already correctly excludes user_provided and empty/whitespace
values. Now loadAuthValues() skips these values and continues to the
next field in the fallback chain or falls through to user DB values.

Added regression tests covering:
- user_provided sentinel is skipped, DB value used instead
- Fallback chain continues past user_provided to next field
- Empty and whitespace env values are skipped
- Real env values are returned correctly
- Optional fields with sentinel values handled gracefully
@Falenos
Falenos force-pushed the fix/user-provided-sentinel-in-tool-auth branch from 63079be to b4f0670 Compare April 27, 2026 10:00
@Falenos
Falenos requested a review from Copilot April 27, 2026 10:03

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

api/server/services/Tools/credentials.js:40

  • findAuthValue() now filters empty/whitespace and AuthType.USER_PROVIDED for env vars, but DB values returned by getUserPluginAuthValue() are still accepted with a simple truthy check (if (value)). This means a whitespace-only DB value (e.g. ' ') will be treated as a valid credential and returned, which can still cause downstream “invalid API key” failures and contradicts the intent of “first non-empty value”. Consider applying the same trim() !== '' (and optionally !== AuthType.USER_PROVIDED) filtering to DB values before returning, so whitespace/sentinel stored values don’t short-circuit the fallback chain.
      const envValue = process.env[field];
      if (envValue && envValue.trim() !== '' && envValue !== AuthType.USER_PROVIDED) {
        return { authField: field, authValue: envValue };
      }
      let value;
      try {
        value = await getUserPluginAuthValue(userId, field, throwError);
      } catch (err) {
        if (optional && optional.has(field)) {
          return { authField: field, authValue: undefined };
        }
        if (field === fields[fields.length - 1]) {
          throw err;
        }
      }
      if (value) {
        return { authField: field, authValue: value };
      }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@danny-avila
danny-avila changed the base branch from main to dev April 28, 2026 21:07
@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila danny-avila changed the title fix: filter user_provided sentinel in tool credential loading 🛡️ fix: Filter user_provided Sentinel in Tool Credential Loading Apr 29, 2026
@danny-avila
danny-avila merged commit f2df0ea into LibreChat-AI:dev Apr 29, 2026
14 of 15 checks passed
@Falenos
Falenos deleted the fix/user-provided-sentinel-in-tool-auth branch April 29, 2026 10:34
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request May 3, 2026
…ibreChat-AI#12840)

When GOOGLE_KEY=user_provided is set as an endpoint config, the
loadAuthValues() function in credentials.js would pass the literal
string 'user_provided' to tools via the || fallback chain. This caused
Gemini Image Tools to fail at runtime with an invalid API key error,
as initializeGeminiClient() received the sentinel value instead of a
real key.

The fix aligns loadAuthValues() with checkPluginAuth() in format.ts,
which already correctly excludes user_provided and empty/whitespace
values. Now loadAuthValues() skips these values and continues to the
next field in the fallback chain or falls through to user DB values.

Added regression tests covering:
- user_provided sentinel is skipped, DB value used instead
- Fallback chain continues past user_provided to next field
- Empty and whitespace env values are skipped
- Real env values are returned correctly
- Optional fields with sentinel values handled gracefully
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…ibreChat-AI#12840)

When GOOGLE_KEY=user_provided is set as an endpoint config, the
loadAuthValues() function in credentials.js would pass the literal
string 'user_provided' to tools via the || fallback chain. This caused
Gemini Image Tools to fail at runtime with an invalid API key error,
as initializeGeminiClient() received the sentinel value instead of a
real key.

The fix aligns loadAuthValues() with checkPluginAuth() in format.ts,
which already correctly excludes user_provided and empty/whitespace
values. Now loadAuthValues() skips these values and continues to the
next field in the fallback chain or falls through to user DB values.

Added regression tests covering:
- user_provided sentinel is skipped, DB value used instead
- Fallback chain continues past user_provided to next field
- Empty and whitespace env values are skipped
- Real env values are returned correctly
- Optional fields with sentinel values handled gracefully
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
…ibreChat-AI#12840)

When GOOGLE_KEY=user_provided is set as an endpoint config, the
loadAuthValues() function in credentials.js would pass the literal
string 'user_provided' to tools via the || fallback chain. This caused
Gemini Image Tools to fail at runtime with an invalid API key error,
as initializeGeminiClient() received the sentinel value instead of a
real key.

The fix aligns loadAuthValues() with checkPluginAuth() in format.ts,
which already correctly excludes user_provided and empty/whitespace
values. Now loadAuthValues() skips these values and continues to the
next field in the fallback chain or falls through to user DB values.

Added regression tests covering:
- user_provided sentinel is skipped, DB value used instead
- Fallback chain continues past user_provided to next field
- Empty and whitespace env values are skipped
- Real env values are returned correctly
- Optional fields with sentinel values handled gracefully
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: loadAuthValues() passes user_provided sentinel as literal API key to tools via || fallback chain

3 participants