Commit graph

3922 commits

Author SHA1 Message Date
Atef Bellaaj
a0fed6173c
🗂️ refactor: Migrate S3 Storage to TypeScript in packages/api (#11947)
* Migrate S3 storage module with unit and integration tests

  - Migrate S3 CRUD and image operations to packages/api/src/storage/s3/
  - Add S3ImageService class with dependency injection
  - Add unit tests using aws-sdk-client-mock
  - Add integration tests with real s3 bucket (condition presence of  AWS_TEST_BUCKET_NAME)

* AI Review Findings Fixes

* chore: tests and refactor S3 storage types

- Added mock implementations for the 'sharp' library in various test files to improve image processing testing.
- Updated type references in S3 storage files from MongoFile to TFile for consistency and type safety.
- Refactored S3 CRUD operations to ensure proper handling of file types and improve code clarity.
- Enhanced integration tests to validate S3 file operations and error handling more effectively.

* chore: rename test file

* Remove duplicate import of refreshS3Url

* chore: imports order

* fix: remove duplicate imports for S3 URL handling in UserController

* fix: remove duplicate import of refreshS3FileUrls in files.js

* test: Add mock implementations for 'sharp' and '@librechat/api' in UserController tests

- Introduced mock functions for the 'sharp' library to facilitate image processing tests, including metadata retrieval and buffer conversion.
- Enhanced mocking for '@librechat/api' to ensure consistent behavior in tests, particularly for the needsRefresh and getNewS3URL functions.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-21 14:28:55 -04:00
Danny Avila
e4e468840e
🏢 feat: Multi-Tenant Data Isolation Infrastructure (#12091)
* chore: imports

* chore: optional chaining in `spendTokens.spec.ts`

* feat: Add tenantId field to all MongoDB schemas for multi-tenant isolation

  - Add AsyncLocalStorage-based tenant context (`tenantContext.ts`) for
    request-scoped tenantId propagation without modifying method signatures
  - Add Mongoose `applyTenantIsolation` plugin that injects `{ tenantId }`
    into all query filters when tenant context is present, with
    `TENANT_ISOLATION_STRICT` env var for fail-closed production mode
  - Add optional `tenantId` field to all 28 collection schemas
  - Update all compound unique indexes to include tenantId (email, OAuth IDs,
    role names, serverName, conversationId+user, messageId+user, etc.)
  - Apply tenant isolation plugin in all 28 model factories
  - Add `tenantId?: string` to all TypeScript document interfaces

  Behaviorally inert — transitional mode (default) passes through all queries
  unchanged. No migration required for existing deployments.

* refactor: Update tenant context and enhance tenant isolation plugin

- Changed `tenantId` in `TenantContext` to be optional, allowing for more flexible usage.
- Refactored `runAsSystem` function to accept synchronous functions, improving usability.
- Introduced comprehensive tests for the `applyTenantIsolation` plugin, ensuring correct tenant filtering in various query scenarios.
- Enhanced the plugin to handle aggregate queries and save operations with tenant context, improving data isolation capabilities.

* docs: tenant context documentation and improve tenant isolation tests

- Added detailed documentation for the `tenantStorage` AsyncLocalStorage instance in `tenantContext.ts`, clarifying its usage for async tenant context propagation.
- Updated tests in `tenantIsolation.spec.ts` to improve clarity and coverage, including new tests for strict mode behavior and tenant context propagation through await boundaries.
- Refactored existing test cases for better readability and consistency, ensuring robust validation of tenant isolation functionality.

* feat: Enhance tenant isolation by preventing tenantId mutations in update operations

- Added a new function to assert that tenantId cannot be modified through update operators in Mongoose queries.
- Implemented middleware to enforce this restriction during findOneAndUpdate, updateOne, and updateMany operations.
- Updated documentation to reflect the new behavior regarding tenantId modifications, ensuring clarity on tenant isolation rules.

* feat: Enhance tenant isolation tests and enforce tenantId restrictions

- Updated existing tests to clarify behavior regarding tenantId preservation during save and insertMany operations.
- Introduced new tests to validate that tenantId cannot be modified through update operations, ensuring strict adherence to tenant isolation rules.
- Added checks for mismatched tenantId scenarios, reinforcing the integrity of tenant context propagation.
- Enhanced test coverage for async context propagation and mutation guards, improving overall robustness of tenant isolation functionality.

* fix: Remove duplicate re-exports in utils/index.ts

Merge artifact caused `string` and `tempChatRetention` to be exported
twice, which produces TypeScript compile errors for duplicate bindings.

* fix: Resolve admin capability gap in multi-tenant mode (TODO #12091)

- hasCapabilityForPrincipals now queries both tenant-scoped AND
  platform-level grants when tenantId is set, so seeded ADMIN grants
  remain effective in tenant mode.
- Add applyTenantIsolation to SystemGrant model factory.

* fix: Harden tenant isolation plugin

- Add replaceGuard for replaceOne/findOneAndReplace to prevent
  cross-tenant document reassignment via replacement documents.
- Cache isStrict() result to avoid process.env reads on every query.
  Export _resetStrictCache() for test teardown.
- Replace console.warn with project logger (winston).
- Add 5 new tests for replace guard behavior (46 total).

* style: Fix import ordering in convo.ts and message.ts

Move type imports after value imports per project style guide.

* fix: Remove tenant isolation from SystemGrant, stamp tenantId in replaceGuard

- SystemGrant is a cross-tenant control plane whose methods handle
  tenantId conditions explicitly. Applying the isolation plugin
  injects a hard equality filter that overrides the $and/$or logic
  in hasCapabilityForPrincipals, making platform-level ADMIN grants
  invisible in tenant mode.
- replaceGuard now stamps tenantId into replacement documents when
  absent, preventing replaceOne from silently stripping tenant
  context. Replacements with a matching tenantId are allowed;
  mismatched tenantId still throws.

* test: Add multi-tenant unique constraint and replace stamping tests

- Verify same name/email can exist in different tenants (compound
  unique index allows it).
- Verify duplicate within same tenant is rejected (E11000).
- Verify tenant-scoped query returns only the correct document.
- Update replaceOne test to assert tenantId is stamped into
  replacement document.
- Add test for replacement with matching tenantId.

* style: Reorder imports in message.ts to align with project style guide

* feat: Add migration to drop superseded unique indexes for multi-tenancy

Existing deployments have single-field unique indexes (e.g. { email: 1 })
that block multi-tenant operation — same email in different tenants
triggers E11000. Mongoose autoIndex creates the new compound indexes
but never drops the old ones.

dropSupersededTenantIndexes() drops all 19 superseded indexes across 11
collections. It is idempotent, skips missing indexes/collections, and
is a no-op on fresh databases.

Must be called before enabling multi-tenant middleware on an existing
deployment. Single-tenant deployments are unaffected (old indexes
coexist harmlessly until migration runs).

Includes 11 tests covering:
- Full upgrade simulation (create old indexes, drop them, verify gone)
- Multi-tenant writes work after migration (same email, different tenant)
- Intra-tenant uniqueness preserved (duplicate within tenant rejected)
- Fresh database (no-op, no errors)
- Partial migration (some collections exist, some don't)
- SUPERSEDED_INDEXES coverage validation

* fix: Update systemGrant test — platform grants now satisfy tenant queries

The TODO #12091 fix intentionally changed hasCapabilityForPrincipals to
match both tenant-scoped AND platform-level grants. The test expected
the old behavior (platform grant invisible to tenant query). Updated
test name and expectation to match the new semantics.

* fix: Align getCapabilitiesForPrincipal with hasCapabilityForPrincipals tenant query

getCapabilitiesForPrincipal used a hard tenantId equality filter while
hasCapabilityForPrincipals uses $and/$or to match both tenant-scoped
and platform-level grants. This caused the two functions to disagree
on what grants a principal holds in tenant mode.

Apply the same $or pattern: when tenantId is provided, match both
{ tenantId } and { tenantId: { $exists: false } }.

Adds test verifying platform-level ADMIN grants appear in
getCapabilitiesForPrincipal when called with a tenantId.

* fix: Remove categories from tenant index migration

categoriesSchema is exported but never used to create a Mongoose model.
No Category model factory exists, no code constructs a model from it,
and no categories collection exists in production databases. Including
it in the migration would attempt to drop indexes from a non-existent
collection (harmlessly skipped) but implies the collection is managed.

* fix: Restrict runAsSystem to async callbacks only

Sync callbacks returning Mongoose thenables silently lose ALS context —
the system bypass does nothing and strict mode throws with no indication
runAsSystem was involved. Narrowing to () => Promise<T> makes the wrong
pattern a compile error. All existing call sites already use async.

* fix: Use next(err) consistently in insertMany pre-hook

The hook accepted a next callback but used throw for errors. Standardize
on next(err) for all error paths so the hook speaks one language —
callback-style throughout.

* fix: Replace optional chaining with explicit null assertions in spendTokens tests

Optional chaining on test assertions masks failures with unintelligible
error messages. Add expect(result).not.toBeNull() before accessing
properties, so a null result produces a clear diagnosis instead of
"received value must be a number".
2026-03-21 14:28:54 -04:00
Danny Avila
9e0592a236
📜 feat: Implement System Grants for Capability-Based Authorization (#11896)
* feat: Implement System Grants for Role-Based Capabilities

- Added a new `systemGrant` model and associated methods to manage role-based capabilities within the application.
- Introduced middleware functions `hasCapability` and `requireCapability` to check user permissions based on their roles.
- Updated the database seeding process to include system grants for the ADMIN role, ensuring all necessary capabilities are assigned on startup.
- Enhanced type definitions and schemas to support the new system grant functionality, improving overall type safety and clarity in the codebase.

* test: Add unit tests for capabilities middleware and system grant methods

- Introduced comprehensive unit tests for the capabilities middleware, including `hasCapability` and `requireCapability`, ensuring proper permission checks based on user roles.
- Added tests for the `SystemGrant` methods, verifying the seeding of system grants, capability granting, and revocation processes.
- Enhanced test coverage for edge cases, including idempotency of grant operations and handling of unexpected errors in middleware.
- Utilized mocks for database interactions to isolate tests and improve reliability.

* refactor: Transition to Capability-Based Access Control

- Replaced role-based access checks with capability-based checks across various middleware and routes, enhancing permission management.
- Introduced `hasCapability` and `requireCapability` functions to streamline capability verification for user actions.
- Updated relevant routes and middleware to utilize the new capability system, ensuring consistent permission enforcement.
- Enhanced type definitions and added tests for the new capability functions, improving overall code reliability and maintainability.

* test: Enhance capability-based access tests for ADMIN role

- Updated tests to reflect the new capability-based access control, specifically for the ADMIN role.
- Modified test descriptions to clarify that users with the MANAGE_AGENTS capability can bypass permission checks.
- Seeded capabilities for the ADMIN role in multiple test files to ensure consistent permission checks across different routes and middleware.
- Improved overall test coverage for capability verification, ensuring robust permission management.

* test: Update capability tests for MCP server access

- Renamed test to reflect the correct capability for bypassing permission checks, changing from MANAGE_AGENTS to MANAGE_MCP_SERVERS.
- Updated seeding of capabilities for the ADMIN role to align with the new capability structure.
- Ensured consistency in capability definitions across tests and middleware for improved permission management.

* feat: Add hasConfigCapability for enhanced config access control

- Introduced `hasConfigCapability` function to check user permissions for managing or reading specific config sections.
- Updated middleware to export the new capability function, ensuring consistent access control across the application.
- Enhanced unit tests to cover various scenarios for the new capability, improving overall test coverage and reliability.

* fix: Update tenantId filter in createSystemGrantMethods

- Added a condition to set tenantId filter to { $exists: false } when tenantId is null, ensuring proper handling of cases where tenantId is not provided.
- This change improves the robustness of the system grant methods by explicitly managing the absence of tenantId in the filter logic.

* fix: account deletion capability check

- Updated the `canDeleteAccount` middleware to ensure that the `hasManageUsers` capability check only occurs if a user is present, preventing potential errors when the user object is undefined.
- This change improves the robustness of the account deletion logic by ensuring proper handling of user permissions.

* refactor: Optimize seeding of system grants for ADMIN role

- Replaced sequential capability granting with parallel execution using Promise.all in the seedSystemGrants function.
- This change improves performance and efficiency during the initialization of system grants, ensuring all capabilities are granted concurrently.

* refactor: Simplify systemGrantSchema index definition

- Removed the sparse option from the unique index on principalType, principalId, capability, and tenantId in the systemGrantSchema.
- This change streamlines the index definition, potentially improving query performance and clarity in the schema design.

* refactor: Reorganize role capability check in roles route

- Moved the capability check for reading roles to occur after parsing the roleName, improving code clarity and structure.
- This change ensures that the authorization logic is consistently applied before fetching role details, enhancing overall permission management.

* refactor: Remove unused ISystemGrant interface from systemCapabilities.ts

- Deleted the ISystemGrant interface as it was no longer needed, streamlining the code and improving clarity.
- This change helps reduce clutter in the file and focuses on relevant capabilities for the system.

* refactor: Migrate SystemCapabilities to data-schemas

- Replaced imports of SystemCapabilities from 'librechat-data-provider' with imports from '@librechat/data-schemas' across multiple files.
- This change centralizes the management of system capabilities, improving code organization and maintainability.

* refactor: Update account deletion middleware and capability checks

- Modified the `canDeleteAccount` middleware to ensure that the account deletion permission is only granted to users with the `MANAGE_USERS` capability, improving security and clarity in permission management.
- Enhanced error logging for unauthorized account deletion attempts, providing better insights into permission issues.
- Updated the `capabilities.ts` file to ensure consistent handling of user authentication checks, improving robustness in capability verification.
- Refined type definitions in `systemGrant.ts` and `systemGrantMethods.ts` to utilize the `PrincipalType` enum, enhancing type safety and code clarity.

* refactor: Extract principal ID normalization into a separate function

- Introduced `normalizePrincipalId` function to streamline the normalization of principal IDs based on their type, enhancing code clarity and reusability.
- Updated references in `createSystemGrantMethods` to utilize the new normalization function, improving maintainability and reducing code duplication.

* test: Add unit tests for principalId normalization in systemGrant

- Introduced tests for the `grantCapability`, `revokeCapability`, and `getCapabilitiesForPrincipal` methods to verify correct handling of principalId normalization between string and ObjectId formats.
- Enhanced the `capabilities.ts` middleware to utilize the `PrincipalType` enum for improved type safety.
- Added a new utility function `normalizePrincipalId` to streamline principal ID normalization logic, ensuring consistent behavior across the application.

* feat: Introduce capability implications and enhance system grant methods

- Added `CapabilityImplications` to define relationships between broader and implied capabilities, allowing for more intuitive permission checks.
- Updated `createSystemGrantMethods` to expand capability queries to include implied capabilities, improving authorization logic.
- Enhanced `systemGrantSchema` to include an `expiresAt` field for future TTL enforcement of grants, and added validation to ensure `tenantId` is not set to null.
- Documented authorization requirements for prompt group and prompt deletion methods to clarify access control expectations.

* test: Add unit tests for canDeleteAccount middleware

- Introduced unit tests for the `canDeleteAccount` middleware to verify account deletion permissions based on user roles and capabilities.
- Covered scenarios for both allowed and blocked account deletions, including checks for ADMIN users with the `MANAGE_USERS` capability and handling of undefined user cases.
- Enhanced test structure to ensure clarity and maintainability of permission checks in the middleware.

* fix: Add principalType enum validation to SystemGrant schema

Without enum validation, any string value was accepted for principalType
and silently stored. Invalid documents would never match capability
queries, creating phantom grants impossible to diagnose without raw DB
inspection. All other ACL models in the codebase validate this field.

* fix: Replace seedSystemGrants Promise.all with bulkWrite for concurrency safety

When two server instances start simultaneously (K8s rolling deploy, PM2
cluster), both call seedSystemGrants. With Promise.all + findOneAndUpdate
upsert, both instances may attempt to insert the same documents, causing
E11000 duplicate key errors that crash server startup.

bulkWrite with ordered:false handles concurrent upserts gracefully and
reduces 17 individual round trips to a single network call. The returned
documents (previously discarded) are no longer fetched.

* perf: Add AsyncLocalStorage per-request cache for capability checks

Every hasCapability call previously required 2 DB round trips
(getUserPrincipals + SystemGrant.exists) — replacing what were O(1)
string comparisons. Routes like patchPromptGroup triggered this twice,
and hasConfigCapability's fallback path resolved principals twice.

This adds a per-request AsyncLocalStorage cache that:
- Caches resolved principals (same for all checks within one request)
- Caches capability check results (same user+cap = same answer)
- Automatically scoped to request lifetime (no stale grants)
- Falls through to DB when no store exists (background jobs, tests)
- Requires no signature changes to hasCapability

The capabilityContextMiddleware is registered at the app level before
all routes, initializing a fresh store per request.

* fix: Add error handling for inline hasCapability calls

canDeleteAccount, fetchAssistants, and validateAuthor all call
hasCapability without try-catch. These were previously O(1) string
comparisons that could never throw. Now they hit the database and can
fail on connection timeout or transient errors.

Wrap each call in try-catch, defaulting to deny (false) on error.
This ensures a DB hiccup returns a clean 403 instead of an unhandled
500 with a stack trace.

* test: Add canDeleteAccount DB-error resilience test

Tests that hasCapability rejection (e.g., DB timeout) results in a clean
403 rather than an unhandled exception. Validates the error handling
added in the previous commit.

* refactor: Use barrel import for hasCapability in validateAuthor

Import from ~/server/middleware barrel instead of directly from
~/server/middleware/roles/capabilities for consistency with other
non-middleware consumers. Files within the middleware barrel itself
must continue using direct imports to avoid circular requires.

* refactor: Remove misleading pre('save') hook from SystemGrant schema

The pre('save') hook normalized principalId for USER/GROUP principals,
but the primary write path (grantCapability) uses findOneAndUpdate —
which does not trigger save hooks. The normalization was already handled
explicitly in grantCapability itself. The hook created a false impression
of schema-level enforcement that only covered save()/create() paths.

Replace with a comment documenting that all writes must go through
grantCapability.

* feat: Add READ_ASSISTANTS capability to complete manage/read pair

Every other managed resource had a paired READ_X / MANAGE_X capability
except assistants. This adds READ_ASSISTANTS and registers the
MANAGE_ASSISTANTS → READ_ASSISTANTS implication in CapabilityImplications,
enabling future read-only assistant visibility grants.

* chore: Reorder systemGrant methods for clarity

Moved hasCapabilityForPrincipals to a more logical position in the returned object of createSystemGrantMethods, improving code readability. This change also maintains the inclusion of seedSystemGrants in the export, ensuring all necessary methods are available.

* fix: Wrap seedSystemGrants in try-catch to avoid blocking startup

Seeding capabilities is idempotent and will succeed on the next restart.
A transient DB error during seeding should not prevent the server from
starting — log the error and continue.

* refactor: Improve capability check efficiency and add audit logging

Move hasCapability calls after cheap early-exits in validateAuthor and
fetchAssistants so the DB check only runs when its result matters. Add
logger.debug on every capability bypass grant across all 7 call sites
for auditability, and log errors in catch blocks instead of silently
swallowing them.

* test: Add integration tests for AsyncLocalStorage capability caching

Exercises the full vertical — ALS context, generateCapabilityCheck,
real getUserPrincipals, real hasCapabilityForPrincipals, real MongoDB
via MongoMemoryServer. Covers per-request caching, cross-context
isolation, concurrent request isolation, negative caching, capability
implications, tenant scoping, group-based grants, and requireCapability
middleware.

* test: Add systemGrant data-layer and ALS edge-case integration tests

systemGrant.spec.ts (51 tests): Full integration tests for all
systemGrant methods against real MongoDB — grant/revoke lifecycle,
principalId normalization (string→ObjectId for USER/GROUP, string for
ROLE), capability implications (both directions), tenant scoping,
schema validation (null tenantId, invalid enum, required fields,
unique compound index).

capabilities.integration.spec.ts (27 tests): Adds ALS edge cases —
missing context degrades gracefully with no caching (background jobs,
child processes), nested middleware creates independent inner context,
optional-chaining safety when store is undefined, mid-request grant
changes are invisible due to result caching, requireCapability works
without ALS, and interleaved concurrent contexts maintain isolation.

* fix: Add worker thread guards to capability ALS usage

Detect when hasCapability or capabilityContextMiddleware is called from
a worker thread (where ALS context does not propagate from the parent).
hasCapability logs a warn-once per factory instance; the middleware logs
an error since mounting Express middleware in a worker is likely a
misconfiguration. Both continue to function correctly — the guard is
observability, not a hard block.

* fix: Include tenantId in ALS principal cache key for tenant isolation

The principal cache key was user.id:user.role, which would reuse
cached principals across tenants for the same user within a request.
When getUserPrincipals gains tenant-scoped group resolution, principals
from tenant-a would incorrectly serve tenant-b checks. Changed to
user.id:user.role:user.tenantId to prevent cross-tenant cache hits.

Adds integration test proving separate principal lookups per tenantId.

* test: Remove redundant mocked capabilities.spec.js

The JS wrapper test (7 tests, all mocked) is a strict subset of
capabilities.integration.spec.ts (28 tests, real MongoDB). Every
scenario it covered — hasCapability true/false, tenantId passthrough,
requireCapability 403/500, error handling — is tested with higher
fidelity in the integration suite.

* test: Replace mocked canDeleteAccount tests with real MongoDB integration

Remove hasCapability mock — tests now exercise the full capability
chain against real MongoDB (getUserPrincipals, hasCapabilityForPrincipals,
SystemGrant collection). Only mocks remaining are logger and cache.

Adds new coverage: admin role without grant is blocked, user-level
grant bypasses deletion restriction, null user handling.

* test: Add comprehensive tests for ACL entry management and user group methods

Introduces new tests for `deleteAclEntries`, `bulkWriteAclEntries`, and `findPublicResourceIds` in `aclEntry.spec.ts`, ensuring proper functionality for deleting and bulk managing ACL entries. Additionally, enhances `userGroup.spec.ts` with tests for finding groups by ID and name pattern, including external ID matching and source filtering. These changes improve coverage and validate the integrity of ACL and user group operations against real MongoDB interactions.

* refactor: Update capability checks and logging for better clarity and error handling

Replaced `MANAGE_USERS` with `ACCESS_ADMIN` in the `canDeleteAccount` middleware and related tests to align with updated permission structure. Enhanced logging in various middleware functions to use `logger.warn` for capability check failures, providing clearer error messages. Additionally, refactored capability checks in the `patchPromptGroup` and `validateAuthor` functions to improve readability and maintainability. This commit also includes adjustments to the `systemGrant` methods to implement retry logic for transient failures during capability seeding, ensuring robustness in the face of database errors.

* refactor: Enhance logging and retry logic in seedSystemGrants method

Updated the logging format in the seedSystemGrants method to include error messages for better clarity. Improved the retry mechanism by explicitly mocking multiple failures in tests, ensuring robust error handling during transient database issues. Additionally, refined imports in the systemGrant schema for better type management.

* refactor: Consolidate imports in canDeleteAccount middleware

Merged logger and SystemCapabilities imports from the data-schemas module into a single line for improved readability and maintainability of the code. This change streamlines the import statements in the canDeleteAccount middleware.

* test: Enhance systemGrant tests for error handling and capability validation

Added tests to the systemGrant methods to handle various error scenarios, including E11000 race conditions, invalid ObjectId strings for USER and GROUP principals, and invalid capability strings. These enhancements improve the robustness of the capability granting and revoking logic, ensuring proper error propagation and validation of inputs.

* fix: Wrap hasCapability calls in deny-by-default try-catch at remaining sites

canAccessResource, files.js, and roles.js all had hasCapability inside
outer try-catch blocks that returned 500 on DB failure instead of
falling through to the regular ACL check. This contradicts the
deny-by-default pattern used everywhere else.

Also removes raw error.message from the roles.js 500 response to
prevent internal host/connection info leaking to clients.

* fix: Normalize user ID in canDeleteAccount before passing to hasCapability

requireCapability normalizes req.user.id via _id?.toString() fallback,
but canDeleteAccount passed raw req.user directly. If req.user.id is
absent (some auth layers only populate _id), getUserPrincipals received
undefined, silently returning empty principals and blocking the bypass.

* fix: Harden systemGrant schema and type safety

- Reject empty string tenantId in schema validator (was only blocking
  null; empty string silently orphaned documents)
- Fix reverseImplications to use BaseSystemCapability[] instead of
  string[], preserving the narrow discriminated type
- Document READ_ASSISTANTS as reserved/unenforced

* test: Use fake timers for seedSystemGrants retry tests and add tenantId validation

- Switch retry tests to jest.useFakeTimers() to eliminate 3+ seconds
  of real setTimeout delays per test run
- Add regression test for empty-string tenantId rejection

* docs: Add TODO(#12091) comments for tenant-scoped capability gaps

In multi-tenant mode, platform-level grants (no tenantId) won't match
tenant-scoped queries, breaking admin access. getUserPrincipals also
returns cross-tenant group memberships. Both need fixes in #12091.
2026-03-21 14:28:54 -04:00
Danny Avila
0412f05daf
🪢 chore: Consolidate Pricing and Tx Imports After tx.js Module Removal (#12086)
* 🧹 chore: resolve imports due to rebase

* chore: Update model mocks in unit tests for consistency

- Consolidated model mock implementations across various test files to streamline setup and reduce redundancy.
- Removed duplicate mock definitions for `getMultiplier` and `getCacheMultiplier`, ensuring a unified approach in `recordCollectedUsage.spec.js`, `openai.spec.js`, `responses.unit.spec.js`, and `abortMiddleware.spec.js`.
- Enhanced clarity and maintainability of test files by aligning mock structures with the latest model updates.

* fix: Safeguard token credit checks in transaction tests

- Updated assertions in `transaction.spec.ts` to handle potential null values for `updatedBalance` by using optional chaining.
- Enhanced robustness of tests related to token credit calculations, ensuring they correctly account for scenarios where the balance may not be found.

* chore: transaction methods with bulk insert functionality

- Introduced `bulkInsertTransactions` method in `transaction.ts` to facilitate batch insertion of transaction documents.
- Updated test file `transactions.bulk-parity.spec.ts` to utilize new pricing function assignments and handle potential null values in calculations, improving test robustness.
- Refactored pricing function initialization for clarity and consistency.

* refactor: Enhance type definitions and introduce new utility functions for model matching

- Added `findMatchingPattern` and `matchModelName` utility functions to improve model name matching logic in transaction methods.
- Updated type definitions for `findMatchingPattern` to accept a more specific tokensMap structure, enhancing type safety.
- Refactored `dbMethods` initialization in `transactions.bulk-parity.spec.ts` to include the new utility functions, improving test clarity and functionality.

* refactor: Update database method imports and enhance transaction handling

- Refactored `abortMiddleware.js` to utilize centralized database methods for message handling and conversation retrieval, improving code consistency.
- Enhanced `bulkInsertTransactions` in `transaction.ts` to handle empty document arrays gracefully and added error logging for better debugging.
- Updated type definitions in `transactions.ts` to enforce stricter typing for token types, enhancing type safety across transaction methods.
- Improved test setup in `transactions.bulk-parity.spec.ts` by refining pricing function assignments and ensuring robust handling of potential null values.

* refactor: Update database method references and improve transaction multiplier handling

- Refactored `client.js` to update database method references for `bulkInsertTransactions` and `updateBalance`, ensuring consistency in method usage.
- Enhanced transaction multiplier calculations in `transaction.spec.ts` to provide fallback values for write and read multipliers, improving robustness in cost calculations across structured token spending tests.
2026-03-21 14:28:53 -04:00
Danny Avila
8ba2bde5c1
📦 refactor: Consolidate DB models, encapsulating Mongoose usage in data-schemas (#11830)
* chore: move database model methods to /packages/data-schemas

* chore: add TypeScript ESLint rule to warn on unused variables

* refactor: model imports to streamline access

- Consolidated model imports across various files to improve code organization and reduce redundancy.
- Updated imports for models such as Assistant, Message, Conversation, and others to a unified import path.
- Adjusted middleware and service files to reflect the new import structure, ensuring functionality remains intact.
- Enhanced test files to align with the new import paths, maintaining test coverage and integrity.

* chore: migrate database models to packages/data-schemas and refactor all direct Mongoose Model usage outside of data-schemas

* test: update agent model mocks in unit tests

- Added `getAgent` mock to `client.test.js` to enhance test coverage for agent-related functionality.
- Removed redundant `getAgent` and `getAgents` mocks from `openai.spec.js` and `responses.unit.spec.js` to streamline test setup and reduce duplication.
- Ensured consistency in agent mock implementations across test files.

* fix: update types in data-schemas

* refactor: enhance type definitions in transaction and spending methods

- Updated type definitions in `checkBalance.ts` to use specific request and response types.
- Refined `spendTokens.ts` to utilize a new `SpendTxData` interface for better clarity and type safety.
- Improved transaction handling in `transaction.ts` by introducing `TransactionResult` and `TxData` interfaces, ensuring consistent data structures across methods.
- Adjusted unit tests in `transaction.spec.ts` to accommodate new type definitions and enhance robustness.

* refactor: streamline model imports and enhance code organization

- Consolidated model imports across various controllers and services to a unified import path, improving code clarity and reducing redundancy.
- Updated multiple files to reflect the new import structure, ensuring all functionalities remain intact.
- Enhanced overall code organization by removing duplicate import statements and optimizing the usage of model methods.

* feat: implement loadAddedAgent and refactor agent loading logic

- Introduced `loadAddedAgent` function to handle loading agents from added conversations, supporting multi-convo parallel execution.
- Created a new `load.ts` file to encapsulate agent loading functionalities, including `loadEphemeralAgent` and `loadAgent`.
- Updated the `index.ts` file to export the new `load` module instead of the deprecated `loadAgent`.
- Enhanced type definitions and improved error handling in the agent loading process.
- Adjusted unit tests to reflect changes in the agent loading structure and ensure comprehensive coverage.

* refactor: enhance balance handling with new update interface

- Introduced `IBalanceUpdate` interface to streamline balance update operations across the codebase.
- Updated `upsertBalanceFields` method signatures in `balance.ts`, `transaction.ts`, and related tests to utilize the new interface for improved type safety.
- Adjusted type imports in `balance.spec.ts` to include `IBalanceUpdate`, ensuring consistency in balance management functionalities.
- Enhanced overall code clarity and maintainability by refining type definitions related to balance operations.

* feat: add unit tests for loadAgent functionality and enhance agent loading logic

- Introduced comprehensive unit tests for the `loadAgent` function, covering various scenarios including null and empty agent IDs, loading of ephemeral agents, and permission checks.
- Enhanced the `initializeClient` function by moving `getConvoFiles` to the correct position in the database method exports, ensuring proper functionality.
- Improved test coverage for agent loading, including handling of non-existent agents and user permissions.

* chore: reorder memory method exports for consistency

- Moved `deleteAllUserMemories` to the correct position in the exported memory methods, ensuring a consistent and logical order of method exports in `memory.ts`.
2026-03-21 14:28:53 -04:00
Danny Avila
58f128bee7
🗑️ chore: Remove Deprecated Project Model and Associated Fields (#11773)
* chore: remove projects and projectIds usage

* chore: empty line linting

* chore: remove isCollaborative property across agent models and related tests

- Removed the isCollaborative property from agent models, controllers, and tests, as it is deprecated in favor of ACL permissions.
- Updated related validation schemas and data provider types to reflect this change.
- Ensured all references to isCollaborative were stripped from the codebase to maintain consistency and clarity.
2026-03-21 14:28:53 -04:00
Danny Avila
38521381f4
🐘 feat: FerretDB Compatibility (#11769)
* feat: replace unsupported MongoDB aggregation operators for FerretDB compatibility

Replace $lookup, $unwind, $sample, $replaceRoot, and $addFields aggregation
stages which are unsupported on FerretDB v2.x (postgres-documentdb backend).

- Prompt.js: Replace $lookup/$unwind/$project pipelines with find().select().lean()
  + attachProductionPrompts() batch helper. Replace $group/$replaceRoot/$sample
  in getRandomPromptGroups with distinct() + Fisher-Yates shuffle.
- Agent/Prompt migration scripts: Replace $lookup anti-join pattern with
  distinct() + $nin two-step queries for finding un-migrated resources.

All replacement patterns verified against FerretDB v2.7.0.

* fix: use $pullAll for simple array removals, fix memberIds type mismatches

Replace $pull with $pullAll for exact-value scalar array removals. Both
operators work on MongoDB and FerretDB, but $pullAll is more explicit for
exact matching (no condition expressions).

Fix critical type mismatch bugs where ObjectId values were used against
String[] memberIds arrays in Group queries:
- config/delete-user.js: use string uid instead of ObjectId user._id
- e2e/setup/cleanupUser.ts: convert userId.toString() before query

Harden PermissionService.bulkUpdateResourcePermissions abort handling to
prevent crash when abortTransaction is called after commitTransaction.

All changes verified against FerretDB v2.7.0 and MongoDB Memory Server.

* fix: harden transaction support probe for FerretDB compatibility

Commit the transaction before aborting in supportsTransactions probe, and
wrap abortTransaction in try-catch to prevent crashes when abort is called
after a successful commit (observed behavior on FerretDB).

* feat: add FerretDB compatibility test suite, retry utilities, and CI config

Add comprehensive FerretDB integration test suite covering:
- $pullAll scalar array operations
- $pull with subdocument conditions
- $lookup replacement (find + manual join)
- $sample replacement (distinct + Fisher-Yates)
- $bit and $bitsAllSet operations
- Migration anti-join pattern
- Multi-tenancy (useDb, scaling, write amplification)
- Sharding proof-of-concept
- Production operations (backup/restore, schema migration, deadlock retry)

Add production retryWithBackoff utility for deadlock recovery during
concurrent index creation on FerretDB/DocumentDB backends.

Add UserController.spec.js tests for deleteUserController (runs in CI).

Configure jest and eslint to isolate FerretDB tests from CI pipelines:
- packages/data-schemas/jest.config.mjs: ignore misc/ directory
- eslint.config.mjs: ignore packages/data-schemas/misc/

Include Docker Compose config for local FerretDB v2.7 + postgres-documentdb,
dedicated jest/tsconfig for the test files, and multi-tenancy findings doc.

* style: brace formatting in aclEntry.ts modifyPermissionBits

* refactor: reorganize retry utilities and update imports

- Moved retryWithBackoff utility to a new file `retry.ts` for better structure.
- Updated imports in `orgOperations.ferretdb.spec.ts` to reflect the new location of retry utilities.
- Removed old import statement for retryWithBackoff from index.ts to streamline exports.

* test: add $pullAll coverage for ConversationTag and PermissionService

Add integration tests for deleteConversationTag verifying $pullAll
removes tags from conversations correctly, and for
syncUserEntraGroupMemberships verifying $pullAll removes user from
non-matching Entra groups while preserving local group membership.

---------
2026-03-21 14:28:49 -04:00
crossagent
290984c514
🔑 fix: Type-Safe User Context Forwarding for Non-OAuth Tool Discovery (#12348)
Some checks failed
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* fix(mcp): pass missing customUserVars and user during unauthenticated tool discovery

* fix(mcp): type-safe user context forwarding for non-OAuth tool discovery

Extract UserConnectionContext from OAuthConnectionOptions to properly
model the non-OAuth case where user/customUserVars/requestBody need
placeholder resolution without requiring OAuth-specific fields.

- Remove prohibited `as unknown as` double-cast
- Forward requestBody and connectionTimeout (previously omitted)
- Add unit tests for argument forwarding at Manager and Factory layers
- Add integration test exercising real processMCPEnv substitution

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-21 12:46:23 -04:00
Danny Avila
0736ff2668
v0.8.4 (#12339)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Publish `@librechat/client` to NPM / build-and-publish (push) Has been cancelled
Publish `librechat-data-provider` to NPM / build (push) Has been cancelled
Publish `@librechat/data-schemas` to NPM / build-and-publish (push) Has been cancelled
Publish `librechat-data-provider` to NPM / publish-npm (push) Has been cancelled
* 🔖 chore: Bump version to v0.8.4

- App version: v0.8.4-rc1 → v0.8.4
- @librechat/api: 1.7.26 → 1.7.27
- @librechat/client: 0.4.55 → 0.4.56
- librechat-data-provider: 0.8.400 → 0.8.401
- @librechat/data-schemas: 0.0.39 → 0.0.40

* chore: bun.lock file bumps
2026-03-20 18:01:00 -04:00
Danny Avila
01f19b503a
🛂 fix: Gate MCP Queries Behind USE Permission to Prevent 403 Spam (#12345)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
* 🐛 fix: Gate MCP queries behind USE permission to prevent 403 spam

Closes #12342

When `interface.mcpServers.use` is set to `false` in `librechat.yaml`,
the frontend was still unconditionally fetching `/api/mcp/servers` on
every app startup, window focus, and stale interval — producing
continuous 403 "Insufficient permissions" log entries.

Add `useHasAccess` permission checks to both `useMCPServersQuery` call
sites (`useAppStartup` and `useMCPServerManager`) so the query is
disabled when the user lacks `MCP_SERVERS.USE`, matching the guard
pattern already used by MCP UI components.

* fix: Lint and import order corrections

* fix: Address review findings — gate permissions query, add tests

- Gate `useGetAllEffectivePermissionsQuery` behind `canUseMcp` in
  `useMCPServerManager` for consistency (wasted request when MCP
  disabled, even though this endpoint doesn't 403)
- Sort multi-line `librechat-data-provider` import shortest to longest
- Restore intent comment on `useGetStartupConfig` call
- Add `useAppStartup` test suite covering MCP permission gating:
  query suppression when USE denied, compound `enabled` conditions
  for tools query (servers loading, empty, no user)
2026-03-20 17:10:39 -04:00
Danny Avila
365a0dc0f6
🩺 refactor: Surface Descriptive OCR Error Messages to Client (#12344)
* fix: pass along error message when OCR fails

Right now, if OCR fails, it just says "Error processing file" which
isn't very helpful.

The `error.message` does has helpful information in it, but our
filter wasn't including the right case to pass it along. Now it does!

* fix: extract shared upload error filter, apply to images route

The 'Unable to extract text from' error was only allowlisted in the
files route but not the images route, which also calls
processAgentFileUpload. Extract the duplicated error filter logic
into a shared resolveUploadErrorMessage utility in packages/api so
both routes stay in sync.

---------

Co-authored-by: Dan Lew <daniel@mightyacorn.com>
2026-03-20 17:10:25 -04:00
Danny Avila
676d297cb4
🗣️ a11y: Add Screen Reader Context to Conversation Date Group Headings (#12340)
* fix: add screen reader-only context for convo date groupings

Otherwise, the screen reader simply says something like "today" or
"previous 7 days" without any other context, which is confusing (especially
since this is a heading, so theoretically something you'd navigate to
directly).

Visually it's identical to before, but screen readers have added context
now.

* fix: move a11y key to com_a11y_* namespace and add DateLabel test

Move screen-reader-only translation key from com_ui_* to com_a11y_*
namespace where it belongs, and add test coverage to prevent silent
accessibility regressions.

---------

Co-authored-by: Dan Lew <daniel@mightyacorn.com>
2026-03-20 16:53:26 -04:00
Danny Avila
6976414464
🗣️ a11y: Distinguish Conversation Headings for Screen Readers (#12341)
* fix: distinguish message headings for screen readers

Before, each message would have the heading of either the name of
the user or the name of the agent (e.g. "Dan Lew" or "Claude Sonnet").
If you tried to navigate that with a screen reader, you'd just see
a ton of headings switching back and forth between the two with no
way to figure out where in the conversation each is.

Now, we prefix each header with whether it's a "prompt" or "response",
plus we number them so that you can distinguish how far in the
conversation each part is.

(This is a screen reader only change - there's no visual difference.)

* fix: patch MessageParts heading, guard negative depth, add tests

- Add sr-only heading prefix to MessageParts.tsx (Assistants endpoint path)
- Extract shared getMessageNumber helper to avoid DRY violation between
  getMessageAriaLabel and getHeaderPrefixForScreenReader
- Guard against depth < 0 producing "Prompt 0:" / "Response 0:"
- Remove unused lodash import
- Add unit tests covering all branches including depth edge cases

---------

Co-authored-by: Dan Lew <daniel@mightyacorn.com>
2026-03-20 16:50:12 -04:00
github-actions[bot]
729ba96100
🌍 i18n: Update translation.json with latest translations (#12338)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-20 13:43:25 -04:00
Danny Avila
b66f7914a5
⛓️💥 fix: Replace React Markdown Artifact Renderer with Static HTML (#12337)
Some checks are pending
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
The react-markdown dependency chain uses Node.js subpath imports
(vfile/lib/#minpath) that Sandpack's bundler cannot resolve, breaking
markdown artifact preview. Switch to a self-contained static HTML page
using marked.js from CDN, eliminating the React bootstrap overhead and
the problematic dependency resolution.
2026-03-20 13:31:08 -04:00
YE
59873e74fc
🏮 docs: Add Simplified Chinese README Translation (#12323)
* docs: add Simplified Chinese translation (README.zh.md)

* docs: sync README.zh.md with current English README

Address review findings:
- Fix stale Railway URLs (railway.app -> railway.com, /template/ -> /deploy/)
- Add missing Resumable Streams section
- Add missing sub-features (Agent Marketplace, Collaborative Sharing, Jina
  Reranking, prompt sharing, Helicone provider)
- Update multilingual UI language list from 18 to 30+ languages
- Replace outdated "ChatGPT clone" platform description with current messaging
- Remove stale video embed no longer in English README
- Remove non-standard bold wrappers on links
- Fix trailing whitespace
- Add sync header with date and commit SHA

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-20 13:08:48 -04:00
mfish911
4e5ae28fa9
📡 feat: Support Unauthenticated SMTP Relays (#12322)
* allow smtp server that does not have authentication

* fix: align checkEmailConfig with optional SMTP credentials and add tests

Remove EMAIL_USERNAME/EMAIL_PASSWORD requirements from the hasSMTPConfig
predicate in checkEmailConfig() so the rest of the codebase (login,
startup checks, invite-user) correctly recognizes unauthenticated SMTP
as a valid email configuration.

Add a warning when only one of the two credential env vars is set,
in both sendEmail.js and checkEmailConfig(), to catch partial
misconfigurations early.

Add test coverage for both the transporter auth assembly in sendEmail.js
and the checkEmailConfig predicate in packages/api.

Document in .env.example that credentials are optional for
unauthenticated SMTP relays.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-20 13:07:39 -04:00
ethanlaj
28c2e224ae
🧹 chore: Resolve correct memory directory in .gitignore (#12330)
* fix: Exclude memory directory from gitignore for API package

* fix: Scope memory/ and coordination/ gitignore to repo root

Prefix patterns with `/` so they only match root-level Claude Flow
artifact directories, not workspace source like packages/api/src/memory/.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-20 13:06:04 -04:00
JooyoungChoi14
54fc9c2c99
♾️ fix: Permanent Ban Cache and Expired Ban Cleanup Defects (#12324)
* fix: preserve ban data object in checkBan to prevent permanent cache

The !! operator on line 108 coerces the ban data object to a boolean,
losing the expiresAt property. This causes:

1. Number(true.expiresAt) = NaN → expired bans never cleaned from banLogs
2. banCache.set(key, true, NaN) → Keyv stores with expires: null (permanent)
3. IP-based cache entries persist indefinitely, blocking unrelated users

Fix: replace isBanned (boolean) with banData (original object) so that
expiresAt is accessible for TTL calculation and proper cache expiry.

* fix: address checkBan cleanup defects exposed by ban data fix

The prior commit correctly replaced boolean coercion with the ban data
object, but activated previously-dead cleanup code with several defects:

- IP-only expired bans fell through cleanup without returning next(),
  re-caching with negative TTL (permanent entry) and blocking the user
- Redis deployments used cache-prefixed keys for banLogs.delete(),
  silently failing since bans are stored at raw keys
- banCache.set() calls were fire-and-forget, silently dropping errors
- No guard for missing/invalid expiresAt reproduced the NaN TTL bug
  on legacy ban records

Consolidate expired-ban cleanup into a single block that always returns
next(), use raw keys (req.ip, userId) for banLogs.delete(), add an
expiresAt validity guard, await cache writes with error logging, and
parallelize independent I/O with Promise.all.

Add 25 tests covering all checkBan code paths including the specific
regressions for IP-only cleanup, Redis key mismatch, missing expiresAt,
and cache write failures.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-20 12:47:51 -04:00
Airam Hernández Hernández
96f6976e00
🪂 fix: Automatic logout_hint Fallback for Oversized OpenID Token URLs (#12326)
* fix: automatic logout_hint fallback for long OpenID tokens

Implements OIDC RP-Initiated Logout cascading strategy to prevent errors when id_token_hint makes logout URL too long.

Automatically detects URLs exceeding configurable length and falls back to logout_hint only when URL is too long, preserving previous behavior when token is missing. Adds OPENID_MAX_LOGOUT_URL_LENGTH environment variable. Comprehensive test coverage with 20 tests. Works with any OpenID provider.

* fix: address review findings for OIDC logout URL length fallback

- Replace two-boolean tri-state (useIdTokenHint/urlTooLong) with a single
  string discriminant ('use_token'|'too_long'|'no_token') for clarity
- Fix misleading warning: differentiate 'url too long + no client_id' from
  'no token + no client_id' so operators get actionable advice
- Strict env var parsing: reject partial numeric strings like '500abc' that
  Number.parseInt silently accepted; use regex + Number() instead
- Pre-compute projected URL length from base URL + token length (JWT chars
  are URL-safe), eliminating the set-then-delete mutation pattern
- Extract parseMaxLogoutUrlLength helper for validation and early return
- Add tests: invalid env values, url-too-long + missing OPENID_CLIENT_ID,
  boundary condition (exact max vs max+1), cookie-sourced long token
- Remove redundant try/finally in 'respects custom limit' test
- Use empty value in .env.example to signal optional config (default: 2000)

---------

Co-authored-by: Airam Hernández Hernández <airam.hernandez@intelequia.com>
Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-20 12:46:57 -04:00
Danny Avila
594d9470d5
🪤 fix: Avoid express-rate-limit v8 ERR_ERL_KEY_GEN_IPV6 False Positive (#12333)
* fix: avoid express-rate-limit v8 ERR_ERL_KEY_GEN_IPV6 false positive

express-rate-limit v8 calls keyGenerator.toString() and throws
ERR_ERL_KEY_GEN_IPV6 if the source contains the literal substring
"req.ip" without "ipKeyGenerator". When packages/api compiles
req?.ip to older JS targets, the output contains "req.ip",
triggering the heuristic.

Bracket notation (req?.['ip']) produces identical runtime behavior
but never emits the literal "req.ip" substring regardless of
compilation target.

Closes #12321

* fix: add toString regression test and clean up redundant annotation

Add a test that verifies removePorts.toString() does not contain
"req.ip", guarding against reintroduction of the ERR_ERL_KEY_GEN_IPV6
false positive. Fix a misleading test description and remove a
redundant type annotation on a trivially-inferred local.
2026-03-20 12:32:55 -04:00
Danny Avila
e442984364
💣 fix: Harden against falsified ZIP metadata in ODT parsing (#12320)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Publish `@librechat/client` to NPM / build-and-publish (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* security: replace JSZip metadata guard with yauzl streaming decompression

The ODT decompressed-size guard was checking JSZip's private
_data.uncompressedSize fields, which are populated from the ZIP central
directory — attacker-controlled metadata. A crafted ODT with falsified
uncompressedSize values bypassed the 50MB cap entirely, allowing
content.xml decompression to exhaust Node.js heap memory (DoS).

Replace JSZip with yauzl for ODT extraction. The new extractOdtContentXml
function uses yauzl's streaming API: it lazily iterates ZIP entries,
opens a decompression stream for content.xml, and counts real bytes as
they arrive from the inflate stream. The stream is destroyed the moment
the byte count crosses ODT_MAX_DECOMPRESSED_SIZE, aborting the inflate
before the full payload is materialised in memory.

- Remove jszip from direct dependencies (still transitive via mammoth)
- Add yauzl + @types/yauzl
- Update zip-bomb test to verify streaming abort with DEFLATE payload

* fix: close file descriptor leaks and declare jszip test dependency

- Use a shared `finish()` helper in extractOdtContentXml that calls
  zipfile.close() on every exit path (success, size cap, missing entry,
  openReadStream errors, zipfile errors). Without this, any error path
  leaked one OS file descriptor permanently — uploading many malformed
  ODTs could exhaust the process FD limit (a distinct DoS vector).
- Add jszip to devDependencies so the zip-bomb test has an explicit
  dependency rather than relying on mammoth's transitive jszip.
- Update JSDoc to document that all exit paths close the zipfile.

* fix: move yauzl from dependencies to peerDependencies

Matches the established pattern for runtime parser libraries in
packages/api: mammoth, pdfjs-dist, and xlsx are all peerDependencies
(provided by the consuming /api workspace) with devDependencies for
testing. yauzl was incorrectly placed in dependencies.

* fix: add yauzl to /api dependencies to satisfy peer dep

packages/api declares yauzl as a peerDependency; /api is the consuming
workspace that must provide it at runtime, matching the pattern used
for mammoth, pdfjs-dist, and xlsx.
2026-03-19 22:13:40 -04:00
Brad Russell
ecd6d76bc8
🚦 fix: ERR_ERL_INVALID_IP_ADDRESS and IPv6 Key Collisions in IP Rate Limiters (#12319)
* fix: Add removePorts keyGenerator to all IP-based rate limiters

Six IP-based rate limiters are missing the `keyGenerator: removePorts`
option that is already used by the auth-related limiters (login,
register, resetPassword, verifyEmail). Without it, reverse proxies that
include ports in X-Forwarded-For headers cause
ERR_ERL_INVALID_IP_ADDRESS errors from express-rate-limit.

Fixes #12318

* fix: make removePorts IPv6-safe to prevent rate-limit key collisions

The original regex `/:\d+[^:]*$/` treated the last colon-delimited
segment of bare IPv6 addresses as a port, mangling valid IPs
(e.g. `::1` → `::`, `2001:db8::1` → `2001:db8::`). Distinct IPv6
clients could collapse into the same rate-limit bucket.

Use `net.isIP()` as a fast path for already-valid IPs, then match
bracketed IPv6+port and IPv4+port explicitly. Bare IPv6 addresses
are now returned unchanged.

Also fixes pre-existing property ordering inconsistency in
ttsLimiters.js userLimiterOptions (keyGenerator before store).

* refactor: move removePorts to packages/api as TypeScript, fix import order

- Move removePorts implementation to packages/api/src/utils/removePorts.ts
  with proper Express Request typing
- Reduce api/server/utils/removePorts.js to a thin re-export from
  @librechat/api for backward compatibility
- Consolidate removePorts import with limiterCache from @librechat/api
  in all 6 limiter files, fixing import order (package imports shortest
  to longest, local imports longest to shortest)
- Remove narrating inline comments per code style guidelines

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-19 21:48:03 -04:00
Danny Avila
748fd086c1
📦 chore: Update fast-xml-parser to v5.5.7 (#12317)
- Bump fast-xml-parser dependency from 5.5.6 to 5.5.7 for improved functionality and compatibility.
- Update corresponding entries in both package.json and package-lock.json to reflect the new version.
2026-03-19 18:09:23 -04:00
Danny Avila
1ecff83b20
🪦 fix: ACL-Safe User Account Deletion for Agents, Prompts, and MCP Servers (#12314)
* fix: use ACL ownership for prompt group cleanup on user deletion

deleteUserPrompts previously called getAllPromptGroups with only an
author filter, which defaults to searchShared=true and drops the
author filter for shared/global project entries. This caused any user
deleting their account to strip shared prompt group associations and
ACL entries for other users.

Replace the author-based query with ACL-based ownership lookup:
- Find prompt groups where the user has OWNER permission (DELETE bit)
- Only delete groups where the user is the sole owner
- Preserve multi-owned groups and their ACL entries for other owners

* fix: use ACL ownership for agent cleanup on user deletion

deleteUserAgents used the deprecated author field to find and delete
agents, then unconditionally removed all ACL entries for those agents.
This could destroy ACL entries for agents shared with or co-owned by
other users.

Replace the author-based query with ACL-based ownership lookup:
- Find agents where the user has OWNER permission (DELETE bit)
- Only delete agents where the user is the sole owner
- Preserve multi-owned agents and their ACL entries for other owners
- Also clean up handoff edges referencing deleted agents

* fix: add MCP server cleanup on user deletion

User deletion had no cleanup for MCP servers, leaving solely-owned
servers orphaned in the database with dangling ACL entries for other
users.

Add deleteUserMcpServers that follows the same ACL ownership pattern
as prompt groups and agents: find servers with OWNER permission,
check for sole ownership, and only delete those with no other owners.

* style: fix prettier formatting in Prompt.spec.js

* refactor: extract getSoleOwnedResourceIds to PermissionService

The ACL sole-ownership detection algorithm was duplicated across
deleteUserPrompts, deleteUserAgents, and deleteUserMcpServers.
Centralizes the three-step pattern (find owned entries, find other
owners, compute sole-owned set) into a single reusable utility.

* refactor: use getSoleOwnedResourceIds in all deletion functions

- Replace inline ACL queries with the centralized utility
- Remove vestigial _req parameter from deleteUserPrompts
- Use Promise.all for parallel project removal instead of sequential awaits
- Disconnect live MCP sessions and invalidate tool cache before deleting
  sole-owned MCP server documents
- Export deleteUserMcpServers for testability

* test: improve deletion test coverage and quality

- Move deleteUserPrompts call to beforeAll to eliminate execution-order
  dependency between tests
- Standardize on test() instead of it() for consistency in Prompt.spec.js
- Add assertion for deleting user's own ACL entry preservation on
  multi-owned agents
- Add deleteUserMcpServers integration test suite with 6 tests covering
  sole-owner deletion, multi-owner preservation, session disconnect,
  cache invalidation, model-not-registered guard, and missing MCPManager
- Add PermissionService mock to existing deleteUser.spec.js to fix
  import chain

* fix: add legacy author-based fallback for unmigrated resources

Resources created before the ACL system have author set but no AclEntry
records. The sole-ownership detection returns empty for these, causing
deleteUserPrompts, deleteUserAgents, and deleteUserMcpServers to silently
skip them — permanently orphaning data on user deletion.

Add a fallback that identifies author-owned resources with zero ACL
entries (truly unmigrated) and includes them in the deletion set. This
preserves the multi-owner safety of the ACL path while ensuring pre-ACL
resources are still cleaned up regardless of migration status.

* style: fix prettier formatting across all changed files

* test: add resource type coverage guard for user deletion

Ensures every ResourceType in the ACL system has a corresponding cleanup
handler wired into deleteUserController. When a new ResourceType is added
(e.g. WORKFLOW), this test fails immediately, preventing silent data
orphaning on user account deletion.

* style: fix import order in PermissionService destructure

* test: add opt-out set and fix test lifecycle in coverage guard

Add NO_USER_CLEANUP_NEEDED set for resource types that legitimately
require no per-user deletion. Move fs.readFileSync into beforeAll so
path errors surface as clean test failures instead of unhandled crashes.
2026-03-19 17:46:14 -04:00
Danny Avila
f380390408
🛡️ fix: Prevent loop in ChatGPT import on Cyclic Parent Graphs (#12313)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Cap adjustTimestampsForOrdering to N passes and add cycle detection
to findValidParent, preventing DoS via crafted ChatGPT export files
with cyclic parentMessageId relationships.

Add breakParentCycles to sever cyclic back-edges before saving,
ensuring structurally valid message trees are persisted to the DB.
2026-03-19 17:15:12 -04:00
Danny Avila
ec0238d7ca
🐳 chore: Upgrade Alpine packages in Dockerfiles (#12316)
- Added `apk upgrade --no-cache` to both Dockerfile and Dockerfile.multi to ensure all installed packages are up to date.
- Maintained the installation of `jemalloc` and other dependencies without changes.
2026-03-19 17:07:08 -04:00
Danny Avila
3abad53c16
📦 chore: Bump @dicebear dependencies to v9.4.1 (#12315)
- Bump @dicebear/collection and @dicebear/core to version 9.4.1 across multiple package files for consistency and improved functionality.
- Update related dependencies in the client and packages/client directories to ensure compatibility with the new versions.
2026-03-19 16:44:38 -04:00
Danny Avila
11ab5f6ee5
🛂 fix: Reject OpenID Email Fallback When Stored openidId Mismatches Token Sub (#12312)
* 🔐 fix: Reject OpenID email fallback when stored openidId mismatches token sub

When `findOpenIDUser` falls back to email lookup after the primary
`openidId`/`idOnTheSource` query fails, it now rejects any user whose
stored `openidId` differs from the incoming JWT subject claim. This
closes an account-takeover vector where a valid IdP JWT containing a
victim's email but a different `sub` could authenticate as the victim
when OPENID_REUSE_TOKENS is enabled.

The migration path (user has no `openidId` yet) is unaffected.

* test: Validate openidId mismatch guard in email fallback path

Update `findOpenIDUser` unit tests to assert that email-based lookups
returning a user with a different `openidId` are rejected with
AUTH_FAILED. Add matching integration test in `openIdJwtStrategy.spec`
exercising the full verify callback with the real `findOpenIDUser`.

* 🔐 fix: Remove redundant `openidId` truthiness check from mismatch guard

The `&& openidId` middle term in the guard condition caused it to be
bypassed when the incoming token `sub` was empty or undefined. Since
the JS callers can pass `payload?.sub` (which may be undefined), this
created a path where the guard never fired and the email fallback
returned the victim's account. Removing the term ensures the guard
rejects whenever the stored openidId differs from the incoming value,
regardless of whether the incoming value is falsy.

* test: Cover falsy openidId bypass and openidStrategy mismatch rejection

Add regression test for the guard bypass when `openidId` is an empty
string and the email lookup finds a user with a stored openidId.

Add integration test in openidStrategy.spec.js exercising the
mismatch rejection through the full processOpenIDAuth callback,
ensuring both OIDC paths (JWT reuse and standard callback) are
covered.

Restore intent-documenting comment on the no-provider fixture.
2026-03-19 16:42:57 -04:00
Danny Avila
39f5f83a8a
🔌 fix: Isolate Code-Server HTTP Agents to Prevent Socket Pool Contamination (#12311)
* 🔧 fix: Isolate HTTP agents for code-server axios requests

Prevents socket hang up after 5s on Node 19+ when code executor has
file attachments. follow-redirects (axios dep) leaks `socket.destroy`
as a timeout listener on TCP sockets; with Node 19+ defaulting to
keepAlive: true, tainted sockets re-enter the global pool and destroy
active node-fetch requests in CodeExecutor after the idle timeout.

Uses dedicated http/https agents with keepAlive: false for all axios
calls targeting CODE_BASEURL in crud.js and process.js.

Closes #12298

* ♻️ refactor: Extract code-server HTTP agents to shared module

- Move duplicated agent construction from crud.js and process.js into
  a shared agents.js module to eliminate DRY violation
- Switch process.js from raw `require('axios')` to `createAxiosInstance()`
  for proxy configuration parity with crud.js
- Fix import ordering in process.js (agent constants no longer split imports)
- Add 120s timeout to uploadCodeEnvFile (was the only code-server call
  without a timeout)

*  test: Add regression tests for code-server socket isolation

- Add crud.spec.js covering getCodeOutputDownloadStream and
  uploadCodeEnvFile (agent options, timeout, URL, error handling)
- Add socket pool isolation tests to process.spec.js asserting
  keepAlive:false agents are forwarded to axios
- Update process.spec.js mocks for createAxiosInstance() migration

* ♻️ refactor: Move code-server agents to packages/api

Relocate agents.js from api/server/services/Files/Code/ to
packages/api/src/utils/code.ts per workspace conventions. Consumers
now import codeServerHttpAgent/codeServerHttpsAgent from @librechat/api.
2026-03-19 16:16:57 -04:00
Pol Burkardt Freire
7e74165c3c
📖 feat: Add Native ODT Document Parser Support (#12303)
* fix: add ODT support to native document parser

* fix: replace execSync with jszip for ODT parsing

* docs: update documentParserMimeTypes comment to include odt

* fix: improve ODT XML extraction and add empty.odt fixture

- Scope extraction to <office:body> to exclude metadata/style nodes
- Map </text:p> and </text:h> closings to newlines, preserving paragraph
  structure instead of collapsing everything to a single line
- Handle <text:line-break/> as explicit newlines
- Strip remaining tags, normalize horizontal whitespace, cap consecutive
  blank lines at one
- Regenerate sample.odt as a two-paragraph fixture so the test exercises
  multi-paragraph output
- Add empty.odt fixture and test asserting 'No text found in document'

* fix: address review findings in ODT parser

- Use static `import JSZip from 'jszip'` instead of dynamic import;
  jszip is CommonJS-only with no ESM/Jest-isolation concern (F1)
- Decode the five standard XML entities after tag-stripping so
  documents with &, <, >, ", ' send correct text to the LLM (F2)
- Remove @types/jszip devDependency; jszip ships bundled declarations
  and @types/jszip is a stale 2020 stub that would shadow them (F3)
- Handle <text:tab/> → \t and <text:s .../> → ' ' before the generic
  tag stripper so tab-aligned and multi-space content is preserved (F4)
- Add sample-entities.odt fixture and test covering entity decoding,
  tab, and spacing-element handling (F5)
- Rename 'throws for empty odt' → 'throws for odt with no extractable
  text' to distinguish from a zero-byte/corrupt file case (F8)

* fix: add decompressed content size cap to odtToText (F6)

Reads uncompressed entry sizes from the JSZip internal metadata before
extracting any content. Throws if the total exceeds 50MB, preventing a
crafted ODT with a high-ratio compressed payload from exhausting heap.

Adds a corresponding test using a real DEFLATE-compressed ZIP (~51KB on
disk, 51MB uncompressed) to verify the guard fires before any extraction.

* fix: add java to codeTypeMapping for file upload support

.java files were rejected with "Unable to determine file type" because
browsers send an empty MIME type for them and codeTypeMapping had no
'java' entry for inferMimeType() to fall back on.

text/x-java was already present in all five validation lists
(fullMimeTypesList, codeInterpreterMimeTypesList, retrievalMimeTypesList,
textMimeTypes, retrievalMimeTypes), so mapping to it (not text/plain)
ensures .java uploads work for both File Search and Code Interpreter.

Closes #12307

* fix: address follow-up review findings (A-E)

A: regenerate package-lock.json after removing @types/jszip from
   package.json; without this npm ci was still installing the stale
   2020 type stubs and TypeScript was resolving against them
B: replace dynamic import('jszip') in the zip-bomb test with the same
   static import already used in production; jszip is CJS-only with no
   ESM/Jest isolation concern
C: document that the _data.uncompressedSize guard fails open if jszip
   renames the private field (accepted limitation, test would catch it)
D: rename 'preserves tabs' test to 'normalizes tab and spacing elements
   to spaces' since <text:tab> is collapsed to a space, not kept as \t
E: fix test.each([ formatting artifact (missing newline after '[')

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-19 15:49:52 -04:00
Danny Avila
a88bfae4dd
🖼️ fix: Correct ToolMessage Response Format for Agent-Mode Image Tools (#12310)
* fix: Set response format for agent tools in DALLE3, FluxAPI, and StableDiffusion classes

- Added logic to set `responseFormat` to 'content_and_artifact' when `isAgent` is true in DALLE3.js, FluxAPI.js, and StableDiffusion.js.

* test: Add regression tests for image tool agent mode in imageTools-agent.spec.js

- Introduced a new test suite for DALLE3, FluxAPI, and StableDiffusion classes to verify that the invoke() method returns a ToolMessage with base64 in artifact.content, ensuring it is not serialized into content.
- Validated that responseFormat is set to 'content_and_artifact' when isAgent is true, and confirmed the correct handling of base64 data in the response.

* fix: handle agent error paths and generateFinetunedImage in image tools

- StableDiffusion._call() was returning a raw string on API error, bypassing
  returnValue() and breaking the content_and_artifact contract when isAgent is true
- FluxAPI.generateFinetunedImage() had no isAgent branch; it would call
  processFileURL (unset in agent context) instead of fetching and returning
  the base64 image as an artifact tuple
- Add JSDoc to all three responseFormat assignments clarifying why LangChain
  requires this property for correct ToolMessage construction

* test: expand image tool agent mode regression suite

- Add env var save/restore in beforeEach/afterEach to prevent test pollution
- Add error path tests for all three tools verifying ToolMessage content and
  artifact are correctly populated when the upstream API fails
- Add generate_finetuned action test for FluxAPI covering the new agent branch
  in generateFinetunedImage

* chore: fix lint errors in FluxAPI and imageTools-agent spec

* chore: fix import ordering in imageTools-agent spec
2026-03-19 15:33:46 -04:00
Danny Avila
93952f06b4
🧯 fix: Remove Revoked Agents from User Favorites (#12296)
* 🧯 fix: Remove revoked agents from user favorites

When agent access is revoked, the agent remained in the user's favorites
causing repeated 403 errors on page load. Backend now cleans up favorites
on permission revocation; frontend treats 403 like 404 and auto-removes
stale agent references.

* 🧪 fix: Address review findings for stale agent favorites cleanup

- Guard cleanup effect with ref to prevent infinite loop on mutation
  failure (Finding 1)
- Use validated results.revoked instead of raw request payload for
  revokedUserIds (Finding 3)
- Stabilize staleAgentIds memo with string key to avoid spurious
  re-evaluation during drag-drop (Finding 5)
- Add JSDoc with param types to removeRevokedAgentFromFavorites
  (Finding 7)
- Return promise from removeRevokedAgentFromFavorites for testability
- Add 7 backend tests covering revocation cleanup paths
- Add 3 frontend tests for 403 handling and stale cleanup persistence
2026-03-19 15:15:10 -04:00
Danny Avila
b189972381
🎭 fix: Set Explicit Permission Defaults for USER Role in roleDefaults (#12308)
* fix: set explicit permission defaults for USER role in roleDefaults

Previously several permission types for the USER role had empty
objects in roleDefaults, causing the getPermissionValue fallback to
resolve SHARE/CREATE via the zod schema defaults on fresh installs.
This silently granted users MCP server creation ability and left
share permissions ambiguous.

Sets explicit defaults for all multi-field permission types:
- PROMPTS/AGENTS: USE and CREATE true, SHARE false
- MCP_SERVERS: USE true, CREATE/SHARE false
- REMOTE_AGENTS: all false

Adds regression tests covering the exact reported scenarios (fresh
install with `agents: { use: true }`, restart preserving admin-panel
overrides) and structural guards against future permission schema
expansions missing explicit USER defaults.

Closes #12306.

* fix: guard MCP_SERVERS.CREATE against configDefaults fallback + add migration

The roleDefaults fix alone was insufficient: loadDefaultInterface propagates
configDefaults.mcpServers.create=true as tier-1 in getPermissionValue, overriding
the roleDefault of false. This commit:

- Adds conditional guards for MCP_SERVERS.CREATE and REMOTE_AGENTS.CREATE matching
  the existing AGENTS/PROMPTS pattern (only include CREATE when explicitly configured
  in yaml OR on fresh install)
- Uses raw interfaceConfig for MCP_SERVERS.CREATE tier-1 instead of loadedInterface
  (which includes configDefaults fallback)
- Adds one-time migration backfill: corrects existing MCP_SERVERS.CREATE=true for
  USER role in DB when no explicit yaml config is present
- Adds restart-scenario and migration regression tests for MCP_SERVERS
- Cleans up roles.spec.ts: for..of loops, Permissions[] typing, Set for lookups,
  removes unnecessary aliases, improves JSDoc for exclusion list
- Fixes misleading test name for agents regression test
- Removes redundant not.toHaveProperty assertions after strict toEqual

* fix: use raw interfaceConfig for REMOTE_AGENTS.CREATE tier-1 (consistency)

Aligns REMOTE_AGENTS.CREATE with the MCP_SERVERS.CREATE fix — reads from
raw interfaceConfig instead of loadedInterface to prevent a future
configDefaults fallback from silently overriding the roleDefault.
2026-03-19 14:52:06 -04:00
Danny Avila
9cb5ac63f8
🫧 refactor: Clear Drafts and Surface Error on Expired SSE Stream (#12309)
* refactor: error handling in useResumableSSE for 404 responses

- Added logic to clear drafts from localStorage when a 404 error occurs.
- Integrated errorHandler to notify users of the error condition.
- Introduced comprehensive tests to validate the new behavior, ensuring drafts are cleared and error handling is triggered correctly.C

* feat: add STREAM_EXPIRED error handling and message localization

- Introduced handling for STREAM_EXPIRED errors in useResumableSSE, updating errorHandler to provide relevant feedback.
- Added a new error message for STREAM_EXPIRED in translation files for user notifications.
- Updated tests to ensure proper error handling and message verification for STREAM_EXPIRED scenarios.

* refactor: replace clearDraft with clearAllDrafts utility

- Removed the clearDraft function from useResumableSSE and useSSE hooks, replacing it with the new clearAllDrafts utility for better draft management.
- Updated localStorage interactions to ensure both text and file drafts are cleared consistently for a conversation.
- Enhanced code readability and maintainability by centralizing draft clearing logic.
2026-03-19 14:51:28 -04:00
Danny Avila
b5a55b23a4
📦 chore: NPM audit packages (#12286)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* 🔧 chore: Update dependencies in package-lock.json and package.json

- Bump @aws-sdk/client-bedrock-runtime from 3.980.0 to 3.1011.0 and update related dependencies.
- Update fast-xml-parser version from 5.3.8 to 5.5.6 in package.json.
- Adjust various @aws-sdk and @smithy packages to their latest versions for improved functionality and security.

* 🔧 chore: Update @librechat/agents dependency to version 3.1.57 in package.json and package-lock.json

- Bump @librechat/agents from 3.1.56 to 3.1.57 across multiple package files for consistency.
- Remove axios dependency from package.json as it is no longer needed.
2026-03-17 17:04:18 -04:00
Danny Avila
1e1a3a8f8d v0.8.4-rc1 (#12285)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Publish `@librechat/client` to NPM / build-and-publish (push) Has been cancelled
Publish `librechat-data-provider` to NPM / build (push) Has been cancelled
Publish `@librechat/data-schemas` to NPM / build-and-publish (push) Has been cancelled
Publish `librechat-data-provider` to NPM / publish-npm (push) Has been cancelled
- App version: v0.8.3 → v0.8.4-rc1
- @librechat/api: 1.7.25 → 1.7.26
- @librechat/client: 0.4.54 → 0.4.55
- librechat-data-provider: 0.8.302 → 0.8.400
- @librechat/data-schemas: 0.0.38 → 0.0.39
2026-03-17 16:08:48 -04:00
Dustin Healy
5b31bb720d
🔧 fix: Proper MCP Menu Dismissal (#12256)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* fix: replace manual focus hack with modal menu in MCP selector

Use Ariakit's `modal={true}` instead of a manual `requestAnimationFrame`
focus-restore wrapper, which eliminates the `useRef`/`useCallback`
overhead and lets Ariakit manage focus trapping natively. Also removes
the unused `focusLoop` option from both MCP menu stores and a narrating
comment in MCPSubMenu.

* test: add MCPSelect menu interaction tests

Cover button rendering, menu open/close via click and Escape, and
server toggle keeping the menu open. Renders real MCPServerMenuItem
and StackedMCPIcons components instead of re-implementing their
logic in mocks.

* fix: add unmountOnHide to MCP menu for consistency

Matches the pattern used by MCPSubMenu, BookmarkMenu, and other
Ariakit menus in the codebase. Ensures the menu fully detaches
from the DOM and accessibility tree when closed.

* fix: restore focusLoop on MCP menu stores

Ariakit's CompositeStore (which MenuStore extends) defaults focusLoop
to false. The previous commit incorrectly removed the explicit
focusLoop: true, which silently disabled Arrow-key wraparound
(mandatory per WAI-ARIA Menu pattern). modal={true} only traps Tab
focus — it does not enable Arrow-key looping.

* test: improve MCPSelect test coverage and mock hygiene

- Add aria-modal regression guard so removing modal={true} fails a test
- Add guard branch tests: no MCP access, empty servers, unpinned+empty
- Fix TooltipAnchor mock to correctly spread array children
- Fix import ordering per project conventions
- Move component import to top with other imports
- Replace unasserted jest.fn() mocks with plain values
- Use mutable module-scoped vars for per-test mock overrides

* fix: enhance pointer event handling in FavoriteItem component

Updated the opacity and pointer events logic in the FavoriteItem component to improve user interaction. The changes ensure that the component correctly manages pointer events based on the popover state, enhancing accessibility and usability.

* test: add MCPSubMenu menu interaction tests

Cover guard branch (empty servers), submenu open/close with real
Ariakit components and real MCPServerMenuItem, toggle persistence,
pin/unpin button behavior and aria-label states. Only context
providers and cross-package UI are mocked.

* test: add focusLoop regression guard for both MCP menus

ArrowDown from the last item must wrap to the first — this fails
without focusLoop: true on the menu store, directly guarding the
keyboard accessibility regression that was silently introduced.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-03-17 02:50:18 -04:00
Danny Avila
2f09d29c71
🛂 fix: Validate types Query Param in People Picker Access Middleware (#12276)
* 🛂 fix: Validate `types` query param in people picker access middleware

checkPeoplePickerAccess only inspected `req.query.type` (singular),
allowing callers to bypass type-specific permission checks by using
the `types` (plural) parameter accepted by the controller. Now both
`type` and `types` are collected and each requested principal type is
validated against the caller's role permissions.

* 🛂 refactor: Hoist valid types constant, improve logging, and add edge-case tests

- Hoist VALID_PRINCIPAL_TYPES to module-level Set to avoid per-request allocation
- Include both `type` and `types` in error log for debuggability
- Restore detailed JSDoc documenting per-type permission requirements
- Add missing .json() assertion on partial-denial test
- Add edge-case tests: all-invalid types, empty string types, PrincipalType.PUBLIC

* 🏷️ fix: Align TPrincipalSearchParams with actual controller API

The stale type used `type` (singular) but the controller and all callers
use `types` (plural array). Aligns with PrincipalSearchParams in
types/queries.ts.
2026-03-17 02:46:11 -04:00
Danny Avila
68435cdcd0
🧯 fix: Add Pre-Parse File Size Guard to Document Parser (#12275)
Prevent memory exhaustion DoS by rejecting documents exceeding 15MB
before reading them into memory, closing the gap between the 512MB
upload limit and unbounded in-memory parsing.
2026-03-17 02:36:18 -04:00
Danny Avila
0c378811f1
🏷️ fix: Clear ModelSpec Display Fields When Navigating via Agent Share Link (#12274)
- Extract `specDisplayFieldReset` constant and `mergeQuerySettingsWithSpec`
  utility to `client/src/utils/endpoints.ts` as a single source of truth
  for spec display fields that must be cleared on non-spec transitions.
- Clear `spec`, `iconURL`, `modelLabel`, and `greeting` from the merged
  preset in `ChatRoute.getNewConvoPreset()` when URL query parameters
  override the conversation without explicitly setting a spec.
- Also clear `greeting` in the parallel cleanup in `useQueryParams.newQueryConvo`
  using the shared `specDisplayFieldReset` constant.
- Guard the field reset on `specPreset != null` so null values aren't
  injected when no spec is configured.
- Add comprehensive test coverage for the merge-and-clear logic.
2026-03-17 02:12:34 -04:00
Danny Avila
9a64791e3e
🪢 fix: Action Domain Encoding Collision for HTTPS URLs (#12271)
* fix: strip protocol from domain before encoding in `domainParser`

All https:// (and http://) domains produced the same 10-char base64
prefix due to ENCODED_DOMAIN_LENGTH truncation, causing tool name
collisions for agents with multiple actions.

Strip the protocol before encoding so the base64 key is derived from
the hostname. Add `legacyDomainEncode` to preserve the old encoding
logic for backward-compatible matching of existing stored actions.

* fix: backward-compatible tool matching in ToolService

Update `getActionToolDefinitions` to match stored tools against both
new and legacy domain encodings. Update `loadActionToolsForExecution`
to resolve model-called tool names via a `normalizedToDomain` map
that includes both encoding variants, with legacy fallback for
request builder lookup.

* fix: action route save/delete domain encoding issues

Save routes now remove old tools matching either new or legacy domain
encoding, preventing stale entries when an action's encoding changes
on update.

Delete routes no longer re-encode the already-encoded domain extracted
from the stored actions array, which was producing incorrect keys and
leaving orphaned tools.

* test: comprehensive coverage for action domain encoding

Rewrite ActionService tests to cover real matching patterns used by
ToolService and action routes. Tests verify encode/decode round-trips,
protocol stripping, backward-compatible tool name matching at both
definition and execution phases, save-route cleanup of old/new
encodings, delete-route domain extraction, and the collision fix for
multi-action agents.

* fix: add legacy domain compat to all execution paths, make legacyDomainEncode sync

CRITICAL: processRequiredActions (assistants path) was not updated with
legacy domain matching — existing assistants with https:// domain actions
would silently fail post-deployment because domainMap only had new encoding.

MAJOR: loadAgentTools definitionsOnly=false path had the same issue.

Both now use a normalizedToDomain map with legacy+new entries and extract
function names via the matched key (not the canonical domain).

Also: make legacyDomainEncode synchronous (no async operations), store
legacyNormalized in processedActionSets to eliminate recomputation in
the per-tool fallback, and hoist domainSeparatorRegex to module level.

* refactor: clarify domain variable naming and tool-filter helpers in action routes

Rename shadowed 'domain' to 'encodedDomain' to separate raw URL from
encoded key in both agent and assistant save routes.

Rename shouldRemoveTool to shouldRemoveAgentTool / shouldRemoveAssistantTool
to make the distinct data-shape guards explicit.

Remove await on now-synchronous legacyDomainEncode.

* test: expand coverage for all review findings

- Add validateAndUpdateTool tests (protocol-stripping match logic)
- Restore unicode domain encode/decode/round-trip tests
- Add processRequiredActions matching pattern tests (assistants path)
- Add legacy guard skip test for short bare hostnames
- Add pre-normalized Set test for definition-phase optimization
- Fix corrupt-cache test to assert typeof instead of toBeDefined
- Verify legacyDomainEncode is synchronous (not a Promise)
- Remove all await on legacyDomainEncode (now sync)

58 tests, up from 44.

* fix: address follow-up review findings A-E

A: Fix stale JSDoc @returns {Promise<string>} on now-synchronous
   legacyDomainEncode — changed to @returns {string}.

B: Rename normalizedToDomain to domainLookupMap in processRequiredActions
   and loadAgentTools where keys are raw encoded domains (not normalized),
   avoiding confusion with loadActionToolsForExecution where keys ARE
   normalized.

C: Pre-normalize actionToolNames into a Set<string> in
   getActionToolDefinitions, replacing O(signatures × tools) per-check
   .some() + .replace() with O(1) Set.has() lookups.

D: Remove stripProtocol from ActionService exports — it is a one-line
   internal helper. Spec tests for it removed; behavior is fully covered
   by domainParser protocol-stripping tests.

E: Fix pre-existing bug where processRequiredActions re-loaded action
   sets on every missing-tool iteration. The guard !actionSets.length
   always re-triggered because actionSets was reassigned to a plain
   object (whose .length is undefined). Replaced with a null-check
   on a dedicated actionSetsData variable.

* fix: strip path and query from domain URLs in stripProtocol

URLs like 'https://api.example.com/v1/endpoint?foo=bar' previously
retained the path after protocol stripping, contaminating the encoded
domain key. Now strips everything after the first '/' following the
host, using string indexing instead of URL parsing to avoid punycode
normalization of unicode hostnames.

Closes Copilot review comments 1, 2, and 5.
2026-03-17 01:38:51 -04:00
Danny Avila
c68066a636
🪝 fix: MCP Refresh token on OAuth Discovery Failure (#12266)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
* 🔒 fix: Prevent token leaks to MCP server on OAuth discovery failure

When OAuth metadata discovery fails, refresh logic was falling back to
POSTing refresh tokens to /token on the MCP resource server URL instead
of the authorization server. A malicious MCP server could exploit this
by blocking .well-known discovery to harvest refresh tokens.

Changes:
- Replace unsafe /token fallback with hard error in both refresh paths
- Thread stored token_endpoint (SSRF-validated during initial flow)
  through the refresh chain so legacy servers without .well-known still
  work after the first successful auth
- Fix revokeOAuthToken to always SSRF-validate the revocation URL,
  including the /revoke fallback path
- Redact refresh token and credentials from debug-level log output
- Split branch 2 compound condition for consistent error messages

*  test: Add stored endpoint fallback tests and improve refresh coverage

- Add storedTokenEndpoint fallback tests for both refresh branches
- Add missing test for branch 2 metadata-without-token_endpoint case
- Rename misleading test name to match actual mock behavior
- Split auto-discovered throw test into undefined vs missing-endpoint
- Remove redundant afterEach mockFetch.mockClear() calls (already
  covered by jest.clearAllMocks() in beforeEach)
2026-03-16 09:31:01 -04:00
Danny Avila
d17ac8f06d
🔏 fix: Remove Federated Tokens from OpenID Refresh Response (#12264)
* 🔒 fix: Remove OpenID federated tokens from refresh endpoint response

The refresh controller was attaching federatedTokens (including the
refresh_token) to the user object returned in the JSON response,
exposing HttpOnly-protected tokens to client-side JavaScript.
The tokens are already stored server-side by setOpenIDAuthTokens
and re-attached by the JWT strategy on authenticated requests.

* 🔒 fix: Strip sensitive fields from OpenID refresh response user object

The OpenID refresh path returned the raw findOpenIDUser result without
field projection, unlike the non-OpenID path which excludes password,
__v, totpSecret, and backupCodes via getUserById projection. Destructure
out sensitive fields before serializing.

Also strengthens the regression test: uses not.toHaveProperty for true
property-absence checks (expect.anything() misses null/undefined), adds
positive shape assertion, and DRYs up duplicated mock user setup.
2026-03-16 09:23:46 -04:00
Danny Avila
381ed8539b
🪪 fix: Enforce Conversation Ownership Checks in Remote Agent Controllers (#12263)
* 🔒 fix: Validate conversation ownership in remote agent API endpoints

Add user-scoped ownership checks for client-supplied conversation IDs
in OpenAI-compatible and Open Responses controllers to prevent
cross-tenant file/message loading via IDOR.

* 🔒 fix: Harden ownership checks against type confusion and unhandled errors

- Add typeof string validation before getConvo to block NoSQL operator
  injection (e.g. { "$gt": "" }) bypassing the ownership check
- Move ownership checks inside try/catch so DB errors produce structured
  JSON error responses instead of unhandled promise rejections
- Add string type validation for conversation_id and previous_response_id
  in the upstream TS request validators (defense-in-depth)

* 🧪 test: Add coverage for conversation ownership validation in remote agent APIs

- Fix broken getConvo mock in openai.spec.js (was missing entirely)
- Add tests for: owned conversation, unowned (404), non-string type (400),
  absent conversation_id (skipped), and DB error (500) — both controllers
2026-03-16 09:19:48 -04:00
Danny Avila
951d261f5c
🧯 fix: Prevent Env-Variable Exfil. via Placeholder Injection (#12260)
* 🔒 fix: Resolve env vars before body placeholder expansion to prevent secret exfiltration

Body placeholders ({{LIBRECHAT_BODY_*}}) were substituted before
extractEnvVariable ran, allowing user-controlled body fields containing
${SECRET} patterns to be expanded into real environment values in
outbound headers. Reorder so env vars resolve first, preventing
untrusted input from triggering env expansion.

* 🛡️ fix: Block sensitive infrastructure env vars from placeholder resolution

Add isSensitiveEnvVar blocklist to extractEnvVariable so that internal
infrastructure secrets (JWT_SECRET, JWT_REFRESH_SECRET, CREDS_KEY,
CREDS_IV, MEILI_MASTER_KEY, MONGO_URI, REDIS_URI, REDIS_PASSWORD)
can never be resolved via ${VAR} expansion — even if an attacker
manages to inject a placeholder pattern.

Uses exact-match set (not substring patterns) to avoid breaking
legitimate operator config that references OAuth/API secrets in
MCP and custom endpoint configurations.

* 🧹 test: Rename ANOTHER_SECRET test fixture to ANOTHER_VALUE

Avoid using SECRET-containing names for non-sensitive test fixtures
to prevent confusion with the new isSensitiveEnvVar blocklist.

* 🔒 fix: Resolve env vars before all user-controlled substitutions in processSingleValue

Move extractEnvVariable to run on the raw admin-authored template
BEFORE customUserVars, user fields, OIDC tokens, and body placeholders.

Previously env resolution ran after customUserVars, so a user setting
a custom MCP variable to "${SECRET}" could still trigger env expansion.
Now env vars are resolved strictly on operator config, and all
subsequent user-controlled substitutions cannot introduce ${VAR} patterns
that would be expanded.

Gated by !dbSourced so DB-stored servers continue to skip env resolution.
Adds a security-invariant comment documenting the ordering requirement.

* 🧪 test: Comprehensive security regression tests for placeholder injection

- Cover all three body fields (conversationId, parentMessageId, messageId)
- Add user-field injection test (user.name containing ${VAR})
- Add customUserVars injection test (MY_TOKEN = "${VAR}")
- Add processMCPEnv injection tests for body and customUserVars paths
- Remove redundant process.env setup/teardown already handled by beforeEach/afterEach

* 🧹 chore: Add REDIS_PASSWORD to blocklist integration test; document customUserVars gate
2026-03-16 08:48:24 -04:00
github-actions[bot]
85e24e4c61
🌍 i18n: Update translation.json with latest translations (#12259)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-16 08:18:07 -04:00
Danny Avila
8271055c2d
📦 chore: Bump @librechat/agents to v3.1.56 (#12258)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* 📦 chore: Bump `@librechat/agents` to v3.1.56

* chore: resolve type error, URL property check in isMCPDomainAllowed function
2026-03-15 23:51:41 -04:00
Danny Avila
acd07e8085
🗝️ fix: Exempt Admin-Trusted Domains from MCP OAuth Validation (#12255)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
* fix: exempt allowedDomains from MCP OAuth SSRF checks (#12254)

The SSRF guard in validateOAuthUrl was context-blind — it blocked
private/internal OAuth endpoints even for admin-trusted MCP servers
listed in mcpSettings.allowedDomains. Add isHostnameAllowed() to
domain.ts and skip SSRF checks in validateOAuthUrl when the OAuth
endpoint hostname matches an allowed domain.

* refactor: thread allowedDomains through MCP connection stack

Pass allowedDomains from MCPServersRegistry through BasicConnectionOptions,
MCPConnectionFactory, and into MCPOAuthHandler method calls so the OAuth
layer can exempt admin-trusted domains from SSRF validation.

* test: add allowedDomains bypass tests and fix registry mocks

Add isHostnameAllowed unit tests (exact, wildcard, case-insensitive,
private IPs). Add MCPOAuthSecurity tests covering the allowedDomains
bypass for initiateOAuthFlow, refreshOAuthTokens, and revokeOAuthToken.
Update registry mocks to include getAllowedDomains.

* fix: enforce protocol/port constraints in OAuth allowedDomains bypass

Replace isHostnameAllowed (hostname-only check) with isOAuthUrlAllowed
which parses the full OAuth URL and matches against allowedDomains
entries including protocol and explicit port constraints — mirroring
isDomainAllowedCore's allowlist logic. Prevents a port-scoped entry
like 'https://auth.internal:8443' from also exempting other ports.

* test: cover auto-discovery and branch-3 refresh paths with allowedDomains

Add three new integration tests using a real OAuth test server:
- auto-discovered OAuth endpoints allowed when server IP is in allowedDomains
- auto-discovered endpoints rejected when allowedDomains doesn't match
- refreshOAuthTokens branch 3 (no clientInfo/config) with allowedDomains bypass

Also rename describe block from ephemeral issue number to durable name.

* docs: explain intentional absence of allowedDomains in completeOAuthFlow

Prevents future contributors from assuming a missing parameter during
security audits — URLs are pre-validated during initiateOAuthFlow.

* test: update initiateOAuthFlow assertion for allowedDomains parameter

* perf: avoid redundant URL parse for admin-trusted OAuth endpoints

Move isOAuthUrlAllowed check before the hostname extraction so
admin-trusted URLs short-circuit with a single URL parse instead
of two. The hostname extraction (new URL) is now deferred to the
SSRF-check path where it's actually needed.
2026-03-15 23:03:12 -04:00
Danny Avila
8e8fb01d18
🧱 fix: Enforce Agent Access Control on Context and OCR File Loading (#12253)
* 🔏 fix: Apply agent access control filtering to context/OCR resource loading

The context/OCR file path in primeResources fetched files by file_id
without applying filterFilesByAgentAccess, unlike the file_search and
execute_code paths. Add filterFiles dependency injection to primeResources
and invoke it after getFiles to enforce consistent access control.

* fix: Wire filterFilesByAgentAccess into all agent initialization callers

Pass the filterFilesByAgentAccess function from the JS layer into the TS
initializeAgent → primeResources chain via dependency injection, covering
primary, handoff, added-convo, and memory agent init paths.

* test: Add access control filtering tests for primeResources

Cover filterFiles invocation with context/OCR files, verify filtering
rejects inaccessible files, and confirm graceful fallback when filterFiles,
userId, or agentId are absent.

* fix: Guard filterFilesByAgentAccess against ephemeral agent IDs

Ephemeral agents have no DB document, so getAgent returns null and the
access map defaults to all-false, silently blocking all non-owned files.
Short-circuit with isEphemeralAgentId to preserve the pass-through
behavior for inline-built agents (memory, tool agents).

* fix: Clean up resources.ts and JS caller import order

Remove redundant optional chain on req.user.role inside user-guarded
block, update primeResources JSDoc with filterFiles and agentId params,
and reorder JS imports to longest-to-shortest per project conventions.

* test: Strengthen OCR assertion and add filterFiles error-path test

Use toHaveBeenCalledWith for the OCR filtering test to verify exact
arguments after the OCR→context merge step. Add test for filterFiles
rejection to verify graceful degradation (logs error, returns original
tool_resources).

* fix: Correct import order in addedConvo.js and initialize.js

Sort by total line length descending: loadAddedAgent (91) before
filterFilesByAgentAccess (84), loadAgentTools (91) before
filterFilesByAgentAccess (84).

* test: Add unit tests for filterFilesByAgentAccess and hasAccessToFilesViaAgent

Cover every branch in permissions.js: ephemeral agent guard, missing
userId/agentId/files early returns, all-owned short-circuit, mixed
owned + non-owned with VIEW/no-VIEW, agent-not-found fail-closed,
author path scoped to attached files, EDIT gate on delete, DB error
fail-closed, and agent with no tool_resources.

* test: Cover file.user undefined/null in permissions spec

Files with no user field fall into the non-owned path and get run
through hasAccessToFilesViaAgent. Add two cases: attached file with
no user field is returned, unattached file with no user field is
excluded.
2026-03-15 23:02:36 -04:00