Skip to main content
Back to ScopeForged

ScopeForged Documentation

Technical documentation, guides, and feature references for the ScopeForged client portal.

System & Infrastructure/Security

Security Hardening Guide

Last Updated: 2026-02-14 Status: Implemented Plan Reference: 021-security-hardening.md, 073-security-improvement.md


Overview

The Security Hardening system implements comprehensive security measures to protect the portal from common vulnerabilities. It includes authentication security, data protection, input validation, CSRF protection, security monitoring, a real-time security dashboard, and comprehensive audit reporting.


Table of Contents

  1. Security Features
  2. Security Dashboard
  3. Security Events
  4. Audit Reports
  5. Authentication Security
  6. Data Protection
  7. Input Validation
  8. Security Headers
  9. Technical Architecture
  10. Related Features

Security Features

Overview

FeatureProtection Against
CSRF TokensCross-site request forgery
XSS ProtectionCross-site scripting
SQL InjectionDatabase injection attacks
Password HashingCredential theft
Rate LimitingBrute force attacks
2FAAccount takeover
Session SecuritySession hijacking
EncryptionData theft
Security EventsActivity tracking
Security DashboardReal-time monitoring
Audit ReportsCompliance verification

Security Dashboard

Overview

The Security Dashboard provides a centralized view of your system's security posture. Access it at Admin > Security > Dashboard.

Dashboard Metrics

MetricDescription
Security ScoreOverall security health (0-100)
Failed LoginsFailed login attempts in last 24h
Blocked IPsCurrently blocked IP addresses
Active SessionsCurrently active user sessions
2FA AdoptionPercentage of admins with 2FA enabled
Open AlertsUnresolved security alerts
Critical AlertsHigh-priority alerts needing attention

Security Score Calculation

The security score (0-100) is calculated based on:

  • 2FA Adoption Rate: Lower adoption = lower score
  • Critical Events: Each critical event deducts 5 points (max 30)
  • Failed Logins: >50/day = -10 points, >20/day = -5 points

Dashboard Routes

// Dashboard views
Route::get('/admin/security/dashboard', [SecurityDashboardController::class, 'index']);
Route::get('/admin/security/dashboard/metrics', [SecurityDashboardController::class, 'metrics']);
Route::get('/admin/security/dashboard/events', [SecurityDashboardController::class, 'recentEvents']);

// IP Management
Route::post('/admin/security/dashboard/block-ip', [SecurityDashboardController::class, 'blockIp']);
Route::post('/admin/security/dashboard/unblock-ip', [SecurityDashboardController::class, 'unblockIp']);

Security Events

Overview

Security Events track all security-related activities in your application, providing a comprehensive audit trail for security monitoring and compliance.

Event Types

Event TypeSeverityDescription
login_successInfoSuccessful user login
failed_loginWarningFailed login attempt
logoutInfoUser logout
password_resetInfoPassword reset requested
password_changedInfoPassword changed
two_factor_enabledInfo2FA enabled
two_factor_disabledWarning2FA disabled
two_factor_failedWarning2FA verification failed
ip_blockedCriticalIP address blocked
brute_force_attemptCriticalBrute force detected
unusual_locationWarningLogin from unusual location
suspicious_user_agentWarningSuspicious user agent
multiple_failed_loginsWarningMultiple failed logins

Logging Events

use App\Models\SecurityEvent;

// Log a basic event
SecurityEvent::log(
    SecurityEvent::TYPE_LOGIN_SUCCESS,
    $user->id,
    ['method' => 'password']
);

// Log a critical event
SecurityEvent::logCritical(
    SecurityEvent::TYPE_BRUTE_FORCE,
    null,
    ['attempts' => 10, 'ip' => $request->ip()]
);

// Log a warning event
SecurityEvent::logWarning(
    SecurityEvent::TYPE_LOGIN_FAILED,
    null,
    ['email' => $email]
);

// Convenience methods
SecurityEvent::logFailedLogin($email);
SecurityEvent::logSuccessfulLogin($userId);

Querying Events

// Get recent events
$events = SecurityEvent::recent(24)->get();

// Get critical events
$critical = SecurityEvent::critical()->get();

// Get events by type
$failed = SecurityEvent::ofType(SecurityEvent::TYPE_LOGIN_FAILED)->get();

// Get events from specific IP
$fromIp = SecurityEvent::fromIp('192.168.1.1')->get();

// Get events for a user
$userEvents = SecurityEvent::forUser($userId)->get();

SecurityMetricsService

use App\Services\SecurityMetricsService;

$metrics = app(SecurityMetricsService::class);

// Get various metrics
$failedLogins = $metrics->getFailedLoginAttempts(24);
$blockedIps = $metrics->getBlockedIps();
$suspiciousActivity = $metrics->getSuspiciousActivity(24);
$activeSessions = $metrics->getActiveSessionsCount();
$twoFactorRate = $metrics->getTwoFactorAdoptionRate();
$securityScore = $metrics->getSecurityScore();

// Check if IP should be blocked
if ($metrics->shouldBlockIp($ip, maxAttempts: 5, minutes: 15)) {
    // Block the IP
}

Audit Reports

Overview

Security Audit Reports provide comprehensive analysis of your security posture over configurable time periods. Access at Admin > Security > Audit Reports.

Report Sections

  1. Summary: Overall security score, total events, critical/warning counts
  2. Authentication: Login success/failure rates, 2FA adoption, password changes
  3. Threats: Brute force attempts, blocked IPs, suspicious activity
  4. Compliance: 2FA compliance, inactive users, session security
  5. Alerts: Alert counts, resolution times, false positive rates
  6. Recommendations: Actionable security improvements

Generating Reports

use App\Services\SecurityAuditReportService;

$service = app(SecurityAuditReportService::class);

// Generate a 30-day report
$report = $service->generateReport(
    now()->subDays(30),
    now()
);

// Access report sections
$summary = $report['summary'];
$threats = $report['threats'];
$recommendations = $report['recommendations'];

Export Formats

FormatRouteDescription
HTML/admin/security/auditInteractive view
JSON/admin/security/audit/export/jsonProgrammatic access
CSV/admin/security/audit/export/csvSpreadsheet import

Sample Report Output

{
  "period": {
    "start": "2026-01-01",
    "end": "2026-01-13",
    "days": 13
  },
  "summary": {
    "security_score": 85,
    "total_events": 1250,
    "critical_events": 5,
    "warning_events": 45,
    "two_factor_adoption": 75.0
  },
  "threats": {
    "brute_force_attempts": 3,
    "blocked_ips": 2,
    "total_threats": 8
  },
  "recommendations": [
    {
      "priority": "high",
      "category": "2FA",
      "message": "Only 75% of admin users have 2FA enabled."
    }
  ]
}

Authentication Security

Password Policy

RequirementSetting
Minimum length8 characters
ComplexityMixed case, numbers recommended
HashingBcrypt (cost 10)
HistoryCannot reuse last 5

Two-Factor Authentication

See Authentication Guide for 2FA setup.

Session Security

SettingValuePurpose
session.securetrue (prod)HTTPS only
session.http_onlytrueNo JS access
session.same_sitelaxCSRF protection
session.lifetime120 minAuto logout

Login Security

// Rate limiting (5 attempts per minute)
RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->email . $request->ip());
});

Failed Login Tracking

// Log failed attempts
Event::listen(Failed::class, function ($event) {
    Log::warning('Failed login attempt', [
        'email' => $event->credentials['email'],
        'ip' => request()->ip(),
    ]);
});

Data Protection

Encryption

At Rest:

// Encrypted model attributes
protected $casts = [
    'ssn' => 'encrypted',
    'api_key' => 'encrypted',
];

In Transit:

  • HTTPS enforced in production
  • TLS 1.2+ required
  • HSTS header enabled

Sensitive Data Handling

Data TypeProtection
PasswordsBcrypt hash
API keysEncrypted
PIIEncrypted at rest
TokensHashed, time-limited

File Security

// Files stored outside webroot
'disks' => [
    'secure' => [
        'driver' => 'local',
        'root' => storage_path('app/secure'),
        'visibility' => 'private',
    ],
],

Input Validation

Form Requests

class ClientRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => 'required|email|max:255',
            'company_name' => 'required|string|max:255',
            'phone' => 'nullable|string|max:50',
            'website' => 'nullable|url|max:255',
        ];
    }
}

Sanitization

// Clean user input
$name = strip_tags($request->input('name'));
$html = clean($request->input('content')); // HTMLPurifier

SQL Injection Prevention

// Always use parameter binding
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);

// Use Eloquent (auto-escaped)
$user = User::where('email', $email)->first();

XSS Prevention

{{-- Auto-escaped output --}}
{{ $user->name }}

{{-- Unescaped (use carefully) --}}
{!! $trustedHtml !!}

Security Headers

Configured Headers

The SecurityHeaders middleware (app/Http/Middleware/SecurityHeaders.php) sets the following headers on every response:

HeaderValuePurpose
Content-Security-PolicyNonce-based (see below)Controls allowed content sources
X-Content-Type-OptionsnosniffPrevents MIME sniffing
X-Frame-OptionsSAMEORIGINPrevents clickjacking
X-XSS-Protection1; mode=blockLegacy XSS filter
Referrer-Policystrict-origin-when-cross-originControls referrer info
Permissions-Policygeolocation=(), camera=(), microphone=(), payment=()Disables unused APIs
Strict-Transport-Securitymax-age=31536000; includeSubDomainsHSTS (production only)

Content Security Policy (CSP)

CSP uses nonce-based script authorization instead of unsafe-inline:

// A unique nonce is generated per request
$nonce = base64_encode(random_bytes(16));
View::share('cspNonce', $nonce);

// CSP directives
"script-src 'self' 'nonce-{$nonce}' {cdn_urls} {analytics_urls}"
"style-src 'self' 'unsafe-inline' {font_urls}"

All inline <script> tags must include nonce="{{ $cspNonce }}":

<script nonce="{{ $cspNonce }}">
    // Your inline JavaScript
</script>

Key points:

  • unsafe-eval and unsafe-inline are NOT used for scripts
  • unsafe-inline is still used for styles (required by Tailwind/Alpine)
  • CDN URLs (jsdelivr), analytics (Google), and font URLs are allowlisted
  • Local development adds Vite dev server and WebSocket URLs
  • Never use inline event handlers (onclick=, onchange=, onkeydown=, etc.) — they are blocked by CSP. Use Alpine.js directives instead (@click, @change, @keydown, etc.). Use $el instead of this and $event instead of event in Alpine expressions.

File Upload Content Validation

The ValidFileContent rule (app/Rules/ValidFileContent.php) validates file uploads by checking magic bytes:

File TypeMagic BytesExtension
PDF%PDF.pdf
PNG\x89PNG.png
JPEG\xFF\xD8\xFF.jpg, .jpeg
GIFGIF87a or GIF89a.gif

This prevents spoofed files (e.g., a PHP script renamed to .pdf) from being uploaded.

HTTPS Enforcement

// Force HTTPS in production
if (app()->environment('production')) {
    URL::forceScheme('https');
}

CSRF Protection

Token Validation

<form method="POST">
    @csrf
    <!-- Form fields -->
</form>

API Token Authentication

// API routes use Sanctum tokens instead of CSRF
Route::middleware('auth:sanctum')->group(function () {
    // API routes
});

Security Monitoring

Logging Security Events

EventLogged Data
Login successUser, IP, timestamp
Login failureEmail, IP, timestamp
Password changeUser, timestamp
Permission deniedUser, resource, action
Suspicious activityDetails, IP

Alerting

// Alert on suspicious activity
if ($failedAttempts > 10) {
    Notification::route('mail', config('security.alert_email'))
        ->notify(new SuspiciousActivityAlert($details));
}

Audit Trail

See Activity Logging and Audit Compliance.


Technical Architecture

Security Middleware

Location: app/Http/Middleware/

MiddlewarePurpose
SecurityHeadersAdd security headers
ValidateSignatureVerify signed URLs
ThrottleRequestsRate limiting
VerifyCsrfTokenCSRF protection
EncryptCookiesCookie encryption

Security Service

Location: app/Services/SecurityService.php

class SecurityService
{
    public function hashPassword(string $password): string
    {
        return Hash::make($password);
    }

    public function verifyPassword(string $password, string $hash): bool
    {
        return Hash::check($password, $hash);
    }

    public function generateSecureToken(int $length = 32): string
    {
        return bin2hex(random_bytes($length));
    }

    public function encryptData(string $data): string
    {
        return Crypt::encryptString($data);
    }

    public function decryptData(string $encrypted): string
    {
        return Crypt::decryptString($encrypted);
    }
}

Configuration

// config/security.php
return [
    'password_min_length' => 8,
    'session_lifetime' => 120,
    'max_login_attempts' => 5,
    'lockout_duration' => 60, // seconds
    'require_2fa_for_admin' => true,
    'alert_email' => env('SECURITY_ALERT_EMAIL'),
];

Security Checklist

Authentication

  • Strong password policy
  • Two-factor authentication
  • Session timeout
  • Login rate limiting
  • Failed login logging

Data Protection

  • HTTPS enforced
  • Data encrypted at rest
  • Secure cookie settings
  • Files outside webroot

Application

  • CSRF protection
  • XSS prevention
  • SQL injection prevention
  • Security headers
  • Input validation

Monitoring

  • Security event logging
  • Audit trail
  • Alert notifications
  • Regular reviews

Best Practices

For Users

  1. Use strong passwords with mixed characters
  2. Enable 2FA for your account
  3. Log out when using shared devices
  4. Report suspicious activity

For Developers

  1. Never trust user input - validate everything
  2. Use parameterized queries - no string concatenation
  3. Escape output - use Blade's {{ }}
  4. Keep dependencies updated
  5. Follow least privilege principle

Troubleshooting

IssueSolution
CSRF token mismatchClear browser cookies
Session expires quicklyCheck session config
Can't upload filesCheck file type restrictions
Access denied errorsVerify user permissions

Dependencies

FeatureRelationship
AuthenticationLogin security
AuthorizationAccess control

Complementary Features

FeatureDescription
Activity LoggingSecurity logs
Audit ComplianceCompliance
Admin ToolsSecurity tools

See Also