Skip to content

Testing

Testing

Real DNS is non-deterministic and slow, and you want to prove the guard blocks addresses you can't actually route to in CI. Compose InteractsWithSsrf into your base TestCase and resolution becomes a fixture.

use Cbox\Ssrf\Testing\InteractsWithSsrf;

abstract class TestCase extends Orchestra\Testbench\TestCase
{
    use InteractsWithSsrf;
}
beforeEach(function () {
    $this->fakeSsrfDns([
        'good.test' => ['93.184.216.34'],
        'evil.test' => ['169.254.169.254'],
    ]);
});

it('blocks a webhook that resolves to cloud metadata', function () {
    expect(fn () => $this->ssrfGuard()->assertSafe('https://evil.test'))
        ->toThrow(Cbox\Ssrf\Exceptions\BlockedUrl::class);
});

What the trait gives you

Method Does
fakeSsrfDns(array $dns) Answers DNS from a fixed host => [addresses] table. Returns the FakeResolver.
withSsrfConfig(array $overrides) Sets config('ssrf.*') keys (given without the prefix) and reapplies them.
ssrfGuard() The UrlGuard as the application sees it, rebuilt from current config and DNS.
refreshSsrfGuard() Drops the memoised policy and guard. The others call it for you.

That last point is the reason to use the trait rather than binding FakeResolver yourself: the guard and its policy are container singletons built from config, so if anything has already resolved the guard — a beforeEach that makes a request, say — a later config(['ssrf.enforce' => false]) silently changes nothing and your test asserts against the old policy. Every helper here drops both singletons first.

Testing your webhook delivery without real hosts

Combine FakeResolver with Laravel's Http::fake():

Http::fake(['good.test/*' => Http::response(['ok' => true])]);

$response = Http::ssrf()->post('https://good.test/hook');
expect($response->json('ok'))->toBeTrue();

The guard runs under Http::fake() too — it sits in the handler stack ahead of the stub — so a test that posts to a private address still gets BlockedUrl:

Http::fake();

expect(fn () => Http::ssrf()->post('https://bad.test/hook'))->toThrow(BlockedUrl::class);

What the package's own suite covers

The package is proven against real SSRF vectors, not mocks that return success: private/loopback/link-local/CGNAT addresses, cloud-metadata IPs, IPv6 transition forms (6to4, NAT64, IPv4-mapped), integer/hex IP literals, embedded credentials, disallowed schemes, and blocked host suffixes. See tests/Feature/.