danny-avila/LibreChat · warning
Refusing to fetch avatar over ${parsed.protocol}
Error message
Refusing to fetch avatar over ${parsed.protocol} What it means
Thrown by fetchAvatarBuffer when the parsed avatar URL uses a protocol outside ALLOWED_AVATAR_PROTOCOLS (only `http:` and `https:`). This is an SSRF guard: it prevents the server from attempting fetches over schemes like `file:`, `ftp:`, `data:`, or `gopher:` that node-fetch might otherwise route or that an attacker could use to read local resources. The check happens before any network I/O, immediately after `new URL(input)` succeeds.
Source
Thrown at api/server/services/Files/images/avatar.js:37
* blocks resolution to private/loopback/link-local IPs at TCP connect time,
* refuses to follow redirects to prevent post-validation rebinding, and caps
* the response body so a hostile payload cannot exhaust memory before
* `sharp()` rejects it.
*
* Per-call agent construction is intentional: avatar fetches are infrequent
* (once per social login per user) and pooling adds complexity without a
* measurable benefit on this path. If this ever becomes a hot path, hoist
* the agents to module scope.
*/
async function fetchAvatarBuffer(input, fetchOptions = {}) {
let parsed;
try {
parsed = new URL(input);
} catch {
throw new Error('Invalid avatar URL');
}
if (!ALLOWED_AVATAR_PROTOCOLS.has(parsed.protocol)) {
throw new Error(`Refusing to fetch avatar over ${parsed.protocol}`);
}
const { httpAgent, httpsAgent } = createSSRFSafeAgents();
/**
* `node-fetch` v2's `timeout` is the total request budget (request initiation
* through full body receipt), not a TCP-connect-only timeout. That is the
* stronger of the two for this path — bounds total slow-loris exposure.
*/
const response = await fetch(parsed.href, {
headers: fetchOptions.headers,
agent: (urlObj) => (urlObj.protocol === 'https:' ? httpsAgent : httpAgent),
redirect: 'error',
timeout: 5000,
size: MAX_AVATAR_BYTES,
});
if (!response.ok) {
throw new Error(`Failed to fetch image from URL. Status: ${response.status}`);View on GitHub (pinned to 5ff282f900)
Solutions
- Ensure the avatar URL passed in starts with `http://` or `https://` — validate the scheme on the client before submit.
- If the input is actually a base64 data URI, route it through saveBase64Image instead of the URL fetch path.
- If the input is a local file path, pass it as a File object (the File branch) rather than a `file://` URL.
- Sanitize stored `picture` values from OAuth providers before persisting them to the user record.
Example fix
// before
await uploadAvatar({ userId, input: 'file:///tmp/avatar.png' });
// after (local file -> File branch)
await uploadAvatar({ userId, input: new File('/tmp/avatar.png') });
// or enforce https in the caller
const url = new URL(raw);
if (url.protocol !== 'https:') throw new Error('Avatar URL must be https'); Defensive patterns
Strategy: validation
Validate before calling
function isSafeAvatarUrl(input) {
if (typeof input !== 'string') return false;
let u;
try { u = new URL(input); } catch { return false; }
return u.protocol === 'http:' || u.protocol === 'https:';
}
// before uploadAvatar:
if (typeof input === 'string' && !isSafeAvatarUrl(input)) {
throw new Error('Avatar URL must be http(s)');
} Type guard
function isHttpUrl(s) {
if (typeof s !== 'string') return false;
try { return ['http:', 'https:'].includes(new URL(s).protocol); }
catch { return false; }
} Try / catch
try {
await uploadAvatar({ userId, input });
} catch (e) {
if (/Refusing to fetch avatar over/.test(e.message)) {
return res.status(400).json({ error: 'Avatar URL must use http or https' });
}
throw e;
} Prevention
- Validate the avatar URL scheme in the OAuth/social callback before persisting `picture`.
- On the frontend, reject non-http(s) avatar URLs at the input field.
- Treat base64 data URIs as base64 input, not as URL input.
When it happens
Trigger: A user's `picture` field (social login profile photo or manually set avatar URL) is a string that parses as a URL but whose scheme is not `http:` or `https:`. Examples: `file:///etc/passwd`, `ftp://host/img.png`, `data:image/png;base64,...`, or a malformed value like `gopher://x`. Passing such a value to uploadAvatar's URL branch hits this branch.
Common situations: Social providers returning non-HTTP avatar URLs; misconfigured test fixtures using `file://` paths; a frontend that accepts a user-pasted avatar URL without client-side scheme validation; data-URI uploads routed through the URL path instead of the base64 path.
Related errors
- Invalid avatar URL
- Avatar response too large: ${buffer.length} bytes
- User ID not found in blob path
- Invalid file path
- Path traversal detected in filename
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/fb3cdccff720eea5.
Report an issue: GitHub.