From sentry-pack
Configures PII scrubbing, GDPR compliance, data retention, and audit controls in Sentry using beforeSend hooks, server-side rules, and API for data deletion.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin sentry-packThis skill is limited to using the following tools:
Configure PII scrubbing, GDPR compliance, data retention, and audit controls for Sentry. This skill covers client-side filtering with `beforeSend`, server-side scrubbing rules, data subject erasure via API, and SOC 2 compliance patterns.
Configures Sentry security for PII scrubbing, DSN/auth token handling, IP anonymization, data protection, and GDPR compliance using Node/TS and Python SDKs.
Checks and configures Sentry error tracking SDKs for frontend, Next.js, Node.js, and Python projects including DSN env vars, source maps, and CI/CD release tracking.
Sets up full Sentry SDK for Python apps including error monitoring, tracing, profiling, logging, metrics, crons, and AI monitoring. Supports Django, Flask, FastAPI, Celery, Starlette, AIOHTTP, Tornado.
Share bugs, ideas, or general feedback.
Configure PII scrubbing, GDPR compliance, data retention, and audit controls for Sentry. This skill covers client-side filtering with beforeSend, server-side scrubbing rules, data subject erasure via API, and SOC 2 compliance patterns.
Sentry captures error context that often contains personally identifiable information (PII) — emails in stack traces, credit card numbers in request bodies, IP addresses in headers. Production deployments must scrub this data at two layers: client-side via beforeSend hooks (before data leaves the application) and server-side via Sentry's built-in Data Scrubber (defense in depth). GDPR requires additional controls: consent-based initialization, data subject deletion endpoints, and a signed Data Processing Agreement. This skill implements all three layers with TypeScript and Python examples, plus verification tests to prove scrubbing works end-to-end.
@sentry/node or sentry-sdk)project:write and org:admin scopes for API operationsThe first defense layer prevents PII from leaving your application. Configure beforeSend, beforeSendTransaction, and beforeBreadcrumb hooks during SDK initialization:
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
// CRITICAL: disable automatic PII collection
// When false, Sentry will NOT capture IP addresses, cookies, or user-agent
sendDefaultPii: false,
beforeSend(event) {
return scrubEvent(event);
},
beforeSendTransaction(event) {
return scrubEvent(event);
},
beforeBreadcrumb(breadcrumb) {
if (breadcrumb.data) {
const sensitiveKeys = ['password', 'token', 'secret', 'api_key', 'authorization'];
for (const key of sensitiveKeys) {
if (breadcrumb.data[key]) {
breadcrumb.data[key] = '[REDACTED]';
}
}
}
return breadcrumb;
},
});
Implement the scrubEvent function to strip PII from headers, request bodies, error messages, and user context:
function scrubEvent(event: Sentry.Event): Sentry.Event | null {
// Strip sensitive headers
if (event.request?.headers) {
const redactHeaders = ['Authorization', 'Cookie', 'X-Api-Key', 'X-Auth-Token'];
for (const header of redactHeaders) {
delete event.request.headers[header];
}
}
// Scrub request body fields
if (event.request?.data) {
const data = typeof event.request.data === 'string'
? safeJsonParse(event.request.data)
: event.request.data;
if (data && typeof data === 'object') {
scrubObject(data as Record<string, unknown>);
event.request.data = JSON.stringify(data);
}
}
// Scrub PII patterns from error messages
if (event.exception?.values) {
for (const exc of event.exception.values) {
if (exc.value) {
exc.value = scrubPiiPatterns(exc.value);
}
}
}
// Reduce user context to anonymous ID only
if (event.user) {
event.user = { id: event.user.id };
}
return event;
}
function scrubObject(obj: Record<string, unknown>): void {
const sensitiveKeys = [
'password', 'passwd', 'secret', 'token', 'api_key', 'apiKey',
'ssn', 'social_security', 'credit_card', 'cc_number', 'cvv',
'email', 'phone', 'address', 'dob', 'date_of_birth',
];
for (const key of Object.keys(obj)) {
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
obj[key] = '[REDACTED]';
} else if (typeof obj[key] === 'string') {
obj[key] = scrubPiiPatterns(obj[key] as string);
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
scrubObject(obj[key] as Record<string, unknown>);
}
}
}
function scrubPiiPatterns(str: string): string {
return str
.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[EMAIL]')
.replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{1,7}\b/g, '[CC_NUMBER]')
.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]')
.replace(/\b(\+1)?[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{4}\b/g, '[PHONE]');
}
function safeJsonParse(str: string): unknown {
try { return JSON.parse(str); } catch { return null; }
}
Python equivalent — use the same before_send pattern:
import sentry_sdk
import re
def scrub_event(event, hint):
"""Remove PII from Sentry events before transmission."""
# Strip sensitive headers
request = event.get("request", {})
headers = request.get("headers", {})
for key in ["Authorization", "Cookie", "X-Api-Key"]:
headers.pop(key, None)
# Scrub user context to anonymous ID
user = event.get("user")
if user:
event["user"] = {"id": user.get("id")}
# Scrub PII patterns from exception messages
for exc in event.get("exception", {}).get("values", []):
if exc.get("value"):
exc["value"] = re.sub(
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
"[EMAIL]", exc["value"]
)
return event
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
send_default_pii=False,
before_send=scrub_event,
traces_sample_rate=0.1,
)
Server-side scrubbing acts as defense in depth. Configure in Project Settings > Security & Privacy:
password, secret, token, api_key, ssn, credit_card, cvv, authorizationtransaction_id, order_id, request_id, trace_idFor advanced regex-based rules, navigate to Project Settings > Security & Privacy > Advanced Data Scrubbing:
# Remove credit card patterns from all string fields
[Remove] [Regex: \d{4}-\d{4}-\d{4}-\d{4}] from [$string]
# Remove email addresses everywhere
[Remove] [Regex: \b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b] from [$string]
# Remove SSN patterns
[Remove] [Regex: \b\d{3}-\d{2}-\d{4}\b] from [$string]
# Mask passwords in request bodies
[Mask] [Password] from [extra.request_body]
# Replace credit card data everywhere with placeholder
[Replace] [Credit card] with [REDACTED] from [**]
Data forwarding — if forwarding events to external systems (Splunk, BigQuery), apply the same scrubbing rules at the destination. Configure forwarding in Project Settings > Data Forwarding.
Data retention — configure in Organization Settings > Subscription > Data Retention:
| Plan | Default retention | Maximum retention |
|---|---|---|
| Developer | 30 days | 30 days |
| Team | 90 days | 90 days |
| Business | 90 days | 365 days |
| Enterprise | 90 days | Custom |
Right to be Informed — document Sentry usage in your privacy policy. Disclose what data is collected (stack traces, device info, anonymized user IDs) and the legal basis (legitimate interest in application reliability).
Consent-based initialization — for strict GDPR compliance, gate Sentry on user consent:
function initSentryWithConsent(hasConsent: boolean): void {
if (!hasConsent) {
// Do not initialize Sentry — no data sent
return;
}
Sentry.init({
dsn: process.env.SENTRY_DSN,
sendDefaultPii: false,
beforeSend: scrubEvent,
});
}
Right to Erasure (Article 17) — delete user data via the Sentry API:
# Delete all events for a specific issue
curl -X DELETE \
-H "Authorization: Bearer ${SENTRY_AUTH_TOKEN}" \
"https://sentry.io/api/0/projects/${SENTRY_ORG}/${SENTRY_PROJECT}/issues/${ISSUE_ID}/" \
|| { echo "ERROR: Deletion failed — verify auth token has project:admin scope"; exit 1; }
// Programmatic deletion for data subject requests
async function handleDeletionRequest(userId: string): Promise<void> {
const org = process.env.SENTRY_ORG;
const project = process.env.SENTRY_PROJECT;
const token = process.env.SENTRY_AUTH_TOKEN;
// Search for issues containing user data
const searchRes = await fetch(
`https://sentry.io/api/0/projects/${org}/${project}/issues/?query=user.id:${userId}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
if (!searchRes.ok) {
throw new Error(`Search failed: ${searchRes.status} ${searchRes.statusText}`);
}
const issues = await searchRes.json();
// Delete each matching issue
for (const issue of issues) {
const deleteRes = await fetch(
`https://sentry.io/api/0/projects/${org}/${project}/issues/${issue.id}/`,
{ method: 'DELETE', headers: { Authorization: `Bearer ${token}` } }
);
if (!deleteRes.ok) {
throw new Error(`Deletion failed for issue ${issue.id}: ${deleteRes.status}`);
}
}
console.log(`Deleted ${issues.length} issues for user ${userId}`);
}
Audit log access — Business and Enterprise plans provide audit logs at Organization Settings > Audit Log. Export via API for SOC 2 evidence:
# Retrieve audit log entries (requires org:admin scope)
curl -H "Authorization: Bearer ${SENTRY_AUTH_TOKEN}" \
"https://sentry.io/api/0/organizations/${SENTRY_ORG}/audit-logs/" \
|| echo "ERROR: Audit logs require Business or Enterprise plan"
SOC 2 compliance checklist:
After completing all three steps, your Sentry deployment will have:
beforeSend removing sensitive headers, request bodies, and PII patterns (emails, SSNs, credit cards, phone numbers)| Error | Cause | Solution |
|---|---|---|
| PII still visible in events | beforeSend not matching patterns | Run verification test below; check regex coverage against your data |
| Over-scrubbing useful data | Safe fields not configured | Add field names to the Safe Fields list in Project Settings |
401 Unauthorized on deletion API | Token missing project:admin scope | Regenerate auth token with correct scopes at Settings > Auth Tokens |
| Audit logs unavailable | Developer or Team plan | Upgrade to Business or Enterprise for audit log access |
sendDefaultPii: true in production | Environment-unaware configuration | Gate PII collection: sendDefaultPii: process.env.NODE_ENV !== 'production' |
| Data retention not applying | Plan limitation | Verify retention settings match plan tier in Organization Settings |
| GDPR erasure request timeout | Large volume of matching issues | Batch deletions with rate limiting; use ?cursor= pagination |
Example 1: GDPR-Compliant Node.js Setup (TypeScript)
Request: "Configure Sentry for GDPR compliance in a Node.js Express app"
import * as Sentry from '@sentry/node';
import express from 'express';
// Initialize with full compliance configuration
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || 'development',
sendDefaultPii: false,
beforeSend(event) {
// Strip all user PII except anonymous ID
if (event.user) {
event.user = { id: event.user.id };
}
// Remove auth headers
if (event.request?.headers) {
delete event.request.headers['Authorization'];
delete event.request.headers['Cookie'];
}
return event;
},
});
const app = express();
// GDPR deletion endpoint
app.delete('/api/gdpr/erasure/:userId', async (req, res) => {
const { userId } = req.params;
try {
await handleDeletionRequest(userId);
res.json({ status: 'deleted', userId });
} catch (error) {
Sentry.captureException(error);
res.status(500).json({ error: 'Deletion failed' });
}
});
Result: PII scrubbing active, IP anonymization enabled server-side, consent-based init, deletion endpoint at /api/gdpr/erasure/:userId.
Example 2: HIPAA-Strict Python Configuration
Request: "Lock down Sentry for a healthcare application — no PHI can be transmitted"
import sentry_sdk
import os
def hipaa_scrub(event, hint):
"""Remove ALL user-identifiable information for HIPAA compliance."""
# Remove entire user context
event.pop("user", None)
# Remove all request headers (may contain PHI in auth tokens)
request = event.get("request", {})
request.pop("headers", None)
request.pop("cookies", None)
request.pop("data", None) # Request body may contain PHI
# Keep only technical error data
return event
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"),
send_default_pii=False,
before_send=hipaa_scrub,
traces_sample_rate=0.05, # Minimal tracing to reduce data exposure
)
Result: Zero user PII transmitted — only stack traces, file paths, and technical metadata reach Sentry.
Example 3: Verify Scrubbing Works
Request: "Test that our PII scrubbing actually removes sensitive data"
// Verification test — send an event with known PII and confirm it's scrubbed
Sentry.withScope((scope) => {
scope.setUser({
id: 'verify-test-001',
email: 'should-be-scrubbed@example.com',
ip_address: '192.168.1.100',
});
scope.setContext('test_data', {
password: 'should-be-scrubbed',
credit_card: '4111-1111-1111-1111',
api_key: 'sk_live_should_be_scrubbed',
safe_field: 'this-should-remain-visible',
});
Sentry.captureMessage('Data scrubbing verification test');
});
// Check the event in Sentry dashboard:
// - email: missing (stripped by beforeSend)
// - ip_address: missing (sendDefaultPii: false)
// - password: [REDACTED]
// - credit_card: [REDACTED]
// - api_key: [REDACTED]
// - safe_field: "this-should-remain-visible" (preserved)
sentry-release-management skillsentry-rate-limits skillsentry-enterprise-rbac skillsentry-performance-tracing skill