Validation and precheck
Reject unsafe URLs when a user submits them. The rule passes when the guard lets the URL through (SafeFetch::allows()) and fails for anything that is not a string:
use RoundlyConsulting\SafeFetch\Rules\SafeUrl;
$request->validate(['feed' => ['required', 'string', 'max:2048', new SafeUrl]]);
$request->validate(['feed' => ['required', 'string', 'max:2048', 'safe_url']]);
$request->validate(['hook' => ['required', new SafeUrl(SafeFetch::policy()->allowHosts(['*.partner.example']))]]);
$request->validate(['hook' => ['required', SafeUrl::withoutLookup()]]); // no DNS lookup
$request->validate(['dev_hook' => ['required', 'safe_url:http']]); // http as well- One message whatever the reason — safe-fetch::validation.safe_url, “The :attribute must be a URL this application is allowed to fetch.” (Slovak too) — so the form never tells a user which internal names exist. A message you give the rule yourself wins.
- The string rule takes one parameter, http (safe_url:http → policy()->allowHttp()). Any other parameter throws InvalidArgumentException rather than being ignored.
- new SafeUrl(?DestinationPolicy $policy = null, bool $lookup = true) takes a policy of its own; call SafeFetch::vet() when you need the exact reason.
- The URL is judged again when it is fetched — DNS can change in between.
Without the DNS lookup
SafeUrl::withoutLookup(?DestinationPolicy $policy = null) judges through SafeFetch::precheck() instead: every rule except the lookup — the shape, scheme, port, blocked and app hosts, the allowlist, localhost, and an address literal in full. A form neither waits on DNS nor fails while a name does not resolve yet. Use it when a URL is stored now and called later:
$request->validate(['endpoint' => ['required', 'string', SafeUrl::withoutLookup($policy)]]);
$stored = SafeFetch::precheck($request->input('endpoint'), $policy)->toString();precheck() returns the CanonicalUrl or throws DestinationRejected, and dispatches DestinationRefused with via precheck. A name it passes can still resolve privately — the guard judges it in full on every request. Use SafeFetch::vet() instead when the URL must resolve now.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.