Users can request and receive a copy of all personal data held about them
Why it matters
GDPR Art. 15 grants every EU data subject the right to receive a copy of all personal data held about them, within 30 days of requesting it. An application with no data export feature and no DSAR process is not compliant — not having a formal process is itself a violation, regardless of whether any user has actually made a request. When a subject access request does arrive (and they will, especially as GDPR awareness grows), scrambling to manually assemble a data export from multiple tables is both error-prone and legally risky. Incomplete exports — for example, returning profile fields but omitting activity history or consent records — also violate Art. 15.
Severity rationale
High because the absence of any DSAR mechanism makes it structurally impossible to comply with Art. 15's 30-day fulfillment deadline, leaving the controller in ongoing breach for every day the gap exists.
Remediation
Implement a self-service data export endpoint in app/api/user/export/route.ts that gathers all personal data held about the authenticated user.
export async function GET() {
const session = await getServerSession()
if (!session?.user?.id) return new Response('Unauthorized', { status: 401 })
const userId = session.user.id
const [profile, activity, purchases, consents] = await Promise.all([
db.user.findUnique({ where: { id: userId }, select: { id: true, email: true, name: true, createdAt: true } }),
db.activityLog.findMany({ where: { userId } }),
db.order.findMany({ where: { userId }, include: { items: true } }),
db.consentRecord.findMany({ where: { userId } }),
])
return new Response(JSON.stringify({ _meta: { exportedAt: new Date().toISOString() }, profile, activity, purchases, consents }, null, 2), {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="my-data-${Date.now()}.json"`,
},
})
}
Add a "Download my data" button in account settings. Log each export request with a timestamp to demonstrate compliance with the 30-day fulfillment requirement per Art. 12(3).
Detection
-
ID:
right-to-access -
Severity:
high -
What to look for: Look for a user settings or profile page with a "Download My Data," "Request My Data," or "Data Subject Access Request" option. Check API routes for a data export or DSAR endpoint (common paths:
/api/user/export,/api/account/download,/api/dsar,/api/me/data). Inspect what the export includes: does it cover all major data types held about the user — profile fields, activity history, uploaded content, messages, purchase history, consent records? Is the response delivered immediately, or is there an email-based fulfillment workflow? GDPR requires DSARs to be fulfilled within 30 days. If using an email-based workflow, check whether there is a documented process for handling and tracking requests within the deadline. Count every data store containing personal data (primary database, analytics, logs, third-party services) and enumerate which are included in the SAR response vs. which are omitted. -
Pass criteria: Authenticated users can initiate a data access request from a settings or profile page, or by contacting a documented privacy contact. The resulting export covers all personal data held about the user. If automated, it is delivered immediately or within a documented timeframe. If manual, the process is documented and tracks the 30-day deadline. Report even on pass: "SAR response covers X of Y identified data stores. Export format includes all required personal data categories." Threshold: no more than 30 days to fulfill a subject access request.
-
Fail criteria: No data access feature or DSAR process exists. Export exists but is incomplete (e.g., only profile info, missing activity history or purchases). No documented process for handling email-based requests.
-
Skip (N/A) when: Application collects no personal data beyond authentication credentials and has no content, history, or profile data to export.
-
Cross-reference: The
right-to-portabilitycheck verifies the machine-readable export format that complements this human-readable access right. -
Detail on fail: Example:
"No data export feature and no documented DSAR process. Users cannot request a copy of their data."or"Export endpoint exists but only returns profile fields; activity history and consent records are excluded."or"Privacy policy says to email privacy@example.com but no internal DSAR tracking process documented.". -
Remediation: Implement a self-service data export endpoint:
// app/api/user/export/route.ts import { getServerSession } from 'next-auth' import { db } from '@/lib/db' export async function GET() { const session = await getServerSession() if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }) const userId = session.user.id // Gather ALL personal data held about this user const [profile, activity, purchases, consents, preferences] = await Promise.all([ db.user.findUnique({ where: { id: userId }, select: { id: true, email: true, name: true, createdAt: true, updatedAt: true } }), db.activityLog.findMany({ where: { userId }, orderBy: { createdAt: 'desc' } }), db.order.findMany({ where: { userId }, include: { items: true } }), db.consentRecord.findMany({ where: { userId }, orderBy: { recordedAt: 'desc' } }), db.userPreference.findUnique({ where: { userId } }), ]) const exportData = { _meta: { exportedAt: new Date().toISOString(), formatVersion: '1.0', description: 'Personal data export — all data held about you.', }, profile, activityHistory: activity, purchases, consentHistory: consents, preferences, } return new Response(JSON.stringify(exportData, null, 2), { headers: { 'Content-Type': 'application/json', 'Content-Disposition': `attachment; filename="my-data-${Date.now()}.json"`, }, }) }Add a "Download my data" button in the account settings page. Log each DSAR request with a timestamp to demonstrate compliance with the 30-day fulfillment requirement.
External references
- gdpr · Art. 15 — Right of access by the data subject
- gdpr · Art. 12 — Transparent information, communication and modalities
- nist:rev5 · IP-3 — Personally identifiable information processing and transparency
Taxons
History
- 2026-04-18·v1.0.0·Initial import from gdpr-readiness·automated