type !== 'domain') { throw new RunnerException('WHOIS only supports "domain" targets here.'); } if (! preg_match('/^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/', $target->value)) { throw new RunnerException('Domain contains characters that are not safe to look up.'); } $python = config('osint.python_venv').'/bin/python3'; $script = config('osint.whois_script'); if (! is_executable($python) || ! is_file($script)) { throw new RunnerException("WHOIS script not set up. Run scripts/python/setup.sh."); } $result = Process::timeout((int) config('osint.run_timeout')) ->run([$python, $script, $target->value]); if (! $result->successful()) { $error = json_decode($result->errorOutput(), true)['error'] ?? $result->errorOutput(); throw new RunnerException("WHOIS lookup failed: {$error}"); } $data = json_decode($result->output(), true); if (! is_array($data)) { throw new RunnerException('WHOIS lookup returned unparseable output.'); } if (empty($data['domain_name']) && empty($data['registrar'])) { return new RunnerResult(raw: $data, summary: 'No WHOIS record found.'); } $summary = sprintf( 'Registrar: %s. Created: %s. Expires: %s. Name servers: %s.', $this->stringify($data['registrar'] ?? null) ?? 'unknown', $this->stringify($data['creation_date'] ?? null) ?? 'unknown', $this->stringify($data['expiration_date'] ?? null) ?? 'unknown', $this->stringify($data['name_servers'] ?? null) ?? 'none' ); return new RunnerResult(raw: $data, summary: $summary); } private function stringify(mixed $value): ?string { if (is_array($value)) { return implode(', ', $value) ?: null; } return $value; } }