Sentry Data Handling
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.
Overview
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.
Prerequisites
- Sentry SDK v8 installed and initialized (
@sentry/nodeorsentry-sdk) - Sentry project with Admin or Owner role (required for Security & Privacy settings)
- Compliance requirements documented (GDPR, HIPAA, PCI-DSS, or SOC 2)
- Auth token with
project:writeandorg:adminscopes for API operations - Data Processing Agreement signed at https://sentry.io/legal/dpa/ (GDPR requirement)
Instructions
Step 1 — Client-Side PII Scrubbing with beforeSend
The 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,
)
Step 2 — Server-Side Data Scrubbing and IP Anonymization
Server-side scrubbing acts as defense in depth. Configure in Project Settings > Security & Privacy:
- Enable Data Scrubber — automatically redacts values matching common PII field names (password, token, secret)
- Custom Sensitive Fields — add project-specific fields:
password,secret,token,api_key,ssn,credit_card,cvv,authorization
- Safe Fields — fields that must never be scrubbed:
transaction_id,order_id,request_id,trace_id
- Scrub IP Addresses — enable to remove client IPs from all events
- Scrub Credit Cards — detect and remove card number patterns
For 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 |
Step 3 — GDPR Compliance and Data Subject Requests
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:
- Enable audit logging (Business/Enterprise plan required)
- Configure SSO/SAML for authentication
- Enable IP allowlisting for API access
- Set up regular access reviews via role-based access control
- Sign the DPA at https://sentry.io/legal/dpa/
- Document data flow in your security documentation
Output
After completing all three steps, your Sentry deployment will have:
- Client-side PII scrubbing via
beforeSendremoving sensitive headers, request bodies, and PII patterns (emails, SSNs, credit cards, phone numbers) - Server-side Data Scrubber enabled with custom sensitive fields and advanced regex rules
- IP anonymization active across all events
- GDPR-compliant consent gating and data subject deletion endpoint
- Data retention configured per organizational requirements
- Audit log access for SOC 2 compliance evidence
Error Handling
| 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 |
Examples
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)
Resources
- Data Scrubbing Rules — client-side scrubbing patterns and regex reference
- Server-Side Scrubbing — dashboard configuration and advanced rules
- GDPR Compliance — consent handling, user deletion, and pseudonymization
- Error Handling Reference — common failure modes and solutions
- Sentry Data Privacy Docs
- Advanced Data Scrubbing
- Sentry GDPR Overview
- Data Processing Agreement
- Sentry Security
Next Steps
- Configure release health to correlate errors with deployments — use the
sentry-release-managementskill - Set up rate limits to control event volume and costs — use the
sentry-rate-limitsskill - Implement role-based access control for team permissions — use the
sentry-enterprise-rbacskill - Add performance monitoring with privacy-safe tracing — use the
sentry-performance-tracingskill