NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Open source

Safe Fetch for Laravel

Install
composer require roundly-consulting/safe-fetch-for-laravel
Requires: PHP ^8.4 · Laravel ^13.0

Overview

SSRF protection for Laravel’s HTTP client. Make server-side requests to URLs you do not control — webhook targets, link previews, avatar, feed and import URLs — without reaching your own network. Every URL is parsed strictly and rebuilt, every address its host resolves to must be public, and the connection is pinned to exactly those addresses, redirects included. MIT-licensed, no database, built only on Laravel’s HTTP client and Roundly’s enums and package-toolkit.

What you get

One call on any request

Http::safe() guards the Laravel HTTP calls you already write — tokens, retries, pools and other middleware keep working. SafeFetch::guard() and http() do the same without the macro.

Internal destinations refused

Private, loopback, link-local, carrier-grade NAT and cloud-metadata addresses — AWS, Google Cloud, Azure, Alibaba, Oracle, OpenStack — in every spelling: numeric forms, IPv4 inside IPv6, names that point inside.

DNS pinned, connection checked

The name is resolved once, every address judged, curl pinned to exactly those with no proxy and no reused connection, and the connected address checked before a byte is sent and again afterwards.

Redirects under control

Off by default. When on, every hop is vetted and pinned again, with a hop limit, no https to http, credentials kept to the first origin and only the headers you name forwarded.

Capped fetch and download

fetch() reads a GET into memory, download() streams it to a file swapped in only on success — both with a byte cap counted after decoding, one time budget and errors that say whether a retry can help.

Validation, events and a health check

The SafeUrl rule and safe_url with one neutral message, a DestinationRefused event for every refusal, and safe-fetch:check for a URL’s verdict or the environment, as text or JSON.

Facade, DI or actions

One SafeFetch facade, an injectable manager or single-purpose actions — plus a recording fake that keeps the real guard and a DNS table for tests.

Documentation

Use cases

Where the package fits — webhook senders, link previews, imports and media from URLs, customer-configured integrations and SSRF alerting.

Installation

Install via Composer — no migrations, safe defaults — then optionally publish the config and translations and check the environment.

Configuration

Every key of config/safe-fetch.php — http, ports, host and range lists, the app-host block, redirects, the fetch cap and budget, the resolver.

The SafeFetch facade

Tour the SafeFetch facade — vet, allows, precheck, guard, http, fetch, download, truncated, policy, networks, urls — and the Http::safe() macro.

DI and actions

Skip the facade: inject SafeFetchManager or call a single-purpose action — every action with its execute() signature and facade method.

Guarded requests

Http::safe() step by step — the pinned curl handler, per-hop vetting, pre-send and post-checks, refused options, pools, retries and the response cap.

Fetch and download

SafeFetch::fetch() reads a capped, guarded GET into memory; download() streams it to a file swapped in only on success. Failures and retries.

Validation and precheck

The SafeUrl rule and the safe_url string rule with one neutral message, SafeUrl::withoutLookup() and SafeFetch::precheck() for save time.

Destination policy

DestinationPolicy — the withers that tighten or loosen per call, host patterns, the order of the rules, private hosts and a policy built from scratch.

Redirects

Redirects are off by default; when on, every hop is vetted and pinned, with a hop limit, no downgrade, credentials and cookies kept per origin.

URLs and blocked networks

How URLs are canonicalised, the 26 blocked ranges, IPv4 inside IPv6, cloud-metadata endpoints, the inspection helpers and a resolver of your own.

Verdicts and exceptions

The Reason enum, the exception hierarchy, retryable() and blockedBy(), neutral messages that never name the URL, and the named constructors.

Events

DestinationRefused fires on every refusal with the reason, host, rule and where it was judged — never the URL — ready for SSRF alerting.

Artisan commands

safe-fetch:check judges a URL or checks the environment, as text or JSON; php artisan about shows the active policy as counts only.

Use in your own package

Recipes for packages that fetch their users’ URLs — a policy that drops the host’s carve-outs, the manager, throttling, retries and save-time prechecks.

Threat model

What the guard defends against — rebinding, disguised addresses, redirects, proxies, parser tricks, leaky errors — and what it does not guarantee.

Testing

SafeFetch::fake() records every verdict and keeps the real guard; fakeDns() answers lookups from a table; every assertion and the pin in a test.

Requirements

PHP 8.4+, Laravel 13, ext-curl to send guarded requests, ext-intl for international names, two Roundly packages — no database.

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 crypto

By 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.