84 lines
2.7 KiB
PHP
84 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Notifications\SecurityReportDangerNotification;
|
|
use App\Services\SecurityReport\SecurityReportScanner;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Notification;
|
|
|
|
final class SecurityReportScanCommand extends Command
|
|
{
|
|
protected $signature = 'security:scan
|
|
{--notify : Send configured email notification when high or critical findings exist}
|
|
{--triggered-by=artisan : Mark the scan source}
|
|
{--user-id= : Associate the scan with a specific user id}';
|
|
|
|
protected $description = 'Run Composer and npm security audits and store a private admin report.';
|
|
|
|
public function handle(SecurityReportScanner $scanner): int
|
|
{
|
|
if (! (bool) config('security-report.enabled', true)) {
|
|
$this->warn('Security report scanning is disabled.');
|
|
|
|
return self::INVALID;
|
|
}
|
|
|
|
$this->info('Running security report scan...');
|
|
|
|
$report = $scanner->scan(
|
|
(string) $this->option('triggered-by'),
|
|
$this->option('user-id') !== null ? (int) $this->option('user-id') : null,
|
|
);
|
|
|
|
if ($report->status === 'failed') {
|
|
$this->error('Security scan failed: ' . (string) ($report->error_message ?? 'Unknown error'));
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->table(
|
|
['Status', 'Critical', 'High', 'Medium', 'Low', 'Unknown', 'Composer outdated', 'npm outdated'],
|
|
[[
|
|
$report->status,
|
|
$report->total_critical,
|
|
$report->total_high,
|
|
$report->total_medium,
|
|
$report->total_low,
|
|
$report->total_unknown,
|
|
$report->composer_outdated_count,
|
|
$report->npm_outdated_count,
|
|
]],
|
|
);
|
|
|
|
if ((bool) $this->option('notify') && $report->hasDangerFindings()) {
|
|
$this->sendDangerNotification($report);
|
|
$this->info('Danger notification sent.');
|
|
}
|
|
|
|
if ($report->hasCriticalFindings() && (bool) config('security-report.fail_on.critical', false)) {
|
|
return self::FAILURE;
|
|
}
|
|
|
|
if ($report->hasHighFindings() && (bool) config('security-report.fail_on.high', false)) {
|
|
return self::FAILURE;
|
|
}
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function sendDangerNotification(\App\Models\SecurityReport $report): void
|
|
{
|
|
$email = trim((string) config('security-report.notify_email', ''));
|
|
|
|
if ($email === '') {
|
|
return;
|
|
}
|
|
|
|
Notification::route('mail', $email)
|
|
->notify(new SecurityReportDangerNotification($report));
|
|
}
|
|
}
|