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

  1. Ensure the avatar URL passed in starts with `http://` or `https://` — validate the scheme on the client before submit.
  2. If the input is actually a base64 data URI, route it through saveBase64Image instead of the URL fetch path.
  3. If the input is a local file path, pass it as a File object (the File branch) rather than a `file://` URL.
  4. 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

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


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/fb3cdccff720eea5. Report an issue: GitHub.