Back to skills
SKILL.md
Gdpr Mauriciodelrio
ASecurityUse this skill when the software processes personal data from users. Although GDPR is EU legislation, this skill applies as a data protection standard for any software regardless of the geographic location of users. Covers granular consent system, data subject rights (ARCO+), encryption and pseudonymization, retention policy, cookie banner, and compliance checklist.
- 85 stars
- 0 votes
- 0 copies
- 0 views
- Added October 8, 2026
Works with
Security analysis
100/100npx -y skills add ThomasMoreAI/legal-skills-open --skill gdpr-mauriciodelrio --agent claude-codeAre you the author of Gdpr Mauriciodelrio?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/thomasmoreai-gdpr-mauriciodelrio)---
name: gdpr-mauriciodelrio
title: GDPR — General Data Protection Regulation
description: Use this skill when the software processes personal data from users. Although GDPR is EU legislation, this skill applies as a data protection standard for any software regardless of the geographic location of users. Covers granular consent system, data subject rights (ARCO+), encryption and pseudonymization, retention policy, cookie banner, and compliance checklist.
author: mauriciodelrio
author_url: https://github.com/mauriciodelrio/delriodev-skills/tree/main/en-skills/governance-risk-and-compliance/gdpr
license: MIT
version: 0.1.0
execution_mode: open
jurisdiction: eu
practice: data-protection
language: en
---
# GDPR — General Data Protection Regulation
GDPR (EU Regulation 2016/679) defines the most rigorous data protection standard globally and should be applied to any software processing personal data, regardless of user geography. Every implementation must respect the 7 principles of Art. 5: lawfulness/fairness/transparency, purpose limitation, data minimization, accuracy, storage limitation, integrity/confidentiality, and accountability. Each processing activity requires a valid legal basis (Art. 6): consent, contract, legal obligation, vital interests, public interest, or legitimate interest. Sensitive data (Art. 9) requires additional legal bases.
## Implementation
### 1. Consent System
#### Database Schema (Prisma)
```prisma
// schema.prisma — Granular consent model
model User {
id String @id @default(cuid())
email String @unique
// Personal data with protection metadata
firstName String?
lastName String?
phone String?
dateOfBirth DateTime?
// Compliance relations
consents Consent[]
dataRequests DataSubjectRequest[]
auditLogs AuditLog[]
// Creation and retention metadata
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete for retention period
retentionExpiresAt DateTime? // Retention expiration date
@@map("users")
}
model Consent {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
// Granular consent type
purpose ConsentPurpose
legalBasis LegalBasis
// Status and traceability
granted Boolean
grantedAt DateTime?
revokedAt DateTime?
expiresAt DateTime?
// Consent evidence
ipAddress String? // IP at the time of consent
userAgent String? // Browser/device
consentText String // Exact text the user accepted
version String // Privacy policy version
source String // Where it was obtained (web, app, api)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, purpose]) // One consent per purpose per user
@@index([userId])
@@index([purpose])
@@map("consents")
}
enum ConsentPurpose {
ESSENTIAL // Basic service functionality
ANALYTICS // Usage analysis and metrics
MARKETING_EMAIL // Marketing communications by email
MARKETING_PUSH // Marketing push notifications
THIRD_PARTY_SHARING // Sharing data with third parties
PROFILING // User profile creation
COOKIES_FUNCTIONAL // Functional cookies
COOKIES_ANALYTICS // Analytics cookies
COOKIES_ADVERTISING // Advertising cookies
}
enum LegalBasis {
CONSENT
CONTRACT
LEGAL_OBLIGATION
VITAL_INTERESTS
PUBLIC_INTEREST
LEGITIMATE_INTEREST
}
```
#### Consent Service
```typescript
// services/consent.service.ts
import { PrismaClient, ConsentPurpose, LegalBasis } from '@prisma/client';
import { z } from 'zod';
import pino from 'pino';
const prisma = new PrismaClient();
const logger = pino({ name: 'consent-service' });
// Validation schema for granting consent
const GrantConsentSchema = z.object({
userId: z.string().cuid(),
purpose: z.nativeEnum(ConsentPurpose),
legalBasis: z.nativeEnum(LegalBasis),
consentText: z.string().min(10),
version: z.string(),
source: z.enum(['web', 'mobile_app', 'api']),
});
type GrantConsentInput = z.infer<typeof GrantConsentSchema>;
interface ConsentContext {
ipAddress: string;
userAgent: string;
}
export class ConsentService {
/**
* Grant consent in a granular manner.
* GDPR Art. 7: Consent must be freely given, specific, informed, and unambiguous.
*/
async grantConsent(input: GrantConsentInput, context: ConsentContext) {
const validated = GrantConsentSchema.parse(input);
const consent = await prisma.consent.upsert({
where: {
userId_purpose: {
userId: validated.userId,
purpose: validated.purpose,
},
},
create: {
userId: validated.userId,
purpose: validated.purpose,
legalBasis: validated.legalBasis,
granted: true,
grantedAt: new Date(),
consentText: validated.consentText,
version: validated.version,
source: validated.source,
ipAddress: context.ipAddress,
userAgent: context.userAgent,
},
update: {
granted: true,
grantedAt: new Date(),
revokedAt: null,
consentText: validated.consentText,
version: validated.version,
source: validated.source,
ipAddress: context.ipAddress,
userAgent: context.userAgent,
},
});
logger.info({
event: 'consent_granted',
userId: validated.userId,
purpose: validated.purpose,
legalBasis: validated.legalBasis,
version: validated.version,
}, 'Consent granted');
return consent;
}
/**
* Revoke consent.
* GDPR Art. 7(3): It must be as easy to withdraw as to give consent.
*/
async revokeConsent(userId: string, purpose: ConsentPurpose) {
const consent = await prisma.consent.update({
where: {
userId_purpose: { userId, purpose },
},
data: {
granted: false,
revokedAt: new Date(),
},
});
logger.info({
event: 'consent_revoked',
userId,
purpose,
}, 'Consent revoked');
// Trigger cleanup of data associated with this purpose
await this.triggerDataCleanup(userId, purpose);
return consent;
}
/**
* Check if a user has active consent for a purpose.
*/
async hasConsent(userId: string, purpose: ConsentPurpose): Promise<boolean> {
const consent = await prisma.consent.findUnique({
where: {
userId_purpose: { userId, purpose },
},
});
if (!consent) return false;
if (!consent.granted) return false;
if (consent.revokedAt) return false;
if (consent.expiresAt && consent.expiresAt < new Date()) return false;
return true;
}
/**
* Get all consents for a user (for privacy panel).
*/
async getUserConsents(userId: string) {
return prisma.consent.findMany({
where: { userId },
orderBy: { purpose: 'asc' },
});
}
private async triggerDataCleanup(userId: string, purpose: ConsentPurpose) {
// Implement cleanup logic based on the revoked purpose
// For example: if ANALYTICS is revoked, delete tracking data
// If MARKETING_EMAIL is revoked, unsubscribe from email lists
logger.info({
event: 'data_cleanup_triggered',
userId,
purpose,
}, 'Data cleanup triggered by consent revocation');
}
}
```
#### Consent Verification Middleware
```typescript
// middleware/consent.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { ConsentPurpose } from '@prisma/client';
import { ConsentService } from '../services/consent.service';
const consentService = new ConsentService();
/**
* Middleware factory that verifies consent before processing the request.
* Use it on routes that require specific consent.
*
* @example
* router.post('/newsletter/subscribe', requireConsent(ConsentPurpose.MARKETING_EMAIL), handler);
*/
export function requireConsent(purpose: ConsentPurpose) {
return async (req: Request, res: Response, next: NextFunction) => {
const userId = req.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Authentication required' });
}
const hasConsent = await consentService.hasConsent(userId, purpose);
if (!hasConsent) {
return res.status(403).json({
error: 'Consent required',
code: 'CONSENT_REQUIRED',
purpose,
message: `Consent required for: ${purpose}`,
consentUrl: `/api/v1/privacy/consent`,
});
}
next();
};
}
```
---
### 2. Data Subject Rights (ARCO+ Rights)
#### Schema for Rights Requests
```prisma
// Add to schema.prisma
model DataSubjectRequest {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
type DSRType
status DSRStatus @default(PENDING)
// Request details
description String?
requestedAt DateTime @default(now())
acknowledgedAt DateTime? // Receipt confirmation
completedAt DateTime? // Resolution date
deadline DateTime // Maximum deadline (30 days from request)
// Response
responseNotes String?
responseData Json? // Exported data (for portability)
// Audit
processedBy String? // ID of the admin who processed
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([status])
@@index([deadline])
@@map("data_subject_requests")
}
enum DSRType {
ACCESS // Art. 15 - Right of access
RECTIFICATION // Art. 16 - Right to rectification
ERASURE // Art. 17 - Right to be forgotten
RESTRICTION // Art. 18 - Right to restriction
PORTABILITY // Art. 20 - Right to data portability
OBJECTION // Art. 21 - Right to object
AUTOMATED_DECISION // Art. 22 - Not to be subject to automated decisions
}
enum DSRStatus {
PENDING
ACKNOWLEDGED
IN_PROGRESS
COMPLETED
REJECTED
EXTENDED // Extended deadline (max 2 additional months)
}
```
#### Data Subject Rights Service
```typescript
// services/data-subject-rights.service.ts
import { PrismaClient, DSRType, DSRStatus } from '@prisma/client';
import pino from 'pino';
import crypto from 'node:crypto';
const prisma = new PrismaClient();
const logger = pino({ name: 'dsr-service' });
// GDPR deadline: 30 calendar days from the request
const GDPR_DEADLINE_DAYS = 30;
const GDPR_EXTENSION_DAYS = 60; // Maximum additional extension
export class DataSubjectRightsService {
/**
* Create a rights request.
* GDPR Art. 12(3): Respond without undue delay, maximum 1 month.
*/
async createRequest(userId: string, type: DSRType, description?: string) {
const deadline = new Date();
deadline.setDate(deadline.getDate() + GDPR_DEADLINE_DAYS);
const request = await prisma.dataSubjectRequest.create({
data: {
userId,
type,
description,
deadline,
acknowledgedAt: new Date(), // Confirm immediate receipt
status: DSRStatus.ACKNOWLEDGED,
},
});
logger.info({
event: 'dsr_created',
requestId: request.id,
userId,
type,
deadline: deadline.toISOString(),
}, 'Data subject rights request created');
return request;
}
/**
* Right of Access (Art. 15).
* Export ALL personal data of the user in structured format.
*/
async processAccessRequest(requestId: string, processedBy: string) {
const request = await prisma.dataSubjectRequest.findUniqueOrThrow({
where: { id: requestId },
include: { user: true },
});
// Collect all personal data for the user
const userData = await this.collectAllUserData(request.userId);
await prisma.dataSubjectRequest.update({
where: { id: requestId },
data: {
status: DSRStatus.COMPLETED,
completedAt: new Date(),
processedBy,
responseData: userData,
},
});
logger.info({
event: 'dsr_access_completed',
requestId,
userId: request.userId,
processedBy,
}, 'Access request completed');
return userData;
}
/**
* Right to be Forgotten (Art. 17).
* Delete all personal data of the user.
* Exceptions: legal obligations, defense of claims.
*/
async processErasureRequest(requestId: string, processedBy: string) {
const request = await prisma.dataSubjectRequest.findUniqueOrThrow({
where: { id: requestId },
});
// Check if there are legal exceptions that prevent deletion
const exceptions = await this.checkErasureExceptions(request.userId);
if (exceptions.length > 0) {
await prisma.dataSubjectRequest.update({
where: { id: requestId },
data: {
status: DSRStatus.REJECTED,
responseNotes: `Partial deletion. Legal exceptions: ${exceptions.join(', ')}`,
processedBy,
},
});
logger.warn({
event: 'dsr_erasure_partial',
requestId,
userId: request.userId,
exceptions,
}, 'Partial deletion due to legal exceptions');
// Delete only data without legal restriction
await this.partialErasure(request.userId, exceptions);
return { partial: true, exceptions };
}
// Full deletion
await this.fullErasure(request.userId);
await prisma.dataSubjectRequest.update({
where: { id: requestId },
data: {
status: DSRStatus.COMPLETED,
completedAt: new Date(),
processedBy,
},
});
logger.info({
event: 'dsr_erasure_completed',
requestId,
userId: request.userId,
processedBy,
}, 'Erasure request completed');
return { partial: false };
}
/**
* Right to Data Portability (Art. 20).
* Export data in a structured, commonly used, and machine-readable format.
*/
async processPortabilityRequest(requestId: string, processedBy: string) {
const request = await prisma.dataSubjectRequest.findUniqueOrThrow({
where: { id: requestId },
});
const userData = await this.collectAllUserData(request.userId);
// Structured JSON format (machine-readable)
const portableData = {
exportMetadata: {
format: 'JSON',
exportDate: new Date().toISOString(),
dataController: process.env.DATA_CONTROLLER_NAME,
requestId,
},
personalData: userData,
};
await prisma.dataSubjectRequest.update({
where: { id: requestId },
data: {
status: DSRStatus.COMPLETED,
completedAt: new Date(),
processedBy,
responseData: portableData,
},
});
return portableData;
}
/**
* Collect all personal data of the user from all tables.
*/
private async collectAllUserData(userId: string) {
const [user, consents, orders, activityLogs] = await Promise.all([
prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
firstName: true,
lastName: true,
phone: true,
dateOfBirth: true,
createdAt: true,
},
}),
prisma.consent.findMany({
where: { userId },
select: {
purpose: true,
granted: true,
grantedAt: true,
revokedAt: true,
version: true,
},
}),
// Add more tables according to your data model
// prisma.order.findMany({ where: { userId } }),
// prisma.address.findMany({ where: { userId } }),
Promise.resolve([]), // Placeholder for orders
Promise.resolve([]), // Placeholder for activity logs
]);
return {
profile: user,
consents,
orders,
activityLogs,
};
}
/**
* Check legal exceptions for deletion.
* Example: invoices must be retained due to tax obligations.
*/
private async checkErasureExceptions(userId: string): Promise<string[]> {
const exceptions: string[] = [];
// Example: Check if there are invoices (legal tax retention obligation)
// const invoices = await prisma.invoice.count({ where: { userId } });
// if (invoices > 0) {
// exceptions.push('invoices_tax_obligation');
// }
return exceptions;
}
private async fullErasure(userId: string) {
await prisma.$transaction(async (tx) => {
// Anonymize instead of hard-delete to maintain referential integrity
await tx.user.update({
where: { id: userId },
data: {
email: `deleted_${crypto.randomUUID()}@anonymized.local`,
firstName: null,
lastName: null,
phone: null,
dateOfBirth: null,
deletedAt: new Date(),
},
});
// Delete consents
await tx.consent.deleteMany({ where: { userId } });
});
}
private async partialErasure(userId: string, exceptions: string[]) {
// Delete only data that has no legal restriction
await prisma.user.update({
where: { id: userId },
data: {
firstName: null,
lastName: null,
phone: null,
dateOfBirth: null,
// Keep email if needed for contractual obligations
},
});
}
}
```
#### Rights API (Express Router)
```typescript
// routes/privacy.routes.ts
import { Router, Request, Response } from 'express';
import { z } from 'zod';
import { DSRType } from '@prisma/client';
import { DataSubjectRightsService } from '../services/data-subject-rights.service';
import { ConsentService } from '../services/consent.service';
import { authenticate } from '../middleware/auth.middleware';
const router = Router();
const dsrService = new DataSubjectRightsService();
const consentService = new ConsentService();
// All routes require authentication
router.use(authenticate);
/**
* GET /api/v1/privacy/my-data
* Get all personal data (Art. 15 - Access)
*/
router.get('/my-data', async (req: Request, res: Response) => {
const request = await dsrService.createRequest(
req.user!.id,
DSRType.ACCESS,
'Personal data access request via API'
);
// For automated requests, process immediately
const data = await dsrService.processAccessRequest(request.id, 'system_auto');
res.json({
requestId: request.id,
data,
exportFormats: ['json', 'csv'],
downloadUrl: `/api/v1/privacy/requests/${request.id}/download`,
});
});
/**
* POST /api/v1/privacy/erasure
* Request data deletion (Art. 17 - Right to be forgotten)
*/
router.post('/erasure', async (req: Request, res: Response) => {
const request = await dsrService.createRequest(
req.user!.id,
DSRType.ERASURE,
req.body.reason
);
res.status(202).json({
requestId: request.id,
status: 'acknowledged',
message: 'Your deletion request has been received. It will be processed within a maximum of 30 days.',
deadline: request.deadline,
});
});
/**
* POST /api/v1/privacy/portability
* Export data in portable format (Art. 20)
*/
router.post('/portability', async (req: Request, res: Response) => {
const request = await dsrService.createRequest(
req.user!.id,
DSRType.PORTABILITY,
'Data portability request'
);
const data = await dsrService.processPortabilityRequest(request.id, 'system_auto');
res.json({
requestId: request.id,
format: 'application/json',
data,
});
});
/**
* GET /api/v1/privacy/consents
* View all user consents
*/
router.get('/consents', async (req: Request, res: Response) => {
const consents = await consentService.getUserConsents(req.user!.id);
res.json({ consents });
});
/**
* POST /api/v1/privacy/consents
* Grant a consent
*/
router.post('/consents', async (req: Request, res: Response) => {
const consent = await consentService.grantConsent(
{ ...req.body, userId: req.user!.id },
{ ipAddress: req.ip ?? 'unknown', userAgent: req.headers['user-agent'] ?? 'unknown' }
);
res.status(201).json({ consent });
});
/**
* DELETE /api/v1/privacy/consents/:purpose
* Revoke a specific consent
*/
router.delete('/consents/:purpose', async (req: Request, res: Response) => {
const purpose = req.params.purpose as any;
const consent = await consentService.revokeConsent(req.user!.id, purpose);
res.json({ consent, message: 'Consent successfully revoked' });
});
export default router;
```
---
### 3. Encryption and Pseudonymization
```typescript
// utils/encryption.service.ts
import crypto from 'node:crypto';
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
const SALT_LENGTH = 32;
/**
* Encryption service for personal data at rest.
* GDPR Art. 32: Appropriate technical measures, including encryption.
*/
export class EncryptionService {
private readonly key: Buffer;
constructor() {
const masterKey = process.env.DATA_ENCRYPTION_KEY;
if (!masterKey || masterKey.length < 32) {
throw new Error('DATA_ENCRYPTION_KEY must be configured with at least 32 characters');
}
// Derive key using HKDF for greater security
this.key = crypto.createHash('sha256').update(masterKey).digest();
}
/**
* Encrypt a personal data field.
* Returns string with format: iv:authTag:ciphertext (all in hex).
*/
encrypt(plaintext: string): string {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, this.key, iv, {
authTagLength: AUTH_TAG_LENGTH,
});
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
/**
* Decrypt a personal data field.
*/
decrypt(encryptedText: string): string {
const [ivHex, authTagHex, ciphertext] = encryptedText.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv(ALGORITHM, this.key, iv, {
authTagLength: AUTH_TAG_LENGTH,
});
decipher.setAuthTag(authTag);
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* Pseudonymize personal data (reversible process with the key).
* GDPR Art. 4(5): Processing in such a manner that it can no longer
* be attributed without additional information.
*/
pseudonymize(data: string): string {
const hmac = crypto.createHmac('sha256', this.key);
hmac.update(data);
return hmac.digest('hex');
}
/**
* Anonymize data (irreversible process).
* Anonymized data is no longer personal data under GDPR.
*/
static anonymize(data: string): string {
const salt = crypto.randomBytes(SALT_LENGTH);
return crypto.createHash('sha256').update(salt).update(data).digest('hex');
}
/**
* Secure password hash (use bcrypt or argon2 in production).
*/
static async hashPassword(password: string): Promise<string> {
// In production use: import bcrypt from 'bcrypt'; return bcrypt.hash(password, 12);
const salt = crypto.randomBytes(16).toString('hex');
return new Promise((resolve, reject) => {
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) reject(err);
resolve(`${salt}:${derivedKey.toString('hex')}`);
});
});
}
}
```
---
### 4. Data Retention Policy
```typescript
// jobs/data-retention.job.ts
import { PrismaClient } from '@prisma/client';
import pino from 'pino';
const prisma = new PrismaClient();
const logger = pino({ name: 'data-retention' });
/**
* Data retention policy per GDPR Art. 5(1)(e).
* "Storage limitation": data shall not be kept longer than
* necessary for the purposes of the processing.
*
* Run as a daily cron job.
*/
interface RetentionPolicy {
model: string;
field: string; // Date field to evaluate retention
retentionDays: number; // Retention days
action: 'anonymize' | 'delete' | 'archive';
legalBasis: string; // Legal justification for retention
}
const RETENTION_POLICIES: RetentionPolicy[] = [
{
model: 'auditLog',
field: 'createdAt',
retentionDays: 365 * 2, // 2 years
action: 'archive',
legalBasis: 'Legal obligation to retain audit records',
},
{
model: 'session',
field: 'lastActivityAt',
retentionDays: 90,
action: 'delete',
legalBasis: 'Inactive sessions are not necessary for the service',
},
{
model: 'deletedUser',
field: 'deletedAt',
retentionDays: 30, // 30-day grace period post-deletion
action: 'delete',
legalBasis: 'Grace period completed, definitive deletion',
},
{
model: 'analyticsEvent',
field: 'createdAt',
retentionDays: 180,
action: 'anonymize',
legalBasis: 'Anonymized analytics data for aggregate statistics',
},
];
export async function executeRetentionPolicies() {
logger.info({ event: 'retention_job_started' }, 'Starting data retention job');
for (const policy of RETENTION_POLICIES) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - policy.retentionDays);
try {
// Example for the session model
if (policy.model === 'session' && policy.action === 'delete') {
const result = await prisma.session.deleteMany({
where: {
[policy.field]: { lt: cutoffDate },
},
});
logger.info({
event: 'retention_policy_executed',
model: policy.model,
action: policy.action,
recordsAffected: result.count,
cutoffDate: cutoffDate.toISOString(),
legalBasis: policy.legalBasis,
}, `Retention policy executed: ${policy.model}`);
}
// Add more models as needed
} catch (error) {
logger.error({
event: 'retention_policy_error',
model: policy.model,
error,
}, `Error executing retention policy: ${policy.model}`);
}
}
logger.info({ event: 'retention_job_completed' }, 'Data retention job completed');
}
```
---
### 5. Cookie Banner and Cookie Consent
```typescript
// middleware/cookie-consent.middleware.ts
import { Request, Response, NextFunction } from 'express';
/**
* Cookie categories per GDPR and ePrivacy Directive.
* Only essential cookies can be used without consent.
*/
enum CookieCategory {
ESSENTIAL = 'essential', // No consent required
FUNCTIONAL = 'functional', // Requires consent
ANALYTICS = 'analytics', // Requires consent
ADVERTISING = 'advertising', // Requires consent
}
interface CookiePreferences {
essential: true; // Always true, cannot be disabled
functional: boolean;
analytics: boolean;
advertising: boolean;
}
/**
* Middleware that checks the user's cookie preferences
* before setting non-essential cookies.
*/
export function cookieConsentGuard(category: CookieCategory) {
return (req: Request, res: Response, next: NextFunction) => {
if (category === CookieCategory.ESSENTIAL) {
return next(); // Essential cookies are always allowed
}
const consentCookie = req.cookies?.cookie_consent;
if (!consentCookie) {
// No consent: do not set non-essential cookies
return next();
}
try {
const preferences: CookiePreferences = JSON.parse(consentCookie);
if (!preferences[category]) {
// User did not consent to this category
// Do not set the cookie and continue
res.locals.cookieBlocked = category;
return next();
}
next();
} catch {
next(); // On error, do not set cookies
}
};
}
/**
* Helper to set cookies respecting consent.
*/
export function setConsentAwareCookie(
res: Response,
name: string,
value: string,
category: CookieCategory,
options: { maxAge?: number; httpOnly?: boolean; secure?: boolean; sameSite?: 'strict' | 'lax' | 'none' } = {}
) {
if (res.locals.cookieBlocked === category) {
return; // Cookie blocked due to lack of consent
}
res.cookie(name, value, {
httpOnly: options.httpOnly ?? true,
secure: options.secure ?? process.env.NODE_ENV === 'production',
sameSite: options.sameSite ?? 'lax',
maxAge: options.maxAge,
path: '/',
});
}
```
---
## Agent workflow
1. Identify what personal data the application processes and document the legal basis (Art. 6) for each processing activity.
2. Implement the granular consent system with Prisma schema, service, and verification middleware.
3. Implement the data subject rights service (access, rectification, erasure, restriction, portability, objection).
4. Add the privacy REST API with endpoints for data access, deletion, portability, and consent management.
5. Implement encryption of personal data at rest (AES-256-GCM) and pseudonymization where applicable.
6. Configure the data retention policy as a cron job with per-model actions (anonymize/delete/archive).
7. Implement cookie banner with granular consent per category (essential/functional/analytics/advertising).
8. Validate against the compliance checklist before deploying.
## Gotchas
Implement Privacy by Design and Privacy by Default (Art. 25): every new feature must be evaluated against GDPR before implementation, and the most restrictive privacy settings are the defaults. Do not use pre-checked consent — it must be a clear affirmative action (Art. 7). Withdrawing consent must be as easy as granting it. Maintain a Record of Processing Activities (ROPA, Art. 30). Conduct DPIAs (Art. 35) for large-scale processing, profiling, or new technologies. Security breaches must be reported to the supervisory authority within 72 hours (Art. 33) and to affected individuals without delay if there is high risk (Art. 34). Do not transfer data outside the EU without a legal mechanism (SCCs, adequacy, BCRs). Do not store data without a retention policy. Do not log personal data in plain text. Do not use personal data in development/testing environments without anonymizing.
---
## GDPR Compliance Checklist
- [ ] Legal basis documented for each data processing activity
- [ ] Granular consent system implemented
- [ ] Clear and accessible privacy policy
- [ ] Data subject rights API (access, deletion, portability, rectification)
- [ ] Encryption of personal data at rest and in transit
- [ ] Data retention policy defined and implemented
- [ ] Cookie banner with granular consent
- [ ] Record of Processing Activities (ROPA)
- [ ] Breach notification process (72 hours)
- [ ] DPIA for high-risk processing
- [ ] Legal mechanism for international transfers
- [ ] Contracts with data processors
- [ ] DPO designated (if applicable per Art. 37)
- [ ] Audit logs for personal data access
- [ ] Pseudonymization/anonymization where possible
- [ ] Development team training on GDPR
---
## References and Resources
- [Official GDPR Text](https://eur-lex.europa.eu/eli/reg/2016/679/oj)
- [AEPD Guidelines (Spain)](https://www.aepd.es/)
- [ICO Guidelines (UK)](https://ico.org.uk/for-organisations/guide-to-data-protection/)
- [EDPB Guidelines](https://edpb.europa.eu/our-work-tools/general-guidance_en)
- [GDPR Enforcement Tracker](https://www.enforcementtracker.com/)
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.