{"record":{"id":"74b58cf97b191358","repo":"BookStackApp/BookStack","slug":"errors-http-ssr-url-no-match","errorCode":null,"errorMessage":"errors.http_ssr_url_no_match","messagePattern":"errors\\.http_ssr_url_no_match","errorType":"exception","errorClass":"HttpFetchException","httpStatus":null,"severity":"error","filePath":"app/Util/SsrUrlValidator.php","lineNumber":31,"sourceCode":" * protocol and host. It can optionally define a path prefix as part of the URL.\n * Wildcards, via a '*', can be used within these elements to match anything but a '/'.\n */\nclass SsrUrlValidator\n{\n    protected string $config;\n\n    public function __construct(?string $config = null)\n    {\n        $this->config = $config ?? config('app.ssr_hosts') ?? '';\n    }\n\n    /**\n     * @throws HttpFetchException\n     */\n    public function ensureAllowed(string $url): void\n    {\n        if (!$this->allowed($url)) {\n            throw new HttpFetchException(trans('errors.http_ssr_url_no_match'));\n        }\n    }\n\n    /**\n     * Check if the given URL is allowed by the configured SSR host values.\n     */\n    public function allowed(string $url): bool\n    {\n        $allowed = $this->getHostPatterns();\n\n        foreach ($allowed as $pattern) {\n            if ($this->urlMatchesPattern($url, $pattern)) {\n                return true;\n            }\n        }\n\n        return false;\n    }","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Util/SsrUrlValidator.php#L13-L49","documentation":"SsrUrlValidator::ensureAllowed() throws HttpFetchException with errors.http_ssr_url_no_match when the supplied URL does not match any configured allowed SSR host pattern. BookStack validates outbound URLs (used for fetching remote resources such as avatars) against the 'allow' host list to prevent SSRF attacks to internal networks.","triggerScenarios":"Any code path calling ensureAllowed($url) (avatar/image fetch flows) where $url's host fails the allowed($url) check against configured allowed hosts — e.g. hosts not whitelisted, or misparsed host values.","commonSituations":"Empty or overly narrow APP/SSR allowed-hosts configuration; hosts with different scheme/port/case not matching patterns; internal IPs (10.x, 169.254.x, localhost) blocked by default SSRF guards; environment migrations where the allowlist wasn't updated.","solutions":["Add the URL's host to the SSR allowed hosts configuration (e.g. via the allow-list settings/REGEX host patterns)","Verify allowed($url) matching logic: scheme, case, port and wildcards in your patterns","Catch HttpFetchException and surface the SSR-block message; do not retry the same URL","Audit requested URLs for redirects that land on non-allowed hosts"],"exampleFix":"// before\n(new SsrUrlValidator())->ensureAllowed('https://internal.example.com/avatar.png');\n// after\n// config: allowed hosts includes 'internal.example.com'\n(new SsrUrlValidator())->ensureAllowed('https://internal.example.com/avatar.png');","handlingStrategy":"validation","validationCode":"// Validate the URL host against your allowed SSR hosts before fetching\n$parsed = parse_url($url, PHP_URL_HOST);\n$allowed = ['example.com', 'cdn.example.com']; // mirror your configured SSR allowlist\nif (!in_array(strtolower((string) $parsed), $allowed, true)) {\n    throw new \\InvalidArgumentException(\"Host not allowed for SSR fetch: {$parsed}\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    (new \\BookStack\\Util\\SsrUrlValidator())->ensureAllowed($url);\n} catch (\\BookStack\\Exceptions\\HttpFetchException $e) {\n    abort(400, 'URL blocked by SSRF protection');\n}","preventionTips":["Keep the SSR allowed-hosts config in sync across environments","Include all CDN/avatar hosts in the allowlist","Avoid fetching user-supplied URLs pointing at internal IPs","Log blocked URLs to spot legitimate hosts missing from the allowlist"],"tags":["php","ssrf","security","configuration","bookstack"],"backgroundTag":"ssrf-url-blocked","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}