Clerk Data HandlingSAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-08Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: clerk-data-handling description: 'Handle user data, privacy, and GDPR compliance with Clerk. Use when implementing data export, user deletion, or privacy compliance features. Trigger with phrases like "clerk user data", "clerk GDPR", "clerk privacy", "clerk data export", "clerk delete user". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Grep version: 1.15.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - saas - clerk - compliance compatibility: Designed for Claude Code --- # Clerk Data Handling ## Overview Manage user data, implement privacy features, and ensure GDPR/CCPA compliance using the Clerk Backend API. Covers data export, right to be forgotten, consent management, and audit logging. ## Prerequisites - Clerk integration working - Understanding of GDPR/CCPA requirements - Database with user-related data linked by Clerk user IDs ## Instructions ### Step 1: User Data Export ```typescript // app/api/privacy/export/route.ts import { auth, clerkClient } from '@clerk/nextjs/server' export async function GET() { const { userId } = await auth() if (!userId) return Response.json({ error: 'Unauthorized' }, { status: 401 }) const client = await clerkClient() const clerkUser = await client.users.getUser(userId) // Gather data from Clerk const clerkData = { id: clerkUser.id, emails: clerkUser.emailAddresses.map((e) => e.emailAddress), firstName: clerkUser.firstName, lastName: clerkUser.lastName, createdAt: clerkUser.createdAt, lastSignInAt: clerkUser.lastSignInAt, publicMetadata: clerkUser.publicMetadata, } // Gather data from your database const appData = await db.user.findUnique({ where: { clerkId: userId }, include: { posts: true, comments: true, preferences: true }, }) return Response.json({ exportDate: new Date().toISOString(), clerkProfile: clerkData, applicationData: appData, }) } ``` ### Step 2: User Deletion (Right to be Forgotten) ```typescript // app/api/privacy/delete/route.ts import { auth, clerkClient } from '@clerk/nextjs/server' export async function DELETE() { const { userId } = await auth() if (!userId) return Response.json({ error: 'Unauthorized' }, { status: 401 }) const deletionLog: { step: string; status: string }[] = [] try { // 1. Delete application data first await db.comment.deleteMany({ where: { authorId: userId } }) deletionLog.push({ step: 'comments', status: 'deleted' }) await db.post.deleteMany({ where: { authorId: userId } }) deletionLog.push({ step: 'posts', status: 'deleted' }) await db.user.delete({ where: { clerkId: userId } }) deletionLog.push({ step: 'app_user', status: 'deleted' }) // 2. Delete from Clerk (this ends the session) const client = await clerkClient() await client.users.deleteUser(userId) deletionLog.push({ step: 'clerk_user', status: 'deleted' }) // 3. Log deletion for compliance audit trail await db.auditLog.create({ data: { action: 'USER_DELETED', subjectId: userId, details: JSON.stringify(deletionLog), timestamp: new Date(), }, }) return Response.json({ deleted: true, log: deletionLog }) } catch (error) { return Response.json({ error: 'Partial deletion', log: deletionLog }, { status: 500 }) } } ``` ### Step 3: Consent Management with Metadata ```typescript // lib/consent.ts import { clerkClient } from '@clerk/nextjs/server' interface ConsentRecord { marketing: boolean analytics: boolean thirdParty: boolean updatedAt: string } export async function updateConsent(userId: string, consent: Partial<ConsentRecord>) { const client = await clerkClient() const user = await client.users.getUser(userId) const existing = (user.publicMetadata.consent as ConsentRecord) || {} const updated: ConsentRecord = { ...existing, ...consent, updatedAt: new Date().toISOString(), } await client.users.updateUser(userId, { publicMetadata: { ...user.publicMetadata, consent: updated }, }) return updated } export async function getConsent(userId: string): Promise<ConsentRecord | null> { const client = await clerkClient() const user = await client.users.getUser(userId) return (user.publicMetadata.consent as ConsentRecord) || null } ``` ### Step 4: Consent UI Component ```typescript 'use client' import { useUser } from '@clerk/nextjs' import { useState } from 'react' export function ConsentManager() { const { user } = useUser() const consent = (user?.publicMetadata as any)?.consent || {} const [marketing, setMarketing] = useState(consent.marketing ?? false) const [analytics, setAnalytics] = useState(consent.analytics ?? true) const saveConsent = async () => { await fetch('/api/privacy/consent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ marketing, analytics }), }) } return ( <div> <h3>Privacy Preferences</h3> <label> <input type="checkbox" checked={marketing} onChange={(e) => setMarketing(e.target.checked)} /> Marketing communications </label> <label> <input type="checkbox" checked={analytics} onChange={(e) => setAnalytics(e.target.checked)} /> Analytics tracking </label> <button onClick={saveConsent}>Save Preferences</button> </div> ) } ``` ### Step 5: Audit Logging via Webhooks ```typescript // app/api/webhooks/clerk/route.ts (audit section) async function logAuditEvent(evt: WebhookEvent) { const auditEntry = { eventType: evt.type, userId: 'user_id' in evt.data ? evt.data.user_id : evt.data.id, timestamp: new Date().toISOString(), metadata: JSON.stringify(evt.data), } await db.auditLog.create({ data: auditEntry }) // Track compliance-relevant events if (['user.deleted', 'user.updated'].includes(evt.type)) { console.log(`[COMPLIANCE] ${evt.type} for user
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__clerk-data-handling.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-08 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Clerk Data Handling skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Clerk Data Handling safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Clerk Data Handling access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Clerk Data Handling work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-08. The repository is watched, and a new audit runs when it changes — this is the first audit.