danny-avila/LibreChat · error

Invalid avatar URL

Error message

Invalid avatar URL

What it means

Thrown by fetchAvatarBuffer when the input string cannot be parsed by the URL constructor. This is the first validation gate in the avatar fetch pipeline: if new URL(input) throws, the input is not a valid absolute URL (it may be a relative path, a bare string, undefined coerced to string, or a malformed URL with invalid syntax). The error fires before any protocol or SSRF checks.

Source

Thrown at api/server/services/Files/images/avatar.js:34

/**
 * Fetches an image URL with SSRF protection: rejects non-http(s) schemes,
 * 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,
  });

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Validate the avatar URL is an absolute http(s) URL before calling resizeAvatar — use a try/catch around new URL() or a regex.
  2. Handle null/undefined/empty picture fields from OAuth providers by falling back to a default avatar.
  3. If the provider returns a relative URL, prepend the provider's base domain (e.g., 'https://avatars.githubusercontent.com' for GitHub).
  4. Add a type check: if typeof input !== 'string', route to the Buffer or File branch instead of attempting URL parsing.

Example fix

// before
const buffer = await resizeAvatar({ userId, input: profile.picture });

// after — validate and fallback
const avatarUrl = profile.picture;
if (typeof avatarUrl === 'string' && /^https?:\/\//.test(avatarUrl)) {
  const buffer = await resizeAvatar({ userId, input: avatarUrl });
} else {
  const buffer = await resizeAvatar({ userId, input: DEFAULT_AVATAR_URL });
}
Defensive patterns

Strategy: validation

Validate before calling

function isValidAvatarUrl(input: unknown): input is string {
  if (typeof input !== 'string') return false;
  try {
    const parsed = new URL(input);
    return parsed.protocol === 'http:' || parsed.protocol === 'https:';
  } catch {
    return false;
  }
}

if (!isValidAvatarUrl(picture)) {
  picture = DEFAULT_AVATAR_URL;
}

Type guard

function isValidAvatarUrl(input: unknown): input is string {
  if (typeof input !== 'string') return false;
  try {
    const parsed = new URL(input);
    return parsed.protocol === 'http:' || parsed.protocol === 'https:';
  } catch {
    return false;
  }
}

Try / catch

try {
  const buffer = await resizeAvatar({ userId, input: picture });
} catch (error) {
  if (error.message === 'Invalid avatar URL') {
    // fallback to default avatar
    const buffer = await resizeAvatar({ userId, input: DEFAULT_AVATAR_URL });
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling fetchAvatarBuffer(input) or resizeAvatar({ input }) where input is a string that the URL constructor rejects — e.g., '/path/to/avatar.png' (relative), '' (empty), 'not-a-url', 'ftp://...' (would pass URL parse but fail protocol check), or a value with invalid characters.

Common situations: A social login provider (Google, GitHub, Facebook) returns a relative avatar URL or an empty/null picture field. Or the user's profile picture field in the database is null/undefined and gets coerced to the string 'undefined'. Or a frontend bug sends a relative path instead of an absolute URL. Or the input is a Buffer or File but typeof input !== 'string' is true and the code path incorrectly reaches the string branch.

Related errors


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