GDPR Privacy Engineering skill for Codex
Know every copy of personal data, and delete it. Codex inventories personal data across databases, logs, analytics and processors into a data map kept in the repo, then builds one DSAR orchestrator that exports and erases across every store, retention jobs that actually run, and privacy by design checks for new features. Legal calls such as lawful basis and DPIA outcomes are flagged for the privacy lead and counsel.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- gdpr-privacy-engineering Installs to ~/.agents/skills/gdpr-privacy-engineering/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install gdpr-privacy-engineering The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project gdpr-privacy-engineering Installs to .agents/skills/gdpr-privacy-engineering/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/gdpr-privacy-engineering.zipunzip -o gdpr-privacy-engineering.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) gdpr-privacy-engineering PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $gdpr-privacy-engineering, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Implements GDPR and UK GDPR requirements in code and infrastructure - personal data inventory from schemas and code, records of processing, retention and deletion jobs, data subject access and erasure (DSAR) pipelines, pseudonymisation, consent records, processor mapping and privacy by design reviews of new features. Use when asked to find where personal data lives, build a DSAR export or account deletion flow, enforce retention periods, prepare engineering input for a DPIA, or check a feature for privacy issues before launch.
Use it when
- You need a map of where personal data lives in the system
- Access, export or erasure requests are handled manually and slowly
- Retention periods exist on paper but no job enforces them
- A new feature collects personal or special category data
Use something else when
- You need the public privacy policy text: use privacy-policy-drafting
- You are reviewing a vendor DPA or contract terms: use contract-review-checklist
How to check the result
A seeded test subject is exported completely and then erased from every store in staging, leaving zero matching rows outside documented exceptions, and retention job counts drop to zero past the limit.
The mistake it prevents
Treating soft deletes, unsalted email hashes or pseudonymised analytics IDs as erasure or anonymisation when they are still personal data.
Done well means: every store of personal data is known, has a purpose and a retention period, and is reached by the export and deletion paths; nothing is collected that the feature does not need. Engineering builds the machinery; the lawful basis, retention periods and DPIA conclusions are decided by the privacy lead or DPO and reviewed by a lawyer. Say that plainly whenever a step depends on a legal judgement.
Inventory before building
- Read
AGENTS.md, then map data stores: databases, caches, search indexes, object storage, queues, data warehouse, logs, analytics, backups, email/SMS providers, support tools, error trackers. - Find personal data in schemas and code:
rg -n -i "email|phone|first_?name|last_?name|address|birth|dob|ssn|national_id|passport|ip_?address|lat(itude)?|lon(gitude)?|device_id|advertising_id" \
--glob '*.{sql,prisma,py,ts,rb,go,java,kt,cs}' migrations/ schema/ src/ app/
rg -n -i "logger\.|console\.log|log\.(info|debug)" src | rg -i "email|user|token|address"
rg -n "posthog|segment|mixpanel|amplitude|ga4|gtag|sentry|datadog|intercom|hubspot" src package.json
- Classify each field: identifier, contact, special category (health, biometric, religion, union membership, sexual orientation, ethnicity: Art. 9, needs a separate condition), financial, location, behavioural, children’s data.
- Record it in a machine-readable data map kept in the repo.
# privacy/data-map.yaml (one entry per store and field group)
- store: postgres.users
fields: [email, display_name, last_login_ip]
category: contact, identifier
purpose: account management, security
lawful_basis: contract (Art. 6(1)(b)); security logging legitimate interest # set by DPO
retention: account lifetime + 30 days
deletion: hard delete via AccountErasureJob
processors: [AWS eu-west-1]
transfers: none outside EEA
This feeds the Article 30 record of processing; the privacy lead owns the final record.
Rights requests (DSARs)
Deadline: one month from receipt, extendable by two further months for complex or numerous requests, with the extension explained to the person within the first month.
| Right | Engineering deliverable |
|---|---|
| Access (Art. 15) | Export of all personal data across stores, plus purposes and recipients |
| Rectification (Art. 16) | Self-service edits, propagated to downstream copies |
| Erasure (Art. 17) | Deletion across stores and processors, with documented exceptions (legal hold, tax records) |
| Portability (Art. 20) | Data the person provided, in JSON or CSV |
| Objection / restriction (Art. 18, 21) | Flags that stop marketing or processing without deleting |
Build one orchestrator that fans out to every store in the data map, so new stores are added in one place:
# Each store registers an exporter and an eraser; the orchestrator logs every outcome.
class StoreHandler(Protocol):
name: str
def export(self, subject_id: str) -> dict: ...
def erase(self, subject_id: str) -> int: ... # rows/objects affected
def erase_subject(subject_id: str, handlers: list[StoreHandler], request_id: str) -> dict:
results = {}
for h in handlers:
try:
results[h.name] = {"status": "ok", "affected": h.erase(subject_id)}
except Exception as exc: # one failing store must not hide the others
results[h.name] = {"status": "failed", "error": type(exc).__name__}
audit_log.info("dsar.erasure", request_id=request_id, results=results) # no PII in the log
return results
- Verify identity before acting; an export sent to an impostor is a breach. Use the existing authenticated session or a verified channel, not just a matching email.
- Erasure must reach processors: call deletion APIs (support desk, CRM, email provider, analytics user deletion endpoints) and record the confirmation.
- Running erasure in production is destructive. Run it against staging fixtures first, and require the user’s explicit confirmation before the first production run of a new handler.
Retention
- Each table or bucket with personal data gets a retention rule enforced by a scheduled job or native TTL (S3 lifecycle rules, DynamoDB TTL, Postgres partition drops, Elasticsearch ILM). A policy document without a job is not retention.
- Logs: keep IPs and user IDs only as long as security needs them (often 30-180 days, set by the privacy lead); strip query strings and bodies that may carry personal data.
- Backups: you usually cannot edit them. Document that deleted data persists until backup expiry, keep that window short, and re-apply deletions after any restore (keep a tombstone list of erased subject IDs).
- Crypto-shredding (per-user data keys, deleting the key to erase) works for event stores and immutable logs where row deletion is impossible.
Privacy by design review for a new feature
- Purpose stated; every new field justified by it (drop the rest)
- Lawful basis confirmed by the privacy lead; consent only where it is the actual basis
- Defaults are the most private option (Art. 25): opt-in for non-essential processing
- Data added to the data map, export and erasure handlers in the same PR
- Retention rule and job added
- New processors or transfers outside the EEA/UK flagged (needs DPA and transfer mechanism)
- High-risk processing (large-scale special category, systematic monitoring, profiling with significant effects) flagged for a DPIA before build, not after launch
- Pseudonymise for analytics (keyed hash with a secret salt, stored separately); remember pseudonymised data is still personal data, only anonymised data is out of scope
Consent records
When consent is the basis, store who consented, to what (versioned text ID), when, how (UI surface), and withdrawals, append-only. Cookie banners must block non-essential tags until consent; check in the browser network panel, not only in the code.
Gotchas
- Personal data in URLs ends up in access logs, analytics and referrers. Use POST bodies or opaque IDs.
- Soft-delete (
deleted_at) is not erasure. Either hard delete or anonymise in place, and make sure search indexes and caches are purged too. - Error trackers capture request bodies and local variables; configure scrubbing
(
send_default_pii=Falsein Sentry SDKs, plusbefore_sendfilters). - Hashing an email without a secret is reversible by dictionary; it is not anonymisation.
- Test and staging databases restored from production copy all of this risk. Use synthetic or masked data.
Verification
- Seed a test subject with data in every store, run export and erasure in staging, then query each store for the subject ID and email: zero rows remain except documented exceptions.
- Retention job dry-run lists the expected rows; after the run, counts older than the limit are zero.
rgthe codebase for new loggers that include personal fields; grep log samples too.- Hand the data map and DSAR test evidence to the privacy lead; final compliance sign-off is theirs and their counsel’s, not the engineering team’s.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "GDPR Privacy Engineering"
short_description: "Know every copy of personal data, and delete it"
default_prompt: "$gdpr-privacy-engineering Find every place we store user emails and IP addresses" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.