feat(prospecting): implement security audit pipeline (Workstream 2A)
Complete security audit integration into the enrichment pipeline:
Backend:
- SecurityAuditService with 7 passive checks: HTTPS, SSL cert, security
headers, exposed files, cookies, server info, technology detection
- Constants file with SECURITY_HEADERS, EXPOSED_PATHS, SEVERITY_SCORES
- SecurityAuditResponse schema with JSON field validators + aliases
- Endpoints: POST /security-audit/{id}, POST /security-audit/batch
- Added to full_enrichment pipeline (Step 5, before scoring)
- get_pending_security_audit() query in prospect_service
Frontend:
- Security tab on prospect detail page with grade badge (A+ to F),
score/100, severity counts, HTTPS/SSL status, missing headers,
exposed files, technologies, and full findings list
- "Run Security Audit" button with loading state
- "Security Audit" batch button on scan-jobs page
Tested on batirenovation-strasbourg.fr: Grade D (50/100), 11 issues
found (missing headers, exposed wp-login, server version disclosure).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,7 @@
|
||||
<!-- Tabs -->
|
||||
{{ tab_header([
|
||||
{'id': 'overview', 'label': 'Overview', 'icon': 'eye'},
|
||||
{'id': 'security', 'label': 'Security', 'icon': 'shield-check'},
|
||||
{'id': 'interactions', 'label': 'Interactions', 'icon': 'chat'},
|
||||
{'id': 'campaigns', 'label': 'Campaigns', 'icon': 'mail'},
|
||||
], active_var='activeTab') }}
|
||||
@@ -198,6 +199,110 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Security -->
|
||||
<div x-show="activeTab === 'security'" class="space-y-6">
|
||||
<!-- Run Audit button -->
|
||||
<div class="flex justify-end">
|
||||
<button type="button" @click="runSecurityAudit()" :disabled="auditRunning"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-yellow-600 border border-transparent rounded-lg hover:bg-yellow-700 focus:outline-none disabled:opacity-50">
|
||||
<span x-show="!auditRunning" x-html="$icon('shield-check', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-show="auditRunning" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="auditRunning ? 'Scanning...' : 'Run Security Audit'"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template x-if="prospect.security_audit">
|
||||
<div class="grid gap-6 md:grid-cols-2">
|
||||
<!-- Grade Card -->
|
||||
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800 text-center">
|
||||
<div class="text-5xl font-bold mb-2" :class="gradeColor(prospect.security_audit.grade)"
|
||||
x-text="prospect.security_audit.grade"></div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400 mb-3">Security Grade</div>
|
||||
<div class="text-2xl font-semibold text-gray-700 dark:text-gray-200"
|
||||
x-text="prospect.security_audit.score + '/100'"></div>
|
||||
<!-- Severity counts -->
|
||||
<div class="flex justify-center gap-3 mt-4">
|
||||
<span x-show="prospect.security_audit.findings_count_critical > 0"
|
||||
class="px-2 py-1 text-xs font-bold rounded bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300"
|
||||
x-text="prospect.security_audit.findings_count_critical + ' critical'"></span>
|
||||
<span x-show="prospect.security_audit.findings_count_high > 0"
|
||||
class="px-2 py-1 text-xs font-bold rounded bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300"
|
||||
x-text="prospect.security_audit.findings_count_high + ' high'"></span>
|
||||
<span x-show="prospect.security_audit.findings_count_medium > 0"
|
||||
class="px-2 py-1 text-xs font-bold rounded bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300"
|
||||
x-text="prospect.security_audit.findings_count_medium + ' medium'"></span>
|
||||
<span x-show="prospect.security_audit.findings_count_low > 0"
|
||||
class="px-2 py-1 text-xs font-bold rounded bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300"
|
||||
x-text="prospect.security_audit.findings_count_low + ' low'"></span>
|
||||
</div>
|
||||
<p x-show="prospect.security_audit.scan_error" class="mt-3 text-xs text-red-500" x-text="prospect.security_audit.scan_error"></p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Info -->
|
||||
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
{{ section_header('Quick Overview', icon='shield-check') }}
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">HTTPS</span>
|
||||
<span class="font-medium" :class="prospect.security_audit.has_https ? 'text-green-600' : 'text-red-600'"
|
||||
x-text="prospect.security_audit.has_https ? 'Yes' : 'No'"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">SSL Valid</span>
|
||||
<span class="font-medium" :class="prospect.security_audit.has_valid_ssl ? 'text-green-600' : prospect.security_audit.has_valid_ssl === false ? 'text-red-600' : 'text-gray-400'"
|
||||
x-text="prospect.security_audit.has_valid_ssl == null ? '—' : prospect.security_audit.has_valid_ssl ? 'Yes' : 'No'"></span>
|
||||
</div>
|
||||
<div x-show="prospect.security_audit.ssl_expires_at" class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">SSL Expires</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300"
|
||||
x-text="new Date(prospect.security_audit.ssl_expires_at).toLocaleDateString()"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">Missing Headers</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-200"
|
||||
x-text="(prospect.security_audit.missing_headers || []).length"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">Exposed Files</span>
|
||||
<span class="font-medium"
|
||||
:class="(prospect.security_audit.exposed_files || []).length > 0 ? 'text-red-600' : 'text-green-600'"
|
||||
x-text="(prospect.security_audit.exposed_files || []).length"></span>
|
||||
</div>
|
||||
<div x-show="(prospect.security_audit.technologies || []).length > 0" class="pt-2 border-t border-gray-100 dark:border-gray-700">
|
||||
<span class="text-gray-600 dark:text-gray-400 text-xs uppercase">Technologies</span>
|
||||
<div class="flex flex-wrap gap-1 mt-1">
|
||||
<template x-for="tech in prospect.security_audit.technologies || []" :key="tech">
|
||||
<span class="px-2 py-0.5 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded" x-text="tech"></span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Findings List (full width) -->
|
||||
<div class="md:col-span-2 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
{{ section_header('Findings', icon='clipboard-list') }}
|
||||
<div class="space-y-2">
|
||||
<template x-for="finding in (prospect.security_audit.findings || []).filter(f => !f.is_positive)" :key="finding.title">
|
||||
<div class="flex items-start gap-3 py-2 border-b border-gray-100 dark:border-gray-700 last:border-0">
|
||||
<span class="mt-0.5 px-2 py-0.5 text-xs font-bold rounded shrink-0"
|
||||
:class="severityBadge(finding.severity)"
|
||||
x-text="finding.severity"></span>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="finding.title"></p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="finding.detail"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p x-show="(prospect.security_audit.findings || []).filter(f => !f.is_positive).length === 0"
|
||||
class="text-sm text-green-600 text-center py-4">No security issues found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p x-show="!prospect.security_audit" class="text-sm text-gray-400 text-center py-8">No security audit yet. Click "Run Security Audit" to scan.</p>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Interactions -->
|
||||
<div x-show="activeTab === 'interactions'" class="space-y-4">
|
||||
<div class="flex justify-end">
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
<span x-html="$icon('mail', 'w-4 h-4 mr-2')"></span>
|
||||
Contact Scrape
|
||||
</button>
|
||||
<button type="button" @click="startBatchJob('security_audit')"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-yellow-600 border border-transparent rounded-lg hover:bg-yellow-700 focus:outline-none">
|
||||
<span x-html="$icon('shield-check', 'w-4 h-4 mr-2')"></span>
|
||||
Security Audit
|
||||
</button>
|
||||
<button type="button" @click="startBatchJob('score_compute')"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none">
|
||||
<span x-html="$icon('cursor-click', 'w-4 h-4 mr-2')"></span>
|
||||
|
||||
Reference in New Issue
Block a user