mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-04 23:30:19 +01:00
🛡️ fix: Implement TOCTOU-Safe SSRF Protection for Actions and MCP (#11722)
* refactor: better SSRF Protection in Action and Tool Services - Added `createSSRFSafeAgents` function to create HTTP/HTTPS agents that block connections to private/reserved IP addresses, enhancing security against SSRF attacks. - Updated `createActionTool` to accept a `useSSRFProtection` parameter, allowing the use of SSRF-safe agents during tool execution. - Modified `processRequiredActions` and `loadAgentTools` to utilize the new SSRF protection feature based on allowed domains configuration. - Introduced `resolveHostnameSSRF` function to validate resolved IPs against private ranges, preventing potential SSRF vulnerabilities. - Enhanced tests for domain resolution and private IP detection to ensure robust SSRF protection mechanisms are in place. * feat: Implement SSRF protection in MCP connections - Added `createSSRFSafeUndiciConnect` function to provide SSRF-safe DNS lookup options for undici agents. - Updated `MCPConnection`, `MCPConnectionFactory`, and `ConnectionsRepository` to include `useSSRFProtection` parameter, enabling SSRF protection based on server configuration. - Enhanced `MCPManager` and `UserConnectionManager` to utilize SSRF protection when establishing connections. - Updated tests to validate the integration of SSRF protection across various components, ensuring robust security measures are in place. * refactor: WS MCPConnection with SSRF protection and async transport construction - Added `resolveHostnameSSRF` to validate WebSocket hostnames against private IP addresses, enhancing SSRF protection. - Updated `constructTransport` method to be asynchronous, ensuring proper handling of SSRF checks before establishing connections. - Improved error handling for WebSocket transport to prevent connections to potentially unsafe addresses. * test: Enhance ActionRequest tests for SSRF-safe agent passthrough - Added tests to verify that httpAgent and httpsAgent are correctly passed to axios.create when provided in ActionRequest. - Included scenarios to ensure agents are not included when no options are specified. - Enhanced coverage for POST requests to confirm agent passthrough functionality. - Improved overall test robustness for SSRF protection in ActionRequest execution.
This commit is contained in:
parent
d6b6f191f7
commit
924be3b647
21 changed files with 567 additions and 53 deletions
|
|
@ -459,6 +459,82 @@ describe('ActionRequest', () => {
|
|||
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: invalid');
|
||||
});
|
||||
|
||||
describe('SSRF-safe agent passthrough', () => {
|
||||
beforeEach(() => {
|
||||
mockedAxios.get.mockResolvedValue({ data: { success: true } });
|
||||
mockedAxios.post.mockResolvedValue({ data: { success: true } });
|
||||
});
|
||||
|
||||
it('should pass httpAgent and httpsAgent to axios.create when provided', async () => {
|
||||
const mockHttpAgent = { keepAlive: true };
|
||||
const mockHttpsAgent = { keepAlive: true };
|
||||
|
||||
const actionRequest = new ActionRequest(
|
||||
'https://example.com',
|
||||
'/test',
|
||||
'GET',
|
||||
'testOp',
|
||||
false,
|
||||
'application/json',
|
||||
);
|
||||
const executor = actionRequest.createExecutor();
|
||||
executor.setParams({ key: 'value' });
|
||||
await executor.execute({ httpAgent: mockHttpAgent, httpsAgent: mockHttpsAgent });
|
||||
|
||||
expect(mockedAxios.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
httpAgent: mockHttpAgent,
|
||||
httpsAgent: mockHttpsAgent,
|
||||
maxRedirects: 0,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include agent keys when no options are provided', async () => {
|
||||
const actionRequest = new ActionRequest(
|
||||
'https://example.com',
|
||||
'/test',
|
||||
'GET',
|
||||
'testOp',
|
||||
false,
|
||||
'application/json',
|
||||
);
|
||||
const executor = actionRequest.createExecutor();
|
||||
executor.setParams({ key: 'value' });
|
||||
await executor.execute();
|
||||
|
||||
const createArg = mockedAxios.create.mock.calls[
|
||||
mockedAxios.create.mock.calls.length - 1
|
||||
][0] as Record<string, unknown>;
|
||||
expect(createArg).not.toHaveProperty('httpAgent');
|
||||
expect(createArg).not.toHaveProperty('httpsAgent');
|
||||
});
|
||||
|
||||
it('should pass agents through for POST requests', async () => {
|
||||
const mockAgent = { ssrf: true };
|
||||
|
||||
const actionRequest = new ActionRequest(
|
||||
'https://example.com',
|
||||
'/test',
|
||||
'POST',
|
||||
'testOp',
|
||||
false,
|
||||
'application/json',
|
||||
);
|
||||
const executor = actionRequest.createExecutor();
|
||||
executor.setParams({ body: 'data' });
|
||||
await executor.execute({ httpAgent: mockAgent, httpsAgent: mockAgent });
|
||||
|
||||
expect(mockedAxios.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
httpAgent: mockAgent,
|
||||
httpsAgent: mockAgent,
|
||||
}),
|
||||
);
|
||||
expect(mockedAxios.post).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ActionRequest Concurrent Execution', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ class RequestExecutor {
|
|||
return this;
|
||||
}
|
||||
|
||||
async execute() {
|
||||
async execute(options?: { httpAgent?: unknown; httpsAgent?: unknown }) {
|
||||
const url = createURL(this.config.domain, this.path);
|
||||
const headers: Record<string, string> = {
|
||||
...this.authHeaders,
|
||||
|
|
@ -300,10 +300,15 @@ class RequestExecutor {
|
|||
*
|
||||
* By setting maxRedirects: 0, we prevent this attack vector.
|
||||
* The action will receive the redirect response (3xx) instead of following it.
|
||||
*
|
||||
* SECURITY: When httpAgent/httpsAgent are provided (SSRF-safe agents), they validate
|
||||
* the DNS-resolved IP at TCP connect time, preventing TOCTOU DNS rebinding attacks.
|
||||
*/
|
||||
const axios = _axios.create({
|
||||
maxRedirects: 0,
|
||||
validateStatus: (status) => status >= 200 && status < 400, // Accept 3xx but don't follow
|
||||
validateStatus: (status) => status >= 200 && status < 400,
|
||||
...(options?.httpAgent != null ? { httpAgent: options.httpAgent } : {}),
|
||||
...(options?.httpsAgent != null ? { httpsAgent: options.httpsAgent } : {}),
|
||||
});
|
||||
|
||||
// Initialize separate containers for query and body parameters.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue