type !== 'domain') { throw new RunnerException('crt.sh only supports "domain" targets.'); } // crt.sh is a free, frequently overloaded public service that // intermittently returns 404/502/503 for queries that succeed // moments later (confirmed by hand: same query, 404 then 200 with // no code changes) — worth a few retries before treating it as a // real failure. $response = Http::timeout((int) config('osint.run_timeout')) ->retry(3, 2000, throw: false) ->get('https://crt.sh/', [ 'q' => $target->value, 'output' => 'json', ]); if (! $response->successful()) { throw new RunnerException('crt.sh request failed with status '.$response->status().' after retries.'); } // crt.sh returns an empty body (not valid JSON) when there are no matches. $certificates = $response->json() ?? []; $subdomains = collect($certificates) ->flatMap(fn (array $cert) => explode("\n", $cert['name_value'] ?? '')) ->map(fn (string $name) => strtolower(trim($name))) ->filter() ->unique() ->sort() ->values(); $summary = $subdomains->isNotEmpty() ? sprintf('Found %d unique subdomain%s in certificate logs.', $subdomains->count(), $subdomains->count() === 1 ? '' : 's') : 'No certificates found.'; return new RunnerResult(raw: ['subdomains' => $subdomains->all()], summary: $summary); } }