2024-12-12 12:52:42 -05:00
|
|
|
/**
|
2025-09-27 21:20:19 -04:00
|
|
|
* @param email
|
|
|
|
|
* @param allowedDomains
|
2024-12-12 12:52:42 -05:00
|
|
|
*/
|
2025-09-27 21:20:19 -04:00
|
|
|
export function isEmailDomainAllowed(email: string, allowedDomains?: string[] | null): boolean {
|
2025-10-29 12:57:43 -04:00
|
|
|
/** If no domain restrictions are configured, allow all */
|
|
|
|
|
if (!allowedDomains || !Array.isArray(allowedDomains) || !allowedDomains.length) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** If restrictions exist, validate email format */
|
2024-12-12 12:52:42 -05:00
|
|
|
if (!email) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-27 21:20:19 -04:00
|
|
|
const domain = email.split('@')[1]?.toLowerCase();
|
2024-12-12 12:52:42 -05:00
|
|
|
|
|
|
|
|
if (!domain) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-27 21:20:19 -04:00
|
|
|
return allowedDomains.some((allowedDomain) => allowedDomain?.toLowerCase() === domain);
|
2024-12-12 12:52:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
* SSRF Protection: Checks if a hostname/IP is a potentially dangerous internal target.
|
|
|
|
|
* Blocks private IPs, localhost, cloud metadata IPs, and common internal hostnames.
|
|
|
|
|
* @param hostname - The hostname or IP to check
|
|
|
|
|
* @returns true if the target is blocked (SSRF risk), false if safe
|
2024-12-12 12:52:42 -05:00
|
|
|
*/
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
export function isSSRFTarget(hostname: string): boolean {
|
|
|
|
|
const normalizedHost = hostname.toLowerCase().trim();
|
|
|
|
|
|
|
|
|
|
// Block localhost variations
|
|
|
|
|
if (
|
|
|
|
|
normalizedHost === 'localhost' ||
|
|
|
|
|
normalizedHost === 'localhost.localdomain' ||
|
|
|
|
|
normalizedHost.endsWith('.localhost')
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if it's an IP address and block private/internal ranges
|
|
|
|
|
const ipv4Match = normalizedHost.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
|
|
|
if (ipv4Match) {
|
|
|
|
|
const [, a, b, c] = ipv4Match.map(Number);
|
|
|
|
|
|
|
|
|
|
// 127.0.0.0/8 - Loopback
|
|
|
|
|
if (a === 127) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 10.0.0.0/8 - Private
|
|
|
|
|
if (a === 10) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 172.16.0.0/12 - Private (172.16.x.x - 172.31.x.x)
|
|
|
|
|
if (a === 172 && b >= 16 && b <= 31) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 192.168.0.0/16 - Private
|
|
|
|
|
if (a === 192 && b === 168) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 169.254.0.0/16 - Link-local (includes cloud metadata 169.254.169.254)
|
|
|
|
|
if (a === 169 && b === 254) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 0.0.0.0 - Special
|
|
|
|
|
if (a === 0 && b === 0 && c === 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IPv6 loopback and private ranges
|
|
|
|
|
const ipv6Normalized = normalizedHost.replace(/^\[|\]$/g, ''); // Remove brackets if present
|
|
|
|
|
if (
|
|
|
|
|
ipv6Normalized === '::1' ||
|
|
|
|
|
ipv6Normalized === '::' ||
|
|
|
|
|
ipv6Normalized.startsWith('fc') || // fc00::/7 - Unique local
|
|
|
|
|
ipv6Normalized.startsWith('fd') || // fd00::/8 - Unique local
|
|
|
|
|
ipv6Normalized.startsWith('fe80') // fe80::/10 - Link-local
|
|
|
|
|
) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Block common internal Docker/Kubernetes service names
|
|
|
|
|
const internalHostnames = [
|
|
|
|
|
'rag_api',
|
|
|
|
|
'rag-api',
|
|
|
|
|
'api',
|
|
|
|
|
'redis',
|
|
|
|
|
'mongodb',
|
|
|
|
|
'mongo',
|
|
|
|
|
'postgres',
|
|
|
|
|
'postgresql',
|
|
|
|
|
'mysql',
|
|
|
|
|
'database',
|
|
|
|
|
'db',
|
|
|
|
|
'elasticsearch',
|
|
|
|
|
'kibana',
|
|
|
|
|
'grafana',
|
|
|
|
|
'prometheus',
|
|
|
|
|
'rabbitmq',
|
|
|
|
|
'kafka',
|
|
|
|
|
'zookeeper',
|
|
|
|
|
'consul',
|
|
|
|
|
'vault',
|
|
|
|
|
'etcd',
|
|
|
|
|
'minio',
|
|
|
|
|
'internal',
|
|
|
|
|
'backend',
|
|
|
|
|
'metadata', // Common metadata service name
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (internalHostnames.includes(normalizedHost)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Block .internal and .local TLDs (common in internal networks)
|
|
|
|
|
if (normalizedHost.endsWith('.internal') || normalizedHost.endsWith('.local')) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parsed domain specification including protocol and port constraints.
|
|
|
|
|
*/
|
|
|
|
|
interface ParsedDomainSpec {
|
|
|
|
|
hostname: string;
|
|
|
|
|
protocol?: 'http:' | 'https:' | null; // null means any protocol
|
|
|
|
|
port?: string | null; // null means any port
|
|
|
|
|
explicitPort: boolean; // true if port was explicitly specified in original string
|
|
|
|
|
isWildcard: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses a domain specification into its components.
|
|
|
|
|
* Supports formats:
|
|
|
|
|
* - `example.com` (any protocol, any port)
|
|
|
|
|
* - `https://example.com` (https only, any port)
|
|
|
|
|
* - `https://example.com:443` (https only, port 443)
|
|
|
|
|
* - `*.example.com` (wildcard subdomain)
|
|
|
|
|
* @param domain - Domain specification string
|
|
|
|
|
* @returns ParsedDomainSpec or null if invalid
|
|
|
|
|
*/
|
|
|
|
|
function parseDomainSpec(domain: string): ParsedDomainSpec | null {
|
2024-12-12 12:52:42 -05:00
|
|
|
try {
|
|
|
|
|
let normalizedDomain = domain.toLowerCase().trim();
|
|
|
|
|
|
|
|
|
|
// Early return for obviously invalid formats
|
|
|
|
|
if (normalizedDomain === 'http://' || normalizedDomain === 'https://') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
// Check for wildcard prefix before parsing
|
|
|
|
|
const isWildcard = normalizedDomain.startsWith('*.');
|
|
|
|
|
|
|
|
|
|
// Check if it has a protocol
|
|
|
|
|
const hasProtocol =
|
|
|
|
|
normalizedDomain.startsWith('http://') || normalizedDomain.startsWith('https://');
|
|
|
|
|
|
|
|
|
|
// Check if port was explicitly specified (e.g., :443, :8080)
|
|
|
|
|
// Need to check before URL parsing because URL normalizes default ports
|
|
|
|
|
const portMatch = normalizedDomain.match(/:(\d+)(\/|$|\?)/);
|
|
|
|
|
const explicitPort = portMatch !== null;
|
|
|
|
|
const explicitPortValue = portMatch ? portMatch[1] : null;
|
|
|
|
|
|
|
|
|
|
// If no protocol, add one temporarily for URL parsing
|
|
|
|
|
if (!hasProtocol) {
|
2024-12-12 12:52:42 -05:00
|
|
|
normalizedDomain = `https://${normalizedDomain}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = new URL(normalizedDomain);
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
|
2024-12-12 12:52:42 -05:00
|
|
|
// Additional validation that hostname isn't just protocol
|
|
|
|
|
if (!url.hostname || url.hostname === 'http:' || url.hostname === 'https:') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
const hostname = url.hostname.replace(/^www\./i, '');
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
hostname,
|
|
|
|
|
protocol: hasProtocol ? (url.protocol as 'http:' | 'https:') : null,
|
|
|
|
|
// Use the explicitly specified port, or null if no port was specified
|
|
|
|
|
port: explicitPort ? explicitPortValue : null,
|
|
|
|
|
explicitPort,
|
|
|
|
|
isWildcard,
|
|
|
|
|
};
|
2024-12-12 12:52:42 -05:00
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
* Checks if hostname matches an allowed pattern (supports wildcards).
|
|
|
|
|
*/
|
|
|
|
|
function hostnameMatches(inputHostname: string, allowedSpec: ParsedDomainSpec): boolean {
|
|
|
|
|
if (allowedSpec.isWildcard) {
|
|
|
|
|
// Extract base domain from wildcard (e.g., "*.example.com" -> "example.com")
|
|
|
|
|
const baseDomain = allowedSpec.hostname.replace(/^\*\./, '');
|
|
|
|
|
return inputHostname === baseDomain || inputHostname.endsWith(`.${baseDomain}`);
|
|
|
|
|
}
|
|
|
|
|
return inputHostname === allowedSpec.hostname;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if the given domain is allowed.
|
|
|
|
|
* SECURITY: When no allowedDomains is configured, blocks SSRF-prone targets
|
|
|
|
|
* (private IPs, localhost, metadata services) to prevent attacks.
|
|
|
|
|
* When allowedDomains IS configured, admins can explicitly allow internal targets if needed.
|
|
|
|
|
*
|
|
|
|
|
* Supports protocol and port restrictions in allowedDomains:
|
|
|
|
|
* - `example.com` - allows any protocol/port
|
|
|
|
|
* - `https://example.com` - allows only HTTPS on default port
|
|
|
|
|
* - `https://example.com:8443` - allows only HTTPS on port 8443
|
|
|
|
|
*
|
|
|
|
|
* @param domain - The domain to check (can include protocol/port)
|
|
|
|
|
* @param allowedDomains - List of allowed domain patterns
|
2024-12-12 12:52:42 -05:00
|
|
|
*/
|
2025-09-27 21:20:19 -04:00
|
|
|
export async function isActionDomainAllowed(
|
|
|
|
|
domain?: string | null,
|
|
|
|
|
allowedDomains?: string[] | null,
|
|
|
|
|
): Promise<boolean> {
|
2024-12-12 12:52:42 -05:00
|
|
|
if (!domain || typeof domain !== 'string') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
const inputSpec = parseDomainSpec(domain);
|
|
|
|
|
if (!inputSpec) {
|
|
|
|
|
return false;
|
2024-12-12 12:52:42 -05:00
|
|
|
}
|
|
|
|
|
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
/** If no domain restrictions configured, block SSRF targets but allow all else */
|
|
|
|
|
if (!Array.isArray(allowedDomains) || !allowedDomains.length) {
|
|
|
|
|
/** SECURITY: Block SSRF-prone targets when no allowlist is configured */
|
|
|
|
|
if (isSSRFTarget(inputSpec.hostname)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2024-12-12 12:52:42 -05:00
|
|
|
}
|
|
|
|
|
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
/** When allowedDomains is configured, check against the list with protocol/port matching */
|
2024-12-12 12:52:42 -05:00
|
|
|
for (const allowedDomain of allowedDomains) {
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
const allowedSpec = parseDomainSpec(allowedDomain);
|
|
|
|
|
if (!allowedSpec) {
|
2024-12-12 12:52:42 -05:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
// Check hostname match (with wildcard support)
|
|
|
|
|
if (!hostnameMatches(inputSpec.hostname, allowedSpec)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If allowedSpec has protocol restriction, input must match
|
|
|
|
|
if (allowedSpec.protocol !== null) {
|
|
|
|
|
// Input must have protocol specified to match a protocol-restricted rule
|
|
|
|
|
if (inputSpec.protocol === null || inputSpec.protocol !== allowedSpec.protocol) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If allowedSpec has explicit port restriction, input must have matching explicit port
|
|
|
|
|
if (allowedSpec.explicitPort) {
|
|
|
|
|
// Input must also have an explicit port that matches
|
|
|
|
|
if (!inputSpec.explicitPort || inputSpec.port !== allowedSpec.port) {
|
|
|
|
|
continue;
|
2024-12-12 12:52:42 -05:00
|
|
|
}
|
|
|
|
|
}
|
🔒 fix: Better SSRF protection for Actions (#11143)
Addresses Server-Side Request Forgery vulnerability that allowed authenticated
attackers to interact with arbitrary internal/external HTTP services via the
Actions feature, including the internal RAG API, localhost services, and cloud
metadata endpoints.
## Security Changes
### 1. SSRF Target Blocklist (when allowedDomains is empty)
- Block localhost and loopback addresses (127.0.0.0/8, ::1)
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block link-local/cloud metadata IPs (169.254.0.0/16)
- Block common internal Docker/Kubernetes service names (rag_api, redis, mongo, etc.)
- Block .internal and .local TLDs
Admins can still explicitly allow internal targets by adding them to
`actions.allowedDomains` in librechat.yaml.
### 2. Protocol and Port Restrictions
Enhanced `allowedDomains` to support protocol and port constraints:
- `example.com` - any protocol, any port (existing behavior)
- `https://example.com` - HTTPS only, any port
- `https://api.example.com:8443` - HTTPS only, port 8443 only
This allows admins to restrict actions to specific protocols/ports, preventing
attackers from accessing unintended services on allowed domains.
### 3. Redirect-based SSRF Prevention
Disabled automatic redirect following in action HTTP requests (`maxRedirects: 0`).
This prevents attackers from bypassing domain restrictions by:
1. Pointing action to allowed external domain
2. External domain redirects to internal service (e.g., 127.0.0.1)
3. Server follows redirect and accesses internal service
## Files Changed
- packages/api/src/auth/domain.ts: Added isSSRFTarget(), parseDomainSpec(),
updated isActionDomainAllowed() with protocol/port matching
- packages/api/src/auth/domain.spec.ts: Added tests for SSRF protection,
protocol restrictions, and port restrictions
- packages/data-provider/src/actions.ts: Added maxRedirects: 0 to axios config
## Configuration Example
# librechat.yaml
actions:
allowedDomains:
- "https://api.example.com" # HTTPS only
- "https://api.example.com:8443" # HTTPS + specific port
- "http://localhost:3000" # Admin override for local devRef: SBA-ADV-20251205-02
CVSSv3: 9.1 (Critical) - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:L
2025-12-29 15:09:55 -05:00
|
|
|
|
|
|
|
|
// All specified constraints matched
|
|
|
|
|
return true;
|
2024-12-12 12:52:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2025-12-18 19:57:49 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extracts domain from MCP server config URL.
|
|
|
|
|
* Returns null for stdio transports (no URL) or invalid URLs.
|
|
|
|
|
* @param config - MCP server configuration (accepts any config with optional url field)
|
|
|
|
|
*/
|
|
|
|
|
export function extractMCPServerDomain(config: Record<string, unknown>): string | null {
|
|
|
|
|
const url = config.url;
|
|
|
|
|
// Stdio transports don't have URLs - always allowed
|
|
|
|
|
if (!url || typeof url !== 'string') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const parsedUrl = new URL(url);
|
|
|
|
|
return parsedUrl.hostname.replace(/^www\./i, '');
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validates MCP server domain against allowedDomains.
|
|
|
|
|
* Reuses isActionDomainAllowed for consistent validation logic.
|
|
|
|
|
* Stdio transports (no URL) are always allowed.
|
|
|
|
|
* @param config - MCP server configuration with optional url field
|
|
|
|
|
* @param allowedDomains - List of allowed domains (with wildcard support)
|
|
|
|
|
*/
|
|
|
|
|
export async function isMCPDomainAllowed(
|
|
|
|
|
config: Record<string, unknown>,
|
|
|
|
|
allowedDomains?: string[] | null,
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
const domain = extractMCPServerDomain(config);
|
|
|
|
|
|
|
|
|
|
// Stdio transports don't have domains - always allowed
|
|
|
|
|
if (!domain) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reuse existing validation logic (includes wildcard support)
|
|
|
|
|
return isActionDomainAllowed(domain, allowedDomains);
|
|
|
|
|
}
|