gdpr
gdprbreach-notificationarticle-33article-34

GDPR Breach Notification: 72-Hour Response Guide for PostgreSQL Teams

Handle GDPR Article 33/34 breach notification from PostgreSQL. 72-hour DPA reporting, structured logging, and post-incident documentation.

RL
Robert Langner
Managing Director, NILS Software GmbH · · 3 min read

The 72-Hour Clock

When a breach is discovered, the clock starts. You have 72 hours to notify the DPA. This is not a lot of time when you're also trying to contain the incident.

Having a structured process in place before a breach occurs is critical.

Documenting a Breach

SELECT pgcomply.report_breach(
  'Staging database credentials exposed in public Git repository',
  'Developer committed .env file containing staging DB credentials to public GitHub repo. Staging DB contains copy of production user data from November snapshot.',
  'high',
  data_types := ARRAY['email', 'name', 'address', 'phone'],
  affected_tables := ARRAY['users', 'profiles'],
  cause := 'Human error — .env not in .gitignore',
  subjects_count := 200
);

-- Output:
-- NOTICE: Breach BR-2026-0003 logged. DPA notification deadline: 2026-02-23 09:15:00

Tracking Progress

SELECT * FROM pgcomply.breach_status();

Shows all breaches with: ID, severity, status (open/investigating/contained/resolved), DPA deadline, hours remaining, reporting status, and subject notification status.

Updating a Breach

As investigation progresses:

SELECT pgcomply.update_breach(
  'BR-2026-0003',
  status := 'contained',
  remediation := 'Credentials rotated, repo made private, .gitignore updated. Staging DB wiped and recreated with anonymized data.'
);

-- After DPA notification:
SELECT pgcomply.update_breach(
  'BR-2026-0003',
  reported_to_dpa := true,
  subjects_notified := true,
  status := 'resolved'
);

Preventing Future Breaches

The best breach management is prevention:

-- Detect staging environments with real PII
SELECT * FROM pgcomply.schema_drift();

-- Ensure masking is active on all PII tables
SELECT * FROM pgcomply.masking_status();

-- Verify health check catches configuration issues
SELECT * FROM pgcomply.health_check();

Real-World Breach Scenarios

Scenario 1: Credential Leak (Most Common)

A developer commits database credentials to a public repository:

SELECT pgcomply.report_breach(
  'Database credentials exposed in public GitHub repository',
  'Developer pushed .env file with production DATABASE_URL to public repo. Repo had 3 forks before discovery.',
  'major',
  data_types := ARRAY['email', 'name', 'phone', 'address'],
  affected_tables := ARRAY['users', 'profiles', 'orders'],
  cause := 'Human error: .env not in .gitignore, no pre-commit hook',
  subjects_count := 4200
);

Immediate actions:

-- 1. Rotate credentials
ALTER ROLE app_writer PASSWORD 'new_strong_password_here';

-- 2. Check if anyone accessed the database
SELECT * FROM pgcomply.session_tracking();
SELECT * FROM pgcomply.dml_history('users', NOW() - INTERVAL '48 hours');

-- 3. Verify data integrity
SELECT pgcomply.verify_audit();

Scenario 2: SQL Injection (Severe)

Application vulnerability allows unauthorized data access:

SELECT pgcomply.report_breach(
  'SQL injection via search endpoint',
  'WAF alert: UNION-based SQL injection on /api/search. Attacker extracted users table. RLS was not enabled.',
  'critical',
  data_types := ARRAY['email', 'name', 'password_hash'],
  affected_tables := ARRAY['users'],
  cause := 'Unsanitized user input in raw SQL query',
  subjects_count := 15000
);

Scenario 3: Excessive Access (Subtle)

An analytics role had more access than needed:

SELECT pgcomply.report_breach(
  'Analytics role accessed unmasked PII for 3 months',
  'Access review found analyst role had SELECT on raw users table instead of masked view.',
  'minor',
  data_types := ARRAY['email', 'phone'],
  affected_tables := ARRAY['users'],
  cause := 'Overly permissive GRANT during onboarding, no periodic access review',
  subjects_count := 12000
);

This one is subtle: no malicious intent, no external actor, but still a reportable incident if the analyst accessed data they shouldn't have seen.

DPA Contact Information

Keep your supervisory authority contact information ready:

-- Store DPA contacts in pgcomply tags
SELECT pgcomply.tag('_compliance', 'dpa_authority', 'Landesbeauftragter für Datenschutz Schleswig-Holstein');
SELECT pgcomply.tag('_compliance', 'dpa_contact', 'mail@datenschutzzentrum.de');
SELECT pgcomply.tag('_compliance', 'dpa_phone', '+49 431 988-1200');
SELECT pgcomply.tag('_compliance', 'dpo_name', 'Robert Langner');

Summary

Breach notification is a race against the clock. pgcomply.report_breach() gives you a structured process: document immediately, track the deadline, update as you investigate, and maintain the evidence trail. The best time to set this up is before you need it.

Frequently Asked Questions

When does the 72-hour breach notification deadline start?
The 72-hour period begins when your organization becomes 'aware' of a breach. Awareness means when you have reasonable certainty that a security incident has compromised personal data. Preliminary investigation time to confirm a breach is generally acceptable, but deliberate delays are not.
What information must the DPA notification contain?
The notification must describe: the nature of the breach, categories and approximate number of affected individuals, the name and contact of the DPO, likely consequences, and measures taken to mitigate. pgcomply.breach_status() tracks all these fields.
Do I need to notify affected individuals?
Article 34 requires notification to individuals only when the breach is likely to result in HIGH risk to their rights. If you have encryption or masking in place that renders the data unintelligible, individual notification may not be required.

Related Articles