🛡️ 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:
Danny Avila 2026-02-11 22:09:58 -05:00 committed by GitHub
parent d6b6f191f7
commit 924be3b647
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 567 additions and 53 deletions

View file

@ -24,6 +24,7 @@ jest.mock('../connection');
const mockRegistryInstance = {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
shouldEnableSSRFProtection: jest.fn().mockReturnValue(false),
};
jest.mock('../registry/MCPServersRegistry', () => ({
@ -108,6 +109,7 @@ describe('ConnectionsRepository', () => {
{
serverName: 'server1',
serverConfig: mockServerConfigs.server1,
useSSRFProtection: false,
},
undefined,
);
@ -129,6 +131,7 @@ describe('ConnectionsRepository', () => {
{
serverName: 'server1',
serverConfig: mockServerConfigs.server1,
useSSRFProtection: false,
},
undefined,
);
@ -167,6 +170,7 @@ describe('ConnectionsRepository', () => {
{
serverName: 'server1',
serverConfig: configWithCachedAt,
useSSRFProtection: false,
},
undefined,
);

View file

@ -84,6 +84,7 @@ describe('MCPConnectionFactory', () => {
serverConfig: mockServerConfig,
userId: undefined,
oauthTokens: null,
useSSRFProtection: false,
});
expect(mockConnectionInstance.connect).toHaveBeenCalled();
});
@ -125,6 +126,7 @@ describe('MCPConnectionFactory', () => {
serverConfig: mockServerConfig,
userId: 'user123',
oauthTokens: mockTokens,
useSSRFProtection: false,
});
});
});
@ -184,6 +186,7 @@ describe('MCPConnectionFactory', () => {
serverConfig: mockServerConfig,
userId: 'user123',
oauthTokens: null,
useSSRFProtection: false,
});
expect(mockLogger.debug).toHaveBeenCalledWith(
expect.stringContaining('No existing tokens found or error loading tokens'),

View file

@ -33,6 +33,7 @@ const mockRegistryInstance = {
getServerConfig: jest.fn(),
getAllServerConfigs: jest.fn(),
getOAuthServers: jest.fn(),
shouldEnableSSRFProtection: jest.fn().mockReturnValue(false),
};
jest.mock('~/mcp/registry/MCPServersRegistry', () => ({