appwrite/appwrite · error · Appwrite\Extend\Exception

avatar_remote_url_failed

avatar_remote_url_failed

Error message

Failed to fetch favicon from the requested URL.

What it means

First SSRF guard in GET /v1/avatars/screenshots: the URL host is checked with `Utopia\Domains\Domain::isKnown()` (skipped only for IP-literal hosts) and an unknown domain throws avatar_remote_url_failed (whose default message is the shared avatars text 'Failed to fetch favicon...'). 'Known' means the registrable domain resolves against the bundled public-suffix data — hostnames with an unrecognized or missing TLD, or raw internal names, fail here.

Source

Thrown at src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php:105

            ->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg', enum: new Enum(name: 'ImageFormat'))
            ->inject('response')
            ->inject('usage')
            ->callback($this->action(...));
    }

    public function action(string $url, array $headers, int $viewportWidth, int $viewportHeight, float $scale, string $theme, string $userAgent, bool $fullpage, string $locale, string $timezone, float $latitude, float $longitude, float $accuracy, bool $touch, array $permissions, int $sleep, int $width, int $height, int $quality, string $output, Response $response, Context $usage)
    {
        if (!\extension_loaded('imagick')) {
            throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
        }

        $host = \parse_url($url, PHP_URL_HOST) ?? '';

        $isIpLiteral = \filter_var(\trim($host, '[]'), FILTER_VALIDATE_IP) !== false;
        if (!$isIpLiteral) {
            $domain = new Domain($host);
            if (!$domain->isKnown()) {
                throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
            }
        }

        $hostnameValidator = new PublicHostname();
        if (!$hostnameValidator->isValid($host)) {
            throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, $hostnameValidator->getDescription());
        }

        $client = new Client();
        $client->setTimeout(30 * 1000); // 30 seconds
        $client->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);

        // Convert indexed array to empty array (should not happen due to Assoc validator)
        if (count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) {
            $headers = [];
        }

        // Create a new object to ensure proper JSON serialization

View on GitHub (pinned to feb9831e60)

Solutions

  1. Use a URL whose host has a real, public TLD (https://staging.example.com instead of http://staging).
  2. If you must screenshot an internal app, expose it under a real domain or use an IP-literal host (IP literals skip the isKnown check but still face the PublicHostname check).
  3. Keep the utopia-php/domains package updated so newly added TLDs are recognized.

Example fix

// before
await avatars.getScreenshot('http://intranet-portal:3000');

// after
await avatars.getScreenshot('https://portal.example.com');
Defensive patterns

Strategy: validation

Validate before calling

function hasKnownTld(host: string): boolean {
  const parts = host.toLowerCase().split('.');
  return parts.length >= 2 && parts[parts.length - 1].length >= 2;
}

Try / catch

try { await avatars.getScreenshot(url); } catch (e) { if (e.code === 'avatar_remote_url_failed') { /* recheck host/TLD, fall back to public domain */ } }

Prevention

When it happens

Trigger: GET /v1/avatars/screenshots?url=http://my-intranet-app:3000 or ?url=https://example.invalid — any hostname whose TLD is not in the public suffix list, or a bare hostname like `http://localapp` without a dot/TLD.

Common situations: Trying to screenshot staging servers on internal hostnames (e.g. http://staging, .local, .internal); typo'd TLDs (.in instead of .in.); test domains on non-existent TLDs; fresh Utopia Domains data missing a newly introduced TLD.

Related errors


AI-assisted analysis of appwrite/appwrite@feb9831e60 (2026-08-18). Data as JSON: /api/errors/044adc72bf6cbb9c. Report an issue: GitHub.