🔒 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
This commit is contained in:
Danny Avila 2025-12-29 15:09:55 -05:00 committed by GitHub
parent a59bab4dc7
commit 4fd09946d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 445 additions and 32 deletions

View file

@ -290,7 +290,21 @@ class RequestExecutor {
...(this.config.contentType ? { 'Content-Type': this.config.contentType } : {}),
};
const method = this.config.method.toLowerCase();
const axios = _axios.create();
/**
* SECURITY: Disable automatic redirects to prevent SSRF bypass.
* Attackers could use redirects to access internal services:
* 1. Set action URL to allowed external domain
* 2. External domain redirects to internal service (e.g., 127.0.0.1, rag_api)
* 3. Without this protection, axios would follow the redirect
*
* By setting maxRedirects: 0, we prevent this attack vector.
* The action will receive the redirect response (3xx) instead of following it.
*/
const axios = _axios.create({
maxRedirects: 0,
validateStatus: (status) => status >= 200 && status < 400, // Accept 3xx but don't follow
});
// Initialize separate containers for query and body parameters.
const queryParams: Record<string, unknown> = {};