mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-24 03:09:26 +00:00
🧯 fix: enforce allowedProviders for discovered sub-agents
Addresses the third P1 codex finding on #12740: `discoverConnectedAgents` forwarded the caller's `endpointOption` verbatim into `initializeAgent`, but on the OpenAI-compat routes that option's `endpoint` is the primary agent's provider (e.g. `openai`), not `agents`. `initializeAgent` only enforces `allowedProviders` when `isAgentsEndpoint(endpointOption.endpoint)` is true, so handoff sub-agents silently bypassed the provider allowlist configured under `endpoints.agents.allowedProviders`. Override `endpointOption.endpoint` to `EModelEndpoint.agents` for every per-sub-agent init call. The primary agent still uses the caller's endpointOption as before — this only affects the BFS-loaded handoff targets. Regression test asserts the override.
This commit is contained in:
parent
e491a23a77
commit
7ec1ba246f
2 changed files with 50 additions and 2 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { Agent, GraphEdge } from 'librechat-data-provider';
|
||||
import type { Response } from 'express';
|
||||
import type { ServerRequest } from '~/types';
|
||||
|
|
@ -202,6 +203,39 @@ describe('discoverConnectedAgents', () => {
|
|||
expect(result.edges.map((e) => e.to)).toEqual(['B']);
|
||||
});
|
||||
|
||||
it('forces `endpoint: agents` on sub-agent init so allowedProviders is always enforced', async () => {
|
||||
const primaryConfig = makeConfig('A', [{ from: 'A', to: 'B', edgeType: 'handoff' }]);
|
||||
const getAgent = jest.fn(async () => makeAgent('B', []));
|
||||
const checkPermission = jest.fn().mockResolvedValue(true);
|
||||
|
||||
await discoverConnectedAgents(
|
||||
{
|
||||
req: makeReq(),
|
||||
res: makeRes(),
|
||||
primaryConfig,
|
||||
// Caller passes a non-agents endpoint (e.g. the OpenAI-compat
|
||||
// controllers do this — endpoint = primary provider)
|
||||
endpointOption: { endpoint: EModelEndpoint.openAI, model_parameters: {} },
|
||||
allowedProviders: new Set(['openai']),
|
||||
modelsConfig: { openai: ['gpt-4o'] },
|
||||
loadTools: jest.fn(),
|
||||
},
|
||||
{
|
||||
getAgent,
|
||||
checkPermission,
|
||||
logViolation: jest.fn(),
|
||||
db: {} as never,
|
||||
},
|
||||
);
|
||||
|
||||
// The helper must override `endpoint` to 'agents' so
|
||||
// `isAgentsEndpoint` is true and the allowedProviders guard in
|
||||
// `initializeAgent` actually runs for sub-agents.
|
||||
expect(mockInitializeAgent).toHaveBeenCalled();
|
||||
const initArgs = mockInitializeAgent.mock.calls[0][0];
|
||||
expect(initArgs.endpointOption.endpoint).toBe(EModelEndpoint.agents);
|
||||
});
|
||||
|
||||
it('passes the configured resourceType (e.g. REMOTE_AGENT) to checkPermission', async () => {
|
||||
const primaryConfig = makeConfig('A', [{ from: 'A', to: 'B', edgeType: 'handoff' }]);
|
||||
const getAgent = jest.fn(async () => makeAgent('B', []));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { logger } from '@librechat/data-schemas';
|
||||
import { ResourceType, PermissionBits } from 'librechat-data-provider';
|
||||
import { ResourceType, PermissionBits, EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { Agent, GraphEdge, TModelsConfig, TEndpointOption } from 'librechat-data-provider';
|
||||
import type { Response as ServerResponse } from 'express';
|
||||
import type { ServerRequest } from '~/types';
|
||||
|
|
@ -196,6 +196,20 @@ export async function discoverConnectedAgents(
|
|||
throw new Error(validation.error?.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force `endpoint: agents` on the per-sub-agent init call so
|
||||
* `initializeAgent`'s `isAgentsEndpoint`-gated `allowedProviders`
|
||||
* check always fires for handoff sub-agents, regardless of which
|
||||
* endpoint the caller entered through. Without this, the OpenAI-
|
||||
* compat routes (whose `endpointOption.endpoint` is the primary
|
||||
* provider, not `agents`) would silently bypass the provider
|
||||
* allowlist configured under `endpoints.agents.allowedProviders`.
|
||||
*/
|
||||
const subAgentEndpointOption: Partial<TEndpointOption> = {
|
||||
...(endpointOption ?? {}),
|
||||
endpoint: EModelEndpoint.agents,
|
||||
};
|
||||
|
||||
const config = await initializeAgent(
|
||||
{
|
||||
req,
|
||||
|
|
@ -205,7 +219,7 @@ export async function discoverConnectedAgents(
|
|||
requestFiles,
|
||||
conversationId,
|
||||
parentMessageId,
|
||||
endpointOption,
|
||||
endpointOption: subAgentEndpointOption,
|
||||
allowedProviders,
|
||||
},
|
||||
db,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue