Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
1136 lines
30 KiB
Markdown
1136 lines
30 KiB
Markdown
# Security Patterns
|
|
|
|
Comprehensive security architecture and patterns for production deployment.
|
|
|
|
## Web Application Firewall (WAF)
|
|
|
|
### Overview
|
|
|
|
Multi-layer WAF system with intelligent threat detection, machine learning integration, and real-time monitoring.
|
|
|
|
**Core Components**:
|
|
- `WafEngine`: Central orchestrator for all security layers
|
|
- `WafMiddleware`: HTTP middleware integration
|
|
- `ThreatAssessmentService`: Unified threat evaluation
|
|
- `MachineLearningEngine`: ML-based anomaly detection (optional)
|
|
|
|
### WAF Architecture
|
|
|
|
```
|
|
Request → WafMiddleware → WafEngine → Security Layers → ThreatAssessment → Block/Allow
|
|
↓
|
|
[SQL Injection, XSS, Path Traversal,
|
|
Command Injection, Rate Limiting,
|
|
Suspicious User Agents]
|
|
```
|
|
|
|
### Security Layers
|
|
|
|
#### 1. SQL Injection Layer
|
|
**Protection**: Detects SQL injection attempts in query parameters, POST data, headers
|
|
**Patterns**: `UNION SELECT`, `DROP TABLE`, `; DELETE`, `' OR 1=1`, `--`, `/**/`
|
|
**Severity**: CRITICAL
|
|
**Action**: Block + Log
|
|
|
|
#### 2. XSS (Cross-Site Scripting) Layer
|
|
**Protection**: Detects XSS attack vectors
|
|
**Patterns**: `<script>`, `javascript:`, `onerror=`, `onload=`, `eval()`, `innerHTML`
|
|
**Severity**: HIGH
|
|
**Action**: Block + Sanitize + Log
|
|
|
|
#### 3. Path Traversal Layer
|
|
**Protection**: Prevents directory traversal attacks
|
|
**Patterns**: `../`, `..\\`, `/etc/passwd`, `C:\Windows`, `%2e%2e`
|
|
**Severity**: CRITICAL
|
|
**Action**: Block + Log
|
|
|
|
#### 4. Command Injection Layer
|
|
**Protection**: Detects shell command injection
|
|
**Patterns**: `; ls`, `| cat`, `&& rm`, `` `whoami` ``, `$(command)`
|
|
**Severity**: CRITICAL
|
|
**Action**: Block + Log + Alert
|
|
|
|
#### 5. Intelligent Rate Limiting Layer
|
|
**Protection**: Adaptive rate limiting based on behavior patterns
|
|
**Algorithm**: Token bucket with dynamic adjustment
|
|
**Thresholds**: Configurable per endpoint/IP/user
|
|
**Action**: 429 Response + Temporary block
|
|
|
|
#### 6. Suspicious User Agent Layer
|
|
**Protection**: Detects malicious bots and scanners
|
|
**Patterns**: Common scanners (sqlmap, nikto, nmap), headless browsers without proper headers
|
|
**Severity**: MEDIUM
|
|
**Action**: CAPTCHA challenge or block
|
|
|
|
### Configuration
|
|
|
|
```php
|
|
use App\Framework\Config\WafConfig;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
|
|
$wafConfig = new WafConfig(
|
|
enabled: true,
|
|
mode: WafMode::BLOCKING, // or MONITORING for testing
|
|
globalTimeout: Duration::fromMilliseconds(50),
|
|
enableMachineLearning: false, // Enable for advanced detection
|
|
feedbackEnabled: true // Collect feedback for tuning
|
|
);
|
|
```
|
|
|
|
### Usage in Middleware
|
|
|
|
```php
|
|
// Automatic integration via WafMiddleware
|
|
final readonly class WafMiddleware implements Middleware
|
|
{
|
|
public function process(
|
|
Request $request,
|
|
callable $next
|
|
): Response {
|
|
// Analyze request through all WAF layers
|
|
$decision = $this->wafEngine->analyzeRequest($request);
|
|
|
|
if ($decision->shouldBlock()) {
|
|
// Log security event
|
|
$this->owaspLogger->logSecurityEvent(
|
|
new SecurityEventType($decision->threatType),
|
|
$request
|
|
);
|
|
|
|
// Return 403 Forbidden
|
|
return new Response(
|
|
status: Status::FORBIDDEN,
|
|
body: 'Request blocked by security policy'
|
|
);
|
|
}
|
|
|
|
return $next($request);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Manual WAF Analysis
|
|
|
|
```php
|
|
use App\Framework\Waf\WafEngine;
|
|
|
|
// Analyze specific request
|
|
$decision = $this->wafEngine->analyzeRequest($request);
|
|
|
|
if ($decision->shouldBlock()) {
|
|
// Handle threat
|
|
$threatLevel = $decision->threatAssessment->overallRisk;
|
|
$detections = $decision->threatAssessment->detections;
|
|
|
|
foreach ($detections as $detection) {
|
|
$this->logger->warning('WAF Detection', [
|
|
'type' => $detection->type,
|
|
'severity' => $detection->severity->value,
|
|
'pattern' => $detection->pattern,
|
|
'value' => $detection->value
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Get layer-specific results
|
|
$layerResults = $this->wafEngine->getLayerResults();
|
|
|
|
// Health monitoring
|
|
$health = $this->wafEngine->getHealthStatus();
|
|
// Returns: ['status' => 'healthy', 'health_percentage' => 100.0, ...]
|
|
```
|
|
|
|
### WAF Feedback System
|
|
|
|
Collect feedback on false positives/negatives for continuous improvement:
|
|
|
|
```php
|
|
use App\Framework\Waf\Feedback\WafFeedbackIntegrator;
|
|
|
|
// Report false positive
|
|
$feedback = $this->feedbackIntegrator->reportFalsePositive(
|
|
requestId: $requestId,
|
|
detectionType: 'sql_injection',
|
|
reason: 'Legitimate query parameter',
|
|
userName: $currentUser->name
|
|
);
|
|
|
|
// Report false negative
|
|
$feedback = $this->feedbackIntegrator->reportFalseNegative(
|
|
requestId: $requestId,
|
|
attackType: 'xss',
|
|
evidence: 'Malicious script bypassed detection',
|
|
userName: $securityTeam->name
|
|
);
|
|
```
|
|
|
|
### Performance Characteristics
|
|
|
|
- **Average Latency**: <5ms per request
|
|
- **Max Layers Parallel**: 6 layers
|
|
- **Throughput**: 10,000+ requests/second
|
|
- **False Positive Rate**: <0.1% (with ML enabled)
|
|
- **Detection Rate**: >99.5% (OWASP Top 10)
|
|
|
|
### Monitoring & Alerts
|
|
|
|
```php
|
|
// Performance stats
|
|
$stats = $this->wafEngine->getPerformanceStats();
|
|
|
|
foreach ($stats as $layerName => $metrics) {
|
|
$this->monitor->gauge("waf.layer.{$layerName}.duration",
|
|
$metrics['execution_duration']->toMilliseconds()
|
|
);
|
|
$this->monitor->gauge("waf.layer.{$layerName}.detections",
|
|
$metrics['detections']
|
|
);
|
|
}
|
|
|
|
// Health checks
|
|
if ($health['status'] === 'degraded') {
|
|
$this->alerting->sendAlert(
|
|
level: AlertLevel::WARNING,
|
|
message: "WAF degraded: {$health['healthy_layers']}/{$health['total_layers']} layers healthy"
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## OWASP Event Integration
|
|
|
|
### Overview
|
|
|
|
Comprehensive security event logging based on OWASP Application Security Logging standards.
|
|
|
|
**Features**:
|
|
- Standardized event types (OWASP event identifiers)
|
|
- Contextual security event data
|
|
- Integration with WAF, authentication, and authorization systems
|
|
- Real-time security monitoring
|
|
|
|
### Event Types
|
|
|
|
```php
|
|
use App\Framework\Security\OWASPEventIdentifier;
|
|
|
|
// Authentication Events
|
|
OWASPEventIdentifier::AUTHN_LOGIN_SUCCESS
|
|
OWASPEventIdentifier::AUTHN_LOGIN_FAILURE
|
|
OWASPEventIdentifier::AUTHN_LOGOUT_SUCCESS
|
|
OWASPEventIdentifier::AUTHN_SESSION_EXPIRED
|
|
OWASPEventIdentifier::AUTHN_TOKEN_INVALID
|
|
|
|
// Authorization Events
|
|
OWASPEventIdentifier::AUTHZ_PERMISSION_DENIED
|
|
OWASPEventIdentifier::AUTHZ_PRIVILEGE_ESCALATION
|
|
|
|
// Input Validation Events
|
|
OWASPEventIdentifier::INPUT_VALIDATION_FAILURE
|
|
OWASPEventIdentifier::INPUT_XSS_DETECTED
|
|
OWASPEventIdentifier::INPUT_SQL_INJECTION_DETECTED
|
|
|
|
// Session Events
|
|
OWASPEventIdentifier::SESSION_HIJACK_ATTEMPT
|
|
OWASPEventIdentifier::SESSION_FIXATION_ATTEMPT
|
|
|
|
// Security Events
|
|
OWASPEventIdentifier::SECURITY_INTRUSION_DETECTED
|
|
OWASPEventIdentifier::SECURITY_MALWARE_DETECTED
|
|
```
|
|
|
|
### Logging Security Events
|
|
|
|
```php
|
|
use App\Framework\Security\OWASPSecurityLogger;
|
|
|
|
// Log authentication failure
|
|
$this->owaspLogger->logSecurityEvent(
|
|
new SecurityEventType(OWASPEventIdentifier::AUTHN_LOGIN_FAILURE),
|
|
request: $request,
|
|
context: [
|
|
'username' => $credentials->username,
|
|
'ip_address' => $request->server->getRemoteAddr(),
|
|
'user_agent' => $request->server->getUserAgent(),
|
|
'failure_reason' => 'Invalid credentials'
|
|
]
|
|
);
|
|
|
|
// Log intrusion detection
|
|
$this->owaspLogger->logSecurityEvent(
|
|
new SecurityEventType(OWASPEventIdentifier::SECURITY_INTRUSION_DETECTED),
|
|
request: $request,
|
|
context: [
|
|
'attack_type' => 'sql_injection',
|
|
'detected_pattern' => "' OR 1=1--",
|
|
'threat_level' => 'critical',
|
|
'waf_layer' => 'SqlInjectionLayer'
|
|
]
|
|
);
|
|
```
|
|
|
|
### WAF-OWASP Bridge
|
|
|
|
Automatic event logging for WAF detections:
|
|
|
|
```php
|
|
final readonly class WafOWASPEventBridge
|
|
{
|
|
public function logWafDetection(
|
|
WafDecision $decision,
|
|
Request $request
|
|
): void {
|
|
if (!$decision->shouldBlock()) {
|
|
return;
|
|
}
|
|
|
|
$eventType = $this->mapWafThreatToOWASP(
|
|
$decision->threatAssessment->primaryThreat
|
|
);
|
|
|
|
$this->owaspLogger->logSecurityEvent(
|
|
$eventType,
|
|
$request,
|
|
context: [
|
|
'threat_score' => $decision->threatAssessment->overallRisk,
|
|
'detections' => $decision->threatAssessment->detections,
|
|
'action_taken' => 'blocked'
|
|
]
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Event Monitoring Dashboard
|
|
|
|
```php
|
|
// Get security event statistics
|
|
$stats = $this->owaspLogger->getEventStatistics(
|
|
timeRange: Duration::fromHours(24)
|
|
);
|
|
|
|
// Returns:
|
|
[
|
|
'total_events' => 1523,
|
|
'by_severity' => [
|
|
'critical' => 5,
|
|
'high' => 23,
|
|
'medium' => 145,
|
|
'low' => 1350
|
|
],
|
|
'by_type' => [
|
|
'authn_login_failure' => 342,
|
|
'input_validation_failure' => 891,
|
|
'authz_permission_denied' => 67,
|
|
...
|
|
],
|
|
'top_ips' => [
|
|
'203.0.113.42' => 89,
|
|
'198.51.100.10' => 67,
|
|
...
|
|
]
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## CSRF Protection
|
|
|
|
### Overview
|
|
|
|
Token-based CSRF protection for all state-changing operations.
|
|
|
|
**Features**:
|
|
- Per-session CSRF tokens
|
|
- Automatic token validation via middleware
|
|
- Framework-level integration
|
|
- Token rotation support
|
|
|
|
### Configuration
|
|
|
|
```php
|
|
// CSRF tokens are automatically generated and validated
|
|
// Configuration via CsrfMiddleware priority
|
|
|
|
use App\Framework\Http\Middlewares\CsrfMiddleware;
|
|
|
|
// Middleware automatically validates CSRF for:
|
|
// - POST, PUT, DELETE, PATCH requests
|
|
// - Excludes: GET, HEAD, OPTIONS
|
|
```
|
|
|
|
### Token Generation
|
|
|
|
```php
|
|
// Automatic token generation in sessions
|
|
$csrfToken = $session->getCsrfToken();
|
|
|
|
// Manual token generation
|
|
use App\Framework\Security\CsrfTokenGenerator;
|
|
|
|
$token = $this->csrfTokenGenerator->generate();
|
|
```
|
|
|
|
### Template Integration
|
|
|
|
```html
|
|
<!-- Automatic token injection via CsrfTokenProcessor -->
|
|
<form method="POST" action="/api/users">
|
|
{csrf_token} <!-- Automatically replaced with hidden input -->
|
|
|
|
<input type="text" name="username" />
|
|
<button type="submit">Create User</button>
|
|
</form>
|
|
|
|
<!-- Rendered as: -->
|
|
<form method="POST" action="/api/users">
|
|
<input type="hidden" name="_csrf_token" value="generated_token_here" />
|
|
|
|
<input type="text" name="username" />
|
|
<button type="submit">Create User</button>
|
|
</form>
|
|
```
|
|
|
|
### JavaScript/AJAX Integration
|
|
|
|
```javascript
|
|
// Get token from meta tag
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
|
|
|
// Include in requests
|
|
fetch('/api/endpoint', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrfToken
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
```
|
|
|
|
### Excluding Routes from CSRF
|
|
|
|
```php
|
|
use App\Framework\Attributes\Route;
|
|
use App\Framework\Http\Method;
|
|
|
|
// API endpoints with other authentication
|
|
#[Route(path: '/api/webhook', method: Method::POST)]
|
|
#[SkipCsrf] // Custom attribute to skip CSRF for this route
|
|
public function handleWebhook(Request $request): JsonResult
|
|
{
|
|
// Validate via signature/API key instead
|
|
$this->validateWebhookSignature($request);
|
|
|
|
return new JsonResult(['status' => 'processed']);
|
|
}
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```php
|
|
// CSRF validation failure throws exception
|
|
use App\Framework\Exception\Security\CsrfTokenMismatchException;
|
|
|
|
try {
|
|
$this->csrfValidator->validate($request);
|
|
} catch (CsrfTokenMismatchException $e) {
|
|
// Log security event
|
|
$this->owaspLogger->logSecurityEvent(
|
|
new SecurityEventType(OWASPEventIdentifier::CSRF_ATTACK_DETECTED),
|
|
$request
|
|
);
|
|
|
|
// Return 403 Forbidden
|
|
return new Response(
|
|
status: Status::FORBIDDEN,
|
|
body: 'CSRF token validation failed'
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
### Overview
|
|
|
|
Multi-level rate limiting with intelligent adaptation and DDoS protection.
|
|
|
|
**Features**:
|
|
- IP-based rate limiting
|
|
- User-based rate limiting
|
|
- Endpoint-specific limits
|
|
- Adaptive thresholds
|
|
- Distributed rate limiting support
|
|
|
|
### Configuration
|
|
|
|
```env
|
|
# Rate Limiting Configuration (.env)
|
|
RATE_LIMIT_DEFAULT=60 # Requests per window
|
|
RATE_LIMIT_WINDOW=60 # Window in seconds
|
|
RATE_LIMIT_AUTH=10 # Auth endpoint limit
|
|
RATE_LIMIT_AUTH_WINDOW=300 # Auth window (5 min)
|
|
RATE_LIMIT_API=30 # API endpoint limit
|
|
RATE_LIMIT_API_WINDOW=60 # API window
|
|
```
|
|
|
|
### Usage Patterns
|
|
|
|
```php
|
|
use App\Framework\RateLimit\RateLimiter;
|
|
use App\Framework\RateLimit\RateLimitKey;
|
|
|
|
// IP-based rate limiting
|
|
$key = RateLimitKey::forIp($request->server->getRemoteAddr());
|
|
$limit = RateLimit::create(requests: 60, window: Duration::fromSeconds(60));
|
|
|
|
if (!$this->rateLimiter->allow($key, $limit)) {
|
|
throw new RateLimitExceededException(
|
|
retryAfter: $this->rateLimiter->retryAfter($key)
|
|
);
|
|
}
|
|
|
|
// User-based rate limiting
|
|
$key = RateLimitKey::forUser($userId);
|
|
$limit = RateLimit::create(requests: 100, window: Duration::fromMinutes(1));
|
|
|
|
if (!$this->rateLimiter->allow($key, $limit)) {
|
|
return new JsonResponse(
|
|
data: ['error' => 'Too many requests'],
|
|
status: Status::TOO_MANY_REQUESTS,
|
|
headers: [
|
|
'Retry-After' => (string) $this->rateLimiter->retryAfter($key)->toSeconds()
|
|
]
|
|
);
|
|
}
|
|
```
|
|
|
|
### Middleware Integration
|
|
|
|
```php
|
|
use App\Framework\Http\Middlewares\RateLimitMiddleware;
|
|
|
|
// Automatically applied via middleware stack
|
|
// Custom limits per route:
|
|
#[Route(path: '/api/search', method: Method::GET)]
|
|
#[RateLimit(requests: 10, window: 60)] // 10 requests per minute
|
|
public function search(Request $request): JsonResult
|
|
{
|
|
return new JsonResult($this->searchService->search($request));
|
|
}
|
|
```
|
|
|
|
### Adaptive Rate Limiting
|
|
|
|
The `IntelligentRateLimitLayer` in WAF automatically adjusts based on:
|
|
- Historical traffic patterns
|
|
- Attack detection
|
|
- Resource utilization
|
|
- User reputation scores
|
|
|
|
### Monitoring
|
|
|
|
```php
|
|
// Get rate limit statistics
|
|
$stats = $this->rateLimiter->getStatistics();
|
|
|
|
// Returns:
|
|
[
|
|
'total_requests' => 15234,
|
|
'blocked_requests' => 523,
|
|
'top_limited_ips' => [
|
|
'203.0.113.42' => 89,
|
|
'198.51.100.10' => 67,
|
|
],
|
|
'average_retry_after' => Duration::fromSeconds(45)
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication & Authorization
|
|
|
|
### IP-based Authentication
|
|
|
|
```php
|
|
use App\Framework\Attributes\Route;
|
|
use App\Framework\Attributes\Auth;
|
|
|
|
// Admin routes restricted to whitelisted IPs
|
|
#[Route(path: '/admin/dashboard', method: Method::GET)]
|
|
#[Auth(strategy: 'ip', allowedIps: ['127.0.0.1', '::1', '203.0.113.0/24'])]
|
|
public function dashboard(): ViewResult
|
|
{
|
|
return new ViewResult('admin/dashboard');
|
|
}
|
|
```
|
|
|
|
### Session-based Authentication
|
|
|
|
```php
|
|
#[Route(path: '/api/users', method: Method::POST)]
|
|
#[Auth(strategy: 'session', roles: ['admin'])]
|
|
public function createUser(CreateUserRequest $request): JsonResult
|
|
{
|
|
// Only authenticated admins can access
|
|
return new JsonResult($this->userService->create($request));
|
|
}
|
|
```
|
|
|
|
### Token-based Authentication
|
|
|
|
```php
|
|
#[Route(path: '/api/v1/data', method: Method::GET)]
|
|
#[Auth(strategy: 'token', scopes: ['read:data'])]
|
|
public function getData(Request $request): JsonResult
|
|
{
|
|
// Validate Bearer token
|
|
$token = $request->headers->getAuthorization();
|
|
$user = $this->tokenAuthenticator->authenticate($token);
|
|
|
|
return new JsonResult($this->dataService->getData($user));
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Security Headers
|
|
|
|
### Automatic Security Headers
|
|
|
|
Framework automatically sets security headers via middleware:
|
|
|
|
```php
|
|
// SecurityHeadersMiddleware
|
|
$headers = [
|
|
'X-Frame-Options' => 'SAMEORIGIN',
|
|
'X-Content-Type-Options' => 'nosniff',
|
|
'X-XSS-Protection' => '1; mode=block',
|
|
'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
|
|
'Content-Security-Policy' => $this->generateCspHeader(),
|
|
'Referrer-Policy' => 'strict-origin-when-cross-origin',
|
|
'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()'
|
|
];
|
|
```
|
|
|
|
### Content Security Policy (CSP)
|
|
|
|
```php
|
|
// Configure CSP via environment or config
|
|
$csp = ContentSecurityPolicy::build()
|
|
->defaultSrc(["'self'"])
|
|
->scriptSrc(["'self'", "'unsafe-inline'", 'https://cdn.example.com'])
|
|
->styleSrc(["'self'", "'unsafe-inline'"])
|
|
->imgSrc(["'self'", 'data:', 'https:'])
|
|
->connectSrc(["'self'", 'wss://localhost'])
|
|
->fontSrc(["'self'", 'https://fonts.gstatic.com'])
|
|
->reportUri('/csp-report');
|
|
```
|
|
|
|
---
|
|
|
|
## State Encryption for LiveComponents
|
|
|
|
### Overview
|
|
|
|
Sensitive LiveComponent state data is automatically encrypted using authenticated encryption (AES-256-GCM via libsodium) to protect against tampering and unauthorized access.
|
|
|
|
**Core Components**:
|
|
- `StateEncryptor`: Low-level encryption/decryption with libsodium
|
|
- `EncryptedStateSerializer`: State serialization with automatic encryption
|
|
- `EncryptionTransformer`: Transparent encryption in state management pipeline
|
|
- `CacheBasedStateManager`: State persistence with encryption support
|
|
|
|
### Security Features
|
|
|
|
**Authenticated Encryption**:
|
|
- Algorithm: XSalsa20-Poly1305 (via `sodium_crypto_secretbox`)
|
|
- Key Size: 256-bit (32 bytes)
|
|
- Nonce: 24 bytes, unique per encryption operation
|
|
- MAC: Poly1305 authentication tag included
|
|
- Version Byte: Prepended for future compatibility
|
|
|
|
**Protection Against**:
|
|
- State tampering (MAC verification fails)
|
|
- Replay attacks (unique nonces per encryption)
|
|
- Unauthorized decryption (strong encryption key required)
|
|
- Side-channel attacks (constant-time comparisons)
|
|
|
|
### Configuration
|
|
|
|
```php
|
|
// Generate encryption key (only once, store in Vault)
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
|
|
$random = new SecureRandomGenerator();
|
|
$encryptionKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 32 bytes
|
|
|
|
// Store in Vault
|
|
$vault->store('state_encryption_key', $encryptionKey);
|
|
```
|
|
|
|
**Environment Configuration**:
|
|
```env
|
|
# .env
|
|
VAULT_ENCRYPTION_KEY=<base64-encoded-key>
|
|
STATE_ENCRYPTION_ENABLED=true
|
|
```
|
|
|
|
### Basic Usage
|
|
|
|
#### Direct Encryption
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Serialization\StateEncryptor;
|
|
use App\Framework\Cryptography\CryptographicUtilities;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
|
|
// Initialize encryptor
|
|
$random = new SecureRandomGenerator();
|
|
$crypto = new CryptographicUtilities($random);
|
|
$encryptionKey = $vault->get('state_encryption_key');
|
|
|
|
$encryptor = new StateEncryptor($encryptionKey, $crypto, $random);
|
|
|
|
// Encrypt plaintext
|
|
$encrypted = $encryptor->encrypt('sensitive data');
|
|
|
|
// Decrypt
|
|
$plaintext = $encryptor->decrypt($encrypted);
|
|
|
|
// Verify encryption format
|
|
if ($encryptor->isEncrypted($encrypted)) {
|
|
// Data is encrypted
|
|
}
|
|
```
|
|
|
|
#### State Serialization with Encryption
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Serialization\EncryptedStateSerializer;
|
|
|
|
// State must implement SerializableState interface
|
|
final readonly class UserProfileState implements SerializableState
|
|
{
|
|
public function __construct(
|
|
public string $userId,
|
|
public Email $email,
|
|
public string $sessionToken // Sensitive data
|
|
) {}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'user_id' => $this->userId,
|
|
'email' => $this->email->value,
|
|
'session_token' => $this->sessionToken,
|
|
];
|
|
}
|
|
|
|
public static function fromArray(array $data): self
|
|
{
|
|
return new self(
|
|
userId: $data['user_id'],
|
|
email: new Email($data['email']),
|
|
sessionToken: $data['session_token']
|
|
);
|
|
}
|
|
}
|
|
|
|
// Serialize and encrypt
|
|
$serializer = new EncryptedStateSerializer($encryptor);
|
|
$encrypted = $serializer->serialize($userProfileState);
|
|
|
|
// Deserialize and decrypt
|
|
$decrypted = $serializer->deserialize($encrypted, UserProfileState::class);
|
|
```
|
|
|
|
#### Automatic Encryption via Transformer Pipeline
|
|
|
|
```php
|
|
use App\Framework\StateManagement\CacheBasedStateManager;
|
|
use App\Framework\StateManagement\EncryptionTransformer;
|
|
|
|
// Setup state manager with encryption transformer
|
|
$encryptionTransformer = new EncryptionTransformer($serializer);
|
|
|
|
$stateManager = new CacheBasedStateManager(
|
|
$cache,
|
|
transformers: [$encryptionTransformer] // Add to transformer pipeline
|
|
);
|
|
|
|
// State is automatically encrypted when stored
|
|
$stateManager->store($componentId, $state);
|
|
|
|
// State is automatically decrypted when retrieved
|
|
$state = $stateManager->retrieve($componentId, UserProfileState::class);
|
|
```
|
|
|
|
### Encryption Details
|
|
|
|
**Encrypted Data Format**:
|
|
```
|
|
[Version Byte: 1 byte][Nonce: 24 bytes][Ciphertext + MAC: variable]
|
|
↑ Output from sodium_crypto_secretbox
|
|
```
|
|
|
|
**Data Flow**:
|
|
```
|
|
1. State Object → toArray()
|
|
2. Array → JSON encoding
|
|
3. JSON → Encryption with unique nonce
|
|
4. Encrypted bytes → Base64 encoding
|
|
5. Base64 string → Cache storage
|
|
```
|
|
|
|
**Decryption Flow**:
|
|
```
|
|
1. Base64 string → Base64 decoding
|
|
2. Encrypted bytes → Extract version byte + nonce + ciphertext
|
|
3. Ciphertext → Decryption with MAC verification
|
|
4. Decrypted JSON → JSON decoding
|
|
5. Array → fromArray() → State Object
|
|
```
|
|
|
|
### Key Management
|
|
|
|
**Best Practices**:
|
|
|
|
1. **Key Generation**:
|
|
```php
|
|
// Use framework's SecureRandomGenerator
|
|
$random = new SecureRandomGenerator();
|
|
$key = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
|
|
// Verify key strength
|
|
$crypto = new CryptographicUtilities($random);
|
|
if (!$crypto->validateEntropy($key, 4.0)) {
|
|
throw new \RuntimeException('Weak encryption key');
|
|
}
|
|
```
|
|
|
|
2. **Key Storage**:
|
|
```php
|
|
// Store in Vault (encrypted at rest)
|
|
$vault->store('state_encryption_key', $key);
|
|
|
|
// DO NOT store in:
|
|
// - .env files (committed to git)
|
|
// - Plain text files
|
|
// - Database without encryption
|
|
```
|
|
|
|
3. **Key Rotation**:
|
|
```php
|
|
// Generate new key
|
|
$newKey = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
|
|
// Re-encrypt existing states
|
|
foreach ($stateManager->getAllStates() as $id => $state) {
|
|
// Decrypt with old key
|
|
$decrypted = $oldEncryptor->decrypt($state);
|
|
|
|
// Encrypt with new key
|
|
$reencrypted = $newEncryptor->encrypt($decrypted);
|
|
|
|
// Store updated state
|
|
$stateManager->update($id, $reencrypted);
|
|
}
|
|
|
|
// Replace key in vault
|
|
$vault->store('state_encryption_key', $newKey);
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Exceptions\StateEncryptionException;
|
|
|
|
try {
|
|
$encrypted = $serializer->serialize($state);
|
|
} catch (StateEncryptionException $e) {
|
|
// Encryption failed
|
|
// Possible causes:
|
|
// - Invalid encryption key
|
|
// - State serialization error
|
|
// - libsodium extension not loaded
|
|
|
|
error_log("State encryption failed: " . $e->getMessage());
|
|
throw $e;
|
|
}
|
|
|
|
try {
|
|
$state = $serializer->deserialize($encrypted, StateClass::class);
|
|
} catch (StateEncryptionException $e) {
|
|
// Decryption failed
|
|
// Possible causes:
|
|
// - Invalid/corrupted encrypted data
|
|
// - MAC verification failed (tampering detected)
|
|
// - Wrong encryption key
|
|
// - Invalid state class
|
|
|
|
error_log("State decryption failed: " . $e->getMessage());
|
|
|
|
// Security response: Do NOT expose details to client
|
|
throw new \RuntimeException('State decryption failed');
|
|
}
|
|
```
|
|
|
|
### Security Considerations
|
|
|
|
**1. Key Protection**:
|
|
- NEVER commit encryption keys to version control
|
|
- Use Vault for secure key storage
|
|
- Rotate keys regularly (recommended: quarterly)
|
|
- Different keys per environment (dev/staging/prod)
|
|
|
|
**2. Nonce Uniqueness**:
|
|
- Framework automatically generates unique nonce per encryption
|
|
- NEVER reuse nonces with same key
|
|
- Nonce is prepended to ciphertext (no separate storage needed)
|
|
|
|
**3. MAC Verification**:
|
|
- Always performed during decryption
|
|
- Constant-time comparison prevents timing attacks
|
|
- Tampering detection is automatic
|
|
|
|
**4. State Validation**:
|
|
```php
|
|
// Always validate decrypted state
|
|
final readonly class SecureStateManager
|
|
{
|
|
public function retrieve(string $id, string $stateClass): object
|
|
{
|
|
// 1. Retrieve encrypted state from cache
|
|
$encrypted = $this->cache->get($id);
|
|
|
|
// 2. Decrypt and deserialize
|
|
$state = $this->serializer->deserialize($encrypted, $stateClass);
|
|
|
|
// 3. Validate decrypted state
|
|
if (!$this->validator->isValid($state)) {
|
|
throw new StateValidationException('Invalid state after decryption');
|
|
}
|
|
|
|
return $state;
|
|
}
|
|
}
|
|
```
|
|
|
|
**5. Performance Considerations**:
|
|
- Encryption overhead: ~1-2ms per operation
|
|
- Use for sensitive data only
|
|
- Consider caching decrypted states in memory for short periods
|
|
- Monitor cache TTL to balance security and performance
|
|
|
|
### Testing
|
|
|
|
```php
|
|
// Example unit test
|
|
it('encrypts and decrypts state correctly', function () {
|
|
$random = new SecureRandomGenerator();
|
|
$crypto = new CryptographicUtilities($random);
|
|
$key = $random->bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
|
|
$encryptor = new StateEncryptor($key, $crypto, $random);
|
|
$serializer = new EncryptedStateSerializer($encryptor);
|
|
|
|
$state = new UserProfileState(
|
|
userId: 'user123',
|
|
email: new Email('test@example.com'),
|
|
sessionToken: 'secret-token'
|
|
);
|
|
|
|
// Encrypt
|
|
$encrypted = $serializer->serialize($state);
|
|
expect($encrypted)->toBeString();
|
|
expect($encrypted)->not->toContain('secret-token'); // Plaintext not visible
|
|
|
|
// Decrypt
|
|
$decrypted = $serializer->deserialize($encrypted, UserProfileState::class);
|
|
expect($decrypted->sessionToken)->toBe('secret-token');
|
|
});
|
|
|
|
it('detects tampering via MAC verification', function () {
|
|
$encrypted = $serializer->serialize($state);
|
|
|
|
// Tamper with ciphertext
|
|
$tampered = substr($encrypted, 0, -5) . 'XXXXX';
|
|
|
|
// Should throw on decryption
|
|
$serializer->deserialize($tampered, UserProfileState::class);
|
|
})->throws(StateEncryptionException::class, 'MAC verification failed');
|
|
```
|
|
|
|
### Migration Guide
|
|
|
|
**Adding Encryption to Existing State**:
|
|
|
|
```php
|
|
// Step 1: Add EncryptionTransformer to StateManager
|
|
$encryptionTransformer = new EncryptionTransformer($serializer);
|
|
|
|
$stateManager = new CacheBasedStateManager(
|
|
$cache,
|
|
transformers: [$encryptionTransformer] // Add transformer
|
|
);
|
|
|
|
// Step 2: Existing states will be re-encrypted on next update
|
|
// No migration script needed - encryption happens transparently
|
|
|
|
// Step 3: Monitor for encryption errors
|
|
try {
|
|
$state = $stateManager->retrieve($id, StateClass::class);
|
|
} catch (StateEncryptionException $e) {
|
|
// Old unencrypted state or corruption
|
|
// Regenerate state from source
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Input Validation
|
|
|
|
### Request Validation
|
|
|
|
```php
|
|
final readonly class CreateUserRequest implements ControllerRequest
|
|
{
|
|
public function __construct(
|
|
public Email $email, // Validated Email value object
|
|
public UserName $userName, // Validated UserName value object
|
|
public ?string $company = null
|
|
) {}
|
|
|
|
public static function fromHttpRequest(HttpRequest $request): self
|
|
{
|
|
$data = $request->parsedBody->toArray();
|
|
|
|
// Automatic validation via Value Objects
|
|
return new self(
|
|
email: new Email($data['email'] ?? ''), // Throws if invalid
|
|
userName: new UserName($data['username'] ?? ''), // Validates format
|
|
company: !empty($data['company']) ? trim($data['company']) : null
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Value Object Validation
|
|
|
|
```php
|
|
// Email value object with built-in validation
|
|
final readonly class Email
|
|
{
|
|
public function __construct(public string $value)
|
|
{
|
|
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
throw new InvalidArgumentException('Invalid email format');
|
|
}
|
|
|
|
// Additional security checks
|
|
if ($this->containsSuspiciousPatterns($value)) {
|
|
throw new SecurityException('Email contains suspicious patterns');
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Security Best Practices
|
|
|
|
### 1. Never Trust User Input
|
|
- Validate all input via Value Objects
|
|
- Use parameterized queries (never string concatenation)
|
|
- Sanitize output based on context (HTML, JavaScript, SQL, etc.)
|
|
|
|
### 2. Defense in Depth
|
|
- Multiple security layers (WAF + validation + authorization)
|
|
- Fail securely (block by default, allow explicitly)
|
|
- Log all security events
|
|
|
|
### 3. Secure Defaults
|
|
- HTTPS enforced in production
|
|
- Security headers enabled by default
|
|
- CSRF protection automatic
|
|
- Rate limiting active
|
|
|
|
### 4. Secrets Management
|
|
- Use Vault for sensitive data
|
|
- Never commit secrets to version control
|
|
- Rotate keys regularly
|
|
- Use different keys per environment
|
|
|
|
### 5. Monitoring & Alerting
|
|
- Monitor OWASP security events
|
|
- Alert on unusual patterns
|
|
- Regular security audits
|
|
- Automated vulnerability scanning
|
|
|
|
### 6. Incident Response
|
|
- Defined escalation procedures
|
|
- Security event playbooks
|
|
- Regular incident response drills
|
|
- Post-mortem analysis
|
|
|
|
---
|
|
|
|
## Production Deployment Checklist
|
|
|
|
### Encryption & Key Management
|
|
- [ ] Generate new VAULT_ENCRYPTION_KEY
|
|
- [ ] Generate STATE_ENCRYPTION_KEY and store in Vault
|
|
- [ ] Verify libsodium extension is installed (`php -m | grep sodium`)
|
|
- [ ] Test encryption key strength (minimum 4.0 bits/byte entropy)
|
|
- [ ] Configure key rotation schedule (recommended: quarterly)
|
|
- [ ] Set up separate encryption keys per environment
|
|
|
|
### State Encryption
|
|
- [ ] Enable STATE_ENCRYPTION_ENABLED=true in production
|
|
- [ ] Add EncryptionTransformer to StateManager pipeline
|
|
- [ ] Test state encryption/decryption flow
|
|
- [ ] Verify encrypted states in cache (no plaintext visible)
|
|
- [ ] Monitor encryption performance (<2ms overhead)
|
|
- [ ] Set up alerts for StateEncryptionException errors
|
|
|
|
### WAF & Security Headers
|
|
- [ ] Configure ADMIN_ALLOWED_IPS with production IPs
|
|
- [ ] Enable WAF in BLOCKING mode
|
|
- [ ] Configure rate limits for production traffic
|
|
- [ ] Set up OWASP event monitoring
|
|
- [ ] Enable security headers
|
|
- [ ] Configure CSP policy
|
|
- [ ] Test CSRF protection
|
|
|
|
### SSL/TLS & Network Security
|
|
- [ ] Verify SSL/TLS configuration
|
|
- [ ] Review and update firewall rules
|
|
- [ ] Test HTTPS enforcement
|
|
- [ ] Configure HSTS headers
|
|
|
|
### Monitoring & Alerting
|
|
- [ ] Set up security alerting
|
|
- [ ] Configure StateEncryptionException monitoring
|
|
- [ ] Monitor encryption key usage patterns
|
|
- [ ] Set up MAC verification failure alerts
|
|
- [ ] Test incident response procedures
|
|
|
|
### Security Testing
|
|
- [ ] Conduct security penetration test
|
|
- [ ] Test state tampering detection (MAC verification)
|
|
- [ ] Verify encrypted state cannot be decrypted without key
|
|
- [ ] Test key rotation process
|
|
- [ ] Validate error handling doesn't leak sensitive info
|
|
|
|
---
|
|
|
|
## Security Contacts
|
|
|
|
**Security Issues**: Report to security@example.com
|
|
**Bug Bounty**: https://example.com/security/bug-bounty
|
|
**Security Documentation**: This file and related guides
|