Initial commit: Laravel OSINT platform

Multi-user OSINT investigation tool with owner-controlled per-user and
per-group access grants. Includes Sherlock, theHarvester, crt.sh, WHOIS,
and ip-api.com collectors wired through a pluggable Runner/queue pipeline,
investigation search, and printable/CSV/JSON reporting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
cedry2k1992
2026-07-20 10:22:05 +03:00
co-authored by Claude Sonnet 5
commit 873699e23e
165 changed files with 18507 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace App\Osint\Runners;
use App\Models\Target;
use App\Osint\Runner;
use App\Osint\RunnerException;
use App\Osint\RunnerResult;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
class TheHarvesterRunner implements Runner
{
public function run(Target $target): RunnerResult
{
if ($target->type !== 'domain') {
throw new RunnerException('theHarvester only supports "domain" targets.');
}
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.theharvester_venv').'/bin/python3';
$script = config('osint.theharvester_script');
if (! is_executable($python) || ! is_file($script)) {
throw new RunnerException('theHarvester not set up. Run scripts/python/setup.sh.');
}
$runDir = storage_path('app/osint/runs/'.Str::uuid());
File::ensureDirectoryExists($runDir);
try {
$result = Process::path($runDir)
->timeout((int) config('osint.run_timeout'))
->run([
$python,
$script,
'-d', $target->value,
'-b', config('osint.theharvester_sources'),
'-f', 'result',
]);
if (! $result->successful()) {
throw new RunnerException('theHarvester exited with an error: '.$result->errorOutput());
}
$jsonPath = $runDir.'/result.json';
if (! File::exists($jsonPath)) {
// No results at all means theHarvester skips writing a file.
return new RunnerResult(raw: [], summary: 'No results found.');
}
$data = json_decode(File::get($jsonPath), true) ?? [];
$hosts = collect($data['hosts'] ?? [])
->map(fn (string $entry) => explode(':', $entry, 2)[0])
->unique()
->sort()
->values();
$emails = collect($data['emails'] ?? [])->unique()->sort()->values();
$ips = collect($data['ips'] ?? [])->unique()->sort()->values();
$summary = sprintf(
'Found %d host%s, %d email%s, %d IP%s.',
$hosts->count(), $hosts->count() === 1 ? '' : 's',
$emails->count(), $emails->count() === 1 ? '' : 's',
$ips->count(), $ips->count() === 1 ? '' : 's',
);
return new RunnerResult(
raw: ['hosts' => $hosts->all(), 'emails' => $emails->all(), 'ips' => $ips->all()],
summary: $summary,
);
} finally {
File::deleteDirectory($runDir);
}
}
}