GDPR Compliance Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
gdpr-compliance | 🛡️ Security | Medium | 30 minutes |
What This Checks
Scans app/Models/User.php, app/Models/Profile.php, app/Models/Customer.php, database/migrations/, routes/web.php, routes/api.php, and config/logging.php for technical GDPR compliance signals. Checks for:
- Data deletion capability (Article 17) -
SoftDeletestrait, anonymize/purge method on the User model, or a dedicatedUserDeletionService/UserErasureService - Encryption at rest -
encryptedcasts inUser,Profile, andCustomermodels for sensitive personal fields - Privacy policy route - a
/privacyor/privacy-policyroute in web or API route files - Data export capability (Article 20) - an export controller, export route,
app/Exports/directory, orexport/downloadDatamethod on the User model - PII protection in logs - log sanitization (
SanitizeLog,mask,redact,scrub,replace_placeholders) inconfig/logging.phpor a custom processor inapp/Logging/
SoftDeletes is not sufficient for true erasure
SoftDeletes preserves all personal data in the database: soft-deleted records are still readable by database admins and analytical tools. GDPR Article 17 requires that data be truly erased or anonymized. Add an anonymize() method alongside SoftDeletes, or use the Prunable / MassPrunable traits to schedule automatic hard-deletion of soft-deleted records after a retention period.
Why It Matters
- Legal Obligation: GDPR fines can reach 4% of annual global turnover or EUR 20 million
- Right to Erasure (Art. 17): Users must be able to request deletion or anonymization of their personal data
- Data Protection (Art. 25): Sensitive personal data must be encrypted at rest
- Lawful Basis (Art. 6): All processing requires a documented lawful basis — typically contractual necessity for SaaS core features, or consent for marketing and non-essential processing
- Transparency (Art. 13/14): Users must be informed about how their data is processed via a privacy policy
How to Fix
Quick Fix (10 minutes)
Add a privacy policy route and data deletion capability:
// routes/web.php
Route::get('/privacy-policy', [PageController::class, 'privacy'])
->name('privacy-policy');
// app/Models/User.php
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Authenticatable
{
use SoftDeletes;
public function anonymize(): void
{
$this->update([
'name' => 'Deleted User',
'email' => 'deleted-' . $this->id . '@anonymized.local',
]);
$this->delete();
}
}Proper Fix (30 minutes)
1. Encrypt sensitive personal data:
class User extends Authenticatable
{
protected $casts = [
'phone' => 'encrypted',
'address' => 'encrypted',
'date_of_birth' => 'encrypted:date',
];
}2. Implement true erasure alongside SoftDeletes:
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Authenticatable
{
use SoftDeletes;
public function anonymize(): void
{
$this->update([
'name' => 'Deleted User',
'email' => 'deleted-' . $this->id . '@anonymized.local',
'phone' => null,
]);
$this->delete(); // soft-delete after anonymization
}
}3. Add data export endpoint (Article 20: Right to Data Portability):
// app/Http/Controllers/UserDataExportController.php
class UserDataExportController extends Controller
{
public function export(Request $request): JsonResponse
{
$user = $request->user();
$data = [
'personal' => $user->only(['name', 'email', 'phone']),
'orders' => $user->orders->toArray(),
'consents' => $user->consents->toArray(),
];
return response()->json($data)
->header('Content-Disposition', 'attachment; filename="my-data.json"');
}
}// routes/web.php
Route::get('/user/export', [UserDataExportController::class, 'export'])
->middleware('auth')
->name('user.data.export');4. Add PII protection to logging:
// app/Logging/SanitizePiiProcessor.php
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
class SanitizePiiProcessor implements ProcessorInterface
{
private array $keys = ['email', 'phone', 'ip', 'password', 'token'];
public function __invoke(LogRecord $record): LogRecord
{
$extra = $record->extra;
foreach ($this->keys as $key) {
if (isset($extra[$key])) {
$extra[$key] = '***';
}
}
return $record->with(extra: $extra);
}
}// config/logging.php — register the processor on your default channel
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'processors' => [SanitizePiiProcessor::class],
],
],References
- GDPR Official Text
- GDPR Article 17 - Right to Erasure
- GDPR Article 20 - Right to Data Portability
- Laravel Encryption Casts
- Laravel Model Pruning (SoftDeletes)
- OWASP Privacy Cheat Sheet
Related Analyzers
- Audit Logging - Validates audit trail logging
- Cookie - Checks cookie security configuration
- Telescope Security - Validates debug tool security