Data Export Controls
Admin Safety · ADM-20 · Priority: P1
Why It Matters
Admin panels in AI-built apps often include "Export" buttons that dump entire database tables into CSV or JSON — with no size limits, no access logging, and no approval workflow. A compromised admin account — or a disgruntled employee — can export your entire user database in one click.
Data export without controls violates GDPR's data minimization principle, SOC 2 access control requirements, and basic security hygiene. If you ever need to demonstrate to a customer or auditor who exported what data and when, you need export controls.
Priority: P1 — Bulk data exfiltration risk. Compliance requirement for enterprise customers.
Affected Stack: Any framework with admin functionality
The Problem
// ❌ Unrestricted bulk export
export async function GET(req: Request) {
const allUsers = await supabase
.from('users')
.select('*'); // All fields, all rows, no limit
return new Response(JSON.stringify(allUsers.data), {
headers: { 'Content-Type': 'application/json' },
});
}
This endpoint returns every user record with every field — emails, billing info, metadata — to anyone with admin access, with no record that the export happened.
The Fix
1. Limit export scope
// ✅ Controlled export with limits and field filtering
export async function POST(req: Request) {
const admin = await getVerifiedAdmin(req);
if (!admin) return new Response('Forbidden', { status: 403 });
const { fields, limit = 100, offset = 0 } = await req.json();
// Allowlist of exportable fields
const ALLOWED_FIELDS = ['id', 'email', 'created_at', 'plan', 'status'];
const safeFields = fields.filter((f: string) => ALLOWED_FIELDS.includes(f));
const { data } = await supabase
.from('users')
.select(safeFields.join(','))
.range(offset, offset + Math.min(limit, 1000)); // Max 1000 per request
return Response.json(data);
}
2. Log every export
// ✅ Audit log for exports
await supabaseAdmin.from('admin_audit_log').insert({
admin_id: admin.id,
action: 'data.export',
target_type: 'users',
details: { fields: safeFields, count: data?.length, offset },
ip_address: req.headers.get('x-forwarded-for'),
});
3. Require MFA for bulk exports
For exports exceeding a threshold (e.g., >100 rows or sensitive fields), require MFA re-verification before allowing the export.
Key controls:
- Field-level allowlist (never export password hashes, tokens, secrets)
- Row limits per request (max 1000, paginated)
- Audit logging for every export (who, what, when, how many)
- MFA challenge for bulk or sensitive exports
- Rate limiting on export endpoints
References
Related Checks
- Admin Audit Log — ADM-04
- MFA for Admin Roles — ADM-13
- No Client-Side-Only Role Checks — ADM-03
Is your app safe? Run Free Scan →