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
+88
View File
@@ -0,0 +1,88 @@
<?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 SherlockRunner implements Runner
{
public function run(Target $target): RunnerResult
{
if ($target->type !== 'username') {
throw new RunnerException('Sherlock only supports "username" targets.');
}
if (! preg_match('/^[a-zA-Z0-9._-]+$/', $target->value)) {
throw new RunnerException('Username contains characters that are not safe to check.');
}
$binary = config('osint.python_venv').'/bin/sherlock';
if (! is_executable($binary)) {
throw new RunnerException("Sherlock binary not found or not executable at [{$binary}]. Run the setup in scripts/python/README.md.");
}
$runDir = storage_path('app/osint/runs/'.Str::uuid());
File::ensureDirectoryExists($runDir);
try {
$result = Process::path($runDir)
->timeout((int) config('osint.run_timeout'))
->run([
$binary,
$target->value,
'--csv',
'--no-color',
'--no-txt',
'--timeout', '30',
]);
if (! $result->successful()) {
throw new RunnerException('Sherlock exited with an error: '.$result->errorOutput());
}
$csvPath = $runDir.'/'.$target->value.'.csv';
if (! File::exists($csvPath)) {
throw new RunnerException('Sherlock did not produce the expected CSV output.');
}
$rows = $this->parseCsv($csvPath);
$summary = count($rows) > 0
? sprintf('Found on %d site%s: %s', count($rows), count($rows) === 1 ? '' : 's', collect($rows)->pluck('name')->implode(', '))
: 'No matching profiles found.';
return new RunnerResult(raw: ['matches' => $rows], summary: $summary);
} finally {
File::deleteDirectory($runDir);
}
}
/**
* @return array<int, array{name: string, url: string, http_status: int|null}>
*/
private function parseCsv(string $path): array
{
$handle = fopen($path, 'r');
$header = fgetcsv($handle);
$rows = [];
while (($row = fgetcsv($handle)) !== false) {
$data = array_combine($header, $row);
$rows[] = [
'name' => $data['name'],
'url' => $data['url_user'],
'http_status' => $data['http_status'] !== '' ? (int) $data['http_status'] : null,
];
}
fclose($handle);
return $rows;
}
}