Security & Compliance
Status: Final Version: 1.0
Purpose
Expand security requirements beyond RBAC to cover encryption, PII handling, threat modeling, access logging, and regulatory compliance (GDPR, SOC 2).
Encryption
At Rest
Database Encryption:
- PostgreSQL: Enable Transparent Data Encryption (TDE) or use encrypted EBS volumes
- Laravel: Encrypt PII fields using Crypt facade
class MetricSubmission extends Model
{
protected $casts = [
'pii_data' => 'encrypted:array', // Laravel 9+ encrypted casting
];
}
File Storage Encryption:
- S3: Enable Server-Side Encryption (SSE-AES256 or SSE-KMS)
- Evidence bucket: ServerSideEncryption: AES256
In Transit
- TLS 1.2+ required for all HTTPS connections
- Certificate Management: AWS Certificate Manager or Let's Encrypt
- HSTS Header:
Strict-Transport-Security: max-age=31536000; includeSubDomains
PII Handling (GDPR Compliance)
Identified PII Fields
| Field/Metric | PII Type | Location | Handling |
|---|---|---|---|
| Employee names | Direct PII | Social metrics (diversity, training) | Encrypted, access-logged |
| Email addresses | Direct PII | users table |
Encrypted |
| IP addresses | Indirect PII | audit_logs.ip_address |
Anonymized after 90 days |
GDPR Rights Implementation
Right to Erasure (Article 17)
class GdprService
{
public function eraseUserData(User $user)
{
DB::transaction(function () use ($user) {
// Anonymize user record
$user->update([
'name' => 'User ' . $user->id . ' (Deleted)',
'email' => 'deleted_' . $user->id . '@anonymized.local',
'phone' => null,
]);
// Remove PII from submissions (keep aggregated data)
$user->submissions()->update([
'pii_data' => null,
'metadata->collector_notes' => '[Redacted]',
]);
// Keep audit trail but anonymize actor
AuditLog::where('actor_user_id', $user->id)
->update(['actor_user_id' => null]);
AuditLog::log('gdpr.erasure_requested', $user, null, 'User data anonymized per GDPR Article 17');
});
}
}
Right to Data Portability (Article 20)
public function exportUserData(User $user)
{
return [
'user' => $user->only(['name', 'email', 'created_at']),
'submissions' => $user->submissions()->with('evidence')->get(),
'audit_log' => AuditLog::where('actor_user_id', $user->id)->get(),
];
}
Secrets Management
Never Store in Code/Env Files: - Database passwords - API keys - Encryption keys - AWS credentials
Use AWS Secrets Manager or HashiCorp Vault:
// config/database.php
'pgsql' => [
'password' => env('DB_PASSWORD') ?: app(SecretsManager::class)->get('db_password'),
],
Access Logging
Requirement: Log all access to PII-containing metrics and evidence files.
class AuditMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
// Log if accessing PII
if ($this->containsPII($request)) {
AuditLog::create([
'action' => 'pii.accessed',
'entity_type' => $request->route()->getName(),
'actor_user_id' => auth()->id(),
'ip_address' => $request->ip(),
'metadata' => [
'url' => $request->fullUrl(),
'method' => $request->method(),
],
]);
}
return $response;
}
}
Threat Model
Top 5 Abuse Cases & Mitigations
| Threat | Attack Vector | Mitigation |
|---|---|---|
| Data Tampering | Attacker modifies approved submissions | Immutable submissions, content hashing, audit logs |
| Fraudulent Evidence | Collector uploads fake invoices | Virus scanning, manual reviewer checks, cross-reference with external data |
| Replay Attacks | Attacker re-submits old data | Idempotency keys (UUID), timestamp validation |
| Insider Approval Abuse | Approver signs off own submissions | Segregation of duties (policy enforcement), audit log alerts |
| Credential Stuffing | Brute force login attempts | Rate limiting (5 attempts/min), CAPTCHA after 3 failures, account lockout |
Additional Threats (vNext)
- SQL Injection: Laravel Eloquent ORM prevents (parameterized queries)
- XSS: Laravel Blade auto-escapes output
- CSRF: Laravel CSRF tokens on all POST/PUT/DELETE
- Mass Assignment:
$fillablewhitelists in all models
Least Privilege
Database User Permissions
| User | Permissions | Use Case |
|---|---|---|
app_user |
SELECT, INSERT, UPDATE (no DELETE) | Application runtime |
migration_user |
ALL (DDL + DML) | Laravel migrations only |
readonly_user |
SELECT only | Reporting, analytics |
API Service Accounts
- Collector API: Can only create submissions, not approve
- Admin API: Full CRUD, restricted by RBAC policies
Compliance Standards
| Standard | Requirement | Implementation |
|---|---|---|
| GDPR | Right to erasure, portability, consent | PII anonymization, data export API |
| SOC 2 Type II | Access controls, audit logging, encryption | RBAC, append-only audit logs, TLS/encryption at rest |
| ISO 27001 | Information security management | Documented security policies, risk assessments |
Acceptance Criteria
- Database encryption enabled (TDE or encrypted volumes)
- Evidence files stored with S3 SSE encryption
- All HTTPS connections use TLS 1.2+
- PII fields encrypted using Laravel
Crypt - GDPR erasure workflow implemented (anonymization)
- All PII access logged to audit trail
- Secrets stored in AWS Secrets Manager (not .env)
- SoD enforced: users cannot approve own submissions
- Rate limiting prevents brute force (5 req/min on auth endpoints)
Cross-References
Change Log
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-03 | Senior Product Architect | Initial security & compliance specification |