gdpr-technical-controls — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited gdpr-technical-controls (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
GDPR (and its UK / EEA siblings) is a legal framework with a heavy technical surface. This skill stays on the engineering side: what to build, what to log, what to expose, what to delete. For DACH-specific Impressum/AGB/AVV requirements, see dach-compliance. For data-protection legal questions, talk to a DPO.
The terms used here line up with the regulation but are paraphrased. When in doubt, the regulation text is the source of truth.
You cannot protect data you cannot enumerate. Build and maintain a record of:
users.email, orders.shipping_address.street, …)A reasonable starting format:
Field | Location | Purpose / basis | Retention | Processors
-------------------------|--------------------------|------------------------|-----------|-----------
user.email | postgres.users | account (contract) | until-deletion + 30d backup decay | Mailgun (transactional)
user.password_hash | postgres.users | auth (contract) | until-deletion | -
order.shipping_address | postgres.order_addresses | fulfillment (contract) | 10y (tax) | DHL (delivery)
user.ip | nginx access logs | abuse (legit interest) | 90d | -
user.session_cookie | redis, nginx logs | session (contract) | session+30d | -This is the foundation for SARs, deletion requests, breach scoping, and processor-agreement enumeration. Keep it in a private repo or wiki — version-controlled, not in a one-off spreadsheet.
A user can ask for a copy of all their personal data. You have one month to respond (extendable to three with reason).
Don't build this manually per request. Build a SAR endpoint or admin tool that produces the export programmatically.
// Sketch — admin tool that produces a SAR bundle
async function generateSAR(userId: string) {
const bundle: Record<string, unknown> = {};
bundle.profile = await db.users.findUnique({ where: { id: userId }, select: safeProfileFields });
bundle.orders = await db.orders.findMany({ where: { userId }, select: orderFieldsForSAR });
bundle.addresses = await db.addresses.findMany({ where: { userId }});
bundle.support_tickets = await db.tickets.findMany({ where: { userEmail: bundle.profile.email }});
bundle.audit_log = await db.authEvents.findMany({ where: { userId }, take: 1000 });
bundle.consent_history = await db.consents.findMany({ where: { userId }});
// Files / media — include URLs to time-limited download links
bundle.uploads = await listUserUploads(userId);
return bundle; // serialize to JSON or ZIP
}Patterns:
A user can ask to be deleted (Article 17). You generally have one month.
Hard problem: most apps have data scattered across services, with foreign keys, with cached copies in third parties.
async function deleteUser(userId: string) {
// 1. Soft-mark for deletion, prevent further logins immediately
await db.users.update({ where: { id: userId }, data: { deletedAt: new Date(), status: 'deleting' }});
await invalidateAllSessions(userId);
// 2. Anonymize records that must be retained for legal reasons (tax invoices etc.)
await db.invoices.updateMany({
where: { userId },
data: { userId: null, customerName: 'DELETED USER', customerEmail: null }
});
// 3. Hard-delete records with no retention obligation
await db.preferences.deleteMany({ where: { userId }});
await db.sessions.deleteMany({ where: { userId }});
await db.notifications.deleteMany({ where: { userId }});
// 4. Files
await deleteAllUserUploads(userId);
// 5. Third-party processors — propagate via their APIs
await mailgunApi.removeContact(user.email);
await stripeApi.customers.delete(user.stripeCustomerId);
await intercomApi.users.archive(user.intercomId);
// 6. Backups — flag for purge on next cycle
await db.backupPurgeQueue.create({ userId, scheduledFor: nextBackupCycle });
// 7. Finally, remove the user record itself
await db.users.delete({ where: { id: userId }});
}Patterns:
user_842 instead of [email protected]). Re-identification is possible if you have the mapping. Still considered personal data under GDPR.True anonymization is harder than it sounds. Removing names doesn't anonymize if a single record can be uniquely identified (e.g. "person aged 47, working in Eisenstadt, with a Tesla" might be one person).
Use pseudonymization in development / staging / analytics. Reserve true anonymization for data leaving the EU regime entirely.
Logs are often the worst PII leak. The app sanitizes inputs; the logger doesn't know about that.
// Log middleware — strip known sensitive headers before recording
function safeReqLog(req) {
return {
method: req.method,
path: req.path,
ip: hashIp(req.ip), // hash, not raw
userAgent: req.headers['user-agent'],
requestId: req.headers['x-request-id'],
// Explicitly NOT: authorization, cookie, body
};
}
// JSON loggers — strip in serializer
const REDACT_KEYS = new Set(['password', 'token', 'cookie', 'authorization', 'email', 'phone']);
function redactingReplacer(key, value) {
return REDACT_KEYS.has(key.toLowerCase()) ? '[REDACTED]' : value;
}Rules:
xxx.xxx.xxx.0 or HMAC-with-rotating-key is safer.log-strategy. 30 days hot is plenty for most operational needs; longer needs justification.Article 33 requires notifying the supervisory authority within 72 hours of becoming aware of a personal-data breach. Article 34 requires notifying affected data subjects if the breach is likely to result in "high risk to the rights and freedoms" of individuals.
Engineering preparations:
incident-response.Every third party that processes personal data on your behalf — analytics, email, CRM, support, AI, CDN, hosting — is a sub-processor and requires a Data Processing Agreement (DPA, German: AVV) before going live.
Engineering responsibilities:
dach-compliance); transactional email is usually contract-based and does not.EU personal data leaving the EEA needs a transfer mechanism: adequacy decision, Standard Contractual Clauses (SCCs), or a derogation. The post-Schrems-II / Data Privacy Framework landscape changes; check current status before relying on US transfers.
Engineering knobs:
Engineering preparation for a GDPR audit / SAR / breach:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.