{"record":{"id":"044adc72bf6cbb9c","repo":"appwrite/appwrite","slug":"avatar-remote-url-failed-044adc","errorCode":"avatar_remote_url_failed","errorMessage":"Failed to fetch favicon from the requested URL.","messagePattern":"Failed to fetch favicon from the requested URL\\.","errorType":"exception","errorClass":"Appwrite\\Extend\\Exception","httpStatus":404,"severity":"error","filePath":"src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php","lineNumber":105,"sourceCode":"            ->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'))\n            ->inject('response')\n            ->inject('usage')\n            ->callback($this->action(...));\n    }\n\n    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)\n    {\n        if (!\\extension_loaded('imagick')) {\n            throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');\n        }\n\n        $host = \\parse_url($url, PHP_URL_HOST) ?? '';\n\n        $isIpLiteral = \\filter_var(\\trim($host, '[]'), FILTER_VALIDATE_IP) !== false;\n        if (!$isIpLiteral) {\n            $domain = new Domain($host);\n            if (!$domain->isKnown()) {\n                throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);\n            }\n        }\n\n        $hostnameValidator = new PublicHostname();\n        if (!$hostnameValidator->isValid($host)) {\n            throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED, $hostnameValidator->getDescription());\n        }\n\n        $client = new Client();\n        $client->setTimeout(30 * 1000); // 30 seconds\n        $client->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON);\n\n        // Convert indexed array to empty array (should not happen due to Assoc validator)\n        if (count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) {\n            $headers = [];\n        }\n\n        // Create a new object to ensure proper JSON serialization","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/appwrite/appwrite/blob/feb9831e60c12102a2c75891715c7401d0f9a89c/src/Appwrite/Platform/Modules/Avatars/Http/Screenshots/Get.php#L87-L123","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use a URL whose host has a real, public TLD (https://staging.example.com instead of http://staging).","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).","Keep the utopia-php/domains package updated so newly added TLDs are recognized."],"exampleFix":"// before\nawait avatars.getScreenshot('http://intranet-portal:3000');\n\n// after\nawait avatars.getScreenshot('https://portal.example.com');","handlingStrategy":"validation","validationCode":"function hasKnownTld(host: string): boolean {\n  const parts = host.toLowerCase().split('.');\n  return parts.length >= 2 && parts[parts.length - 1].length >= 2;\n}","typeGuard":null,"tryCatchPattern":"try { await avatars.getScreenshot(url); } catch (e) { if (e.code === 'avatar_remote_url_failed') { /* recheck host/TLD, fall back to public domain */ } }","preventionTips":["Only screenshot URLs under real public TLDs","Expose internal apps via tunnels under your real domain","Keep the utopia domains data fresh via upgrades"],"tags":["screenshots","domain-validation","ssrf-guard","public-suffix"],"backgroundTag":"unknown-domain-tld","analyzedSha":"feb9831e60c12102a2c75891715c7401d0f9a89c","analyzedAt":"2026-08-18T21:34:56.994Z","contentChangedAt":"2026-08-18T21:34:56.994Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}