Claude-code-plugins-plus sentry-policy-guardrails
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/sentry-pack/skills/sentry-policy-guardrails" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-sentry-policy-guardrails && rm -rf "$T"
manifest:
plugins/saas-packs/sentry-pack/skills/sentry-policy-guardrails/SKILL.mdsource content
Sentry Policy Guardrails
Overview
Organizational governance framework that prevents Sentry configuration drift across multiple services. A shared npm package (
@company/sentry-config) wraps Sentry.init() to enforce PII scrubbing, naming conventions, tagging standards, and per-tier trace rate caps. CI checks block policy violations before merge, and a monthly drift audit detects projects that have fallen out of compliance.
Prerequisites
v8+ installed in target services@sentry/node- Internal npm registry available (GitHub Packages, Artifactory, or similar)
- Team structure and project ownership defined in Sentry
withSENTRY_AUTH_TOKEN
andorg:read
scopesproject:read- Compliance requirements identified (SOC 2, GDPR, HIPAA)
Instructions
Step 1 — Build the Shared Configuration Package
Create
@company/sentry-config that wraps Sentry.init() with non-negotiable defaults.
Mandatory PII scrubbing (cannot be bypassed):
// @company/sentry-config/src/scrubbers.ts import type { Event } from '@sentry/node'; const SENSITIVE_HEADERS = [ 'authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token', 'x-csrf-token', ]; const PII_PATTERNS = [ { pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{1,7}\b/g, replacement: '[CC_REDACTED]' }, { pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, replacement: '[EMAIL_REDACTED]' }, { pattern: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: '[SSN_REDACTED]' }, ]; export function scrubEvent(event: Event): Event | null { if (event.request?.headers) { for (const h of SENSITIVE_HEADERS) delete event.request.headers[h]; } if (event.message) event.message = scrubPII(event.message); if (event.exception?.values) { for (const exc of event.exception.values) { if (exc.value) exc.value = scrubPII(exc.value); } } return event; } function scrubPII(str: string): string { for (const { pattern, replacement } of PII_PATTERNS) { str = str.replace(new RegExp(pattern.source, pattern.flags), replacement); } return str; }
Governed init with naming validation, tag injection, and tier-based caps:
// @company/sentry-config/src/index.ts import * as Sentry from '@sentry/node'; import { scrubEvent } from './scrubbers'; const ENFORCED: Partial<Sentry.NodeOptions> = { sendDefaultPii: false, debug: false, maxBreadcrumbs: 50, sampleRate: 1.0, maxValueLength: 500, }; const VALID_ENVS = ['production', 'staging', 'development', 'canary', 'sandbox']; const TIER_TRACE_CAPS: Record<string, number> = { critical: 0.5, standard: 0.2, internal: 0.05 }; interface PolicyOptions { serviceName: string; // kebab-case, 3-40 chars dsn: string; version: string; tags: { service: string; team: string; tier: 'critical' | 'standard' | 'internal' }; environment?: string; tracesSampleRate?: number; // capped by tier beforeSend?: Sentry.NodeOptions['beforeSend']; } export function initSentry(opts: PolicyOptions): void { if (!opts.dsn) throw new Error('@company/sentry-config: dsn required'); if (!/^[a-z][a-z0-9-]{2,39}$/.test(opts.serviceName)) { throw new Error(`Invalid service name "${opts.serviceName}" — use lowercase kebab-case, 3-40 chars`); } const env = (opts.environment || process.env.NODE_ENV || 'development').toLowerCase(); if (!VALID_ENVS.includes(env)) { throw new Error(`Invalid environment "${env}". Allowed: ${VALID_ENVS.join(', ')}`); } const tierCap = TIER_TRACE_CAPS[opts.tags.tier] ?? 0.2; const sha = (process.env.GIT_SHA || process.env.COMMIT_SHA || '').substring(0, 7); const release = sha ? `${opts.serviceName}@${opts.version}+${sha}` : `${opts.serviceName}@${opts.version}`; Sentry.init({ dsn: opts.dsn, environment: env, release, serverName: opts.serviceName, ...ENFORCED, debug: env !== 'production', tracesSampleRate: Math.min(opts.tracesSampleRate ?? 0.1, tierCap), beforeSend(event, hint) { const scrubbed = scrubEvent(event); // Mandatory — always runs first if (!scrubbed) return null; return opts.beforeSend ? (opts.beforeSend as Function)(scrubbed, hint) : scrubbed; }, initialScope: { tags: { service: opts.tags.service, team: opts.tags.team, tier: opts.tags.tier, deployment: process.env.DEPLOYMENT_ID || 'unknown', region: process.env.AWS_REGION || process.env.GCP_REGION || 'unknown', }, }, }); }
Service usage (two lines to adopt):
import { initSentry } from '@company/sentry-config'; initSentry({ serviceName: 'payments-api', dsn: process.env.SENTRY_DSN!, version: '2.4.1', tags: { service: 'payments-api', team: 'payments', tier: 'critical' }, });
Step 2 — Enforce Compliance via CI
Add a GitHub Actions workflow to every service repository that blocks policy violations.
# .github/workflows/sentry-policy.yml name: Sentry Policy Check on: [pull_request] jobs: sentry-compliance: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Block hardcoded DSNs run: | if grep -rn "https://[a-f0-9]*@.*ingest.*sentry" \ --include="*.ts" --include="*.js" \ --exclude-dir=node_modules --exclude-dir=dist src/; then echo "::error::Hardcoded Sentry DSN — use SENTRY_DSN env var" exit 1 fi - name: Block sendDefaultPii true run: | if grep -rn "sendDefaultPii.*true" \ --include="*.ts" --include="*.js" \ --exclude-dir=node_modules src/; then echo "::error::sendDefaultPii must be false" exit 1 fi - name: Block direct Sentry.init calls run: | HITS=$(grep -rn "Sentry\.init(" --include="*.ts" --include="*.js" \ --exclude-dir=node_modules --exclude="*sentry-config*" src/ || true) if [ -n "$HITS" ]; then echo "::error::Use initSentry() from @company/sentry-config" echo "$HITS" exit 1 fi - name: Verify shared config dependency run: | if ! grep -q "@company/sentry-config" package.json; then echo "::warning::Not using shared Sentry config package" fi
Optional ESLint rule for IDE feedback:
// eslint-rules/no-direct-sentry-init.js module.exports = { meta: { type: 'problem', messages: { bad: 'Use initSentry() from @company/sentry-config' } }, create(ctx) { return { CallExpression(node) { if (node.callee.type === 'MemberExpression' && node.callee.object.name === 'Sentry' && node.callee.property.name === 'init' && !ctx.getFilename().includes('sentry-config')) { ctx.report({ node, messageId: 'bad' }); } }, }; }, };
Step 3 — Drift Detection and Cost Governance
Monthly drift audit across all Sentry projects:
#!/bin/bash # scripts/audit-sentry-drift.sh set -euo pipefail ORG="${SENTRY_ORG:?required}" TOKEN="${SENTRY_AUTH_TOKEN:?required}" API="https://sentry.io/api/0" VIOLATIONS=0 echo "=== Sentry Drift Audit — $(date -u +%Y-%m-%d) ===" PROJECTS=$(curl -s -H "Authorization: Bearer $TOKEN" \ "$API/organizations/$ORG/projects/?all_projects=1" \ | python3 -c "import json,sys; [print(p['slug']) for p in json.load(sys.stdin)]") for P in $PROJECTS; do echo "--- $P ---" curl -s -H "Authorization: Bearer $TOKEN" "$API/projects/$ORG/$P/" \ | python3 -c " import json, sys, re p = json.load(sys.stdin) fails = 0 for label, val in [ ('Data Scrubber', p.get('dataScrubber', False)), ('IP Scrubbing', p.get('scrubIPAddresses', False)), ('Naming', bool(re.match(r'^[a-z]+-[a-z0-9]+-[a-z]+$', p['slug']))), ]: status = 'PASS' if val else 'FAIL' print(f' [{status}] {label}') if not val: fails += 1 missing = [f for f in ['password','ssn','credit_card','api_key','secret'] if f not in p.get('sensitiveFields', [])] if missing: print(f' [FAIL] Sensitive Fields: missing {missing}') fails += 1 else: print(f' [PASS] Sensitive Fields') sys.exit(1 if fails else 0) " || VIOLATIONS=$((VIOLATIONS + 1)) done echo "" echo "Projects with violations: $VIOLATIONS / $(echo "$PROJECTS" | wc -l)" [ "$VIOLATIONS" -eq 0 ] && echo "STATUS: ALL COMPLIANT" || { echo "STATUS: DRIFT DETECTED"; exit 1; }
Per-team cost quotas (run weekly):
// scripts/check-team-quotas.ts const QUOTAS = [ { team: 'payments', errors: 10_000, transactions: 50_000 }, { team: 'platform', errors: 20_000, transactions: 100_000 }, { team: 'growth', errors: 15_000, transactions: 200_000 }, { team: 'infrastructure', errors: 8_000, transactions: 20_000 }, ]; async function main() { const org = process.env.SENTRY_ORG!, token = process.env.SENTRY_AUTH_TOKEN!; const headers = { Authorization: `Bearer ${token}` }; // Fetch 30-day usage grouped by project and category const stats = await fetch( `https://sentry.io/api/0/organizations/${org}/stats_v2/?` + 'field=sum(quantity)&groupBy=project&groupBy=category&interval=1d&statsPeriod=30d', { headers }, ).then(r => r.json()) as any; // Map projects to teams const projects = await fetch( `https://sentry.io/api/0/organizations/${org}/projects/?all_projects=1`, { headers }, ).then(r => r.json()) as any[]; const teamOf = new Map(projects.map((p: any) => [p.slug, p.teams?.[0]?.slug || 'unknown'])); const usage = new Map<string, { errors: number; txns: number }>(); for (const g of stats.groups || []) { const team = teamOf.get(g.by.project) || 'unknown'; if (!usage.has(team)) usage.set(team, { errors: 0, txns: 0 }); const u = usage.get(team)!; if (g.by.category === 'error') u.errors += g.totals['sum(quantity)']; if (g.by.category === 'transaction') u.txns += g.totals['sum(quantity)']; } let overBudget = false; for (const q of QUOTAS) { const u = usage.get(q.team) || { errors: 0, txns: 0 }; const ePct = ((u.errors / q.errors) * 100).toFixed(0); const tPct = ((u.txns / q.transactions) * 100).toFixed(0); const flag = u.errors > q.errors || u.txns > q.transactions ? 'OVER' : 'OK'; console.log(`[${q.team}] errors=${u.errors}/${q.errors} (${ePct}%) txns=${u.txns}/${q.transactions} (${tPct}%) [${flag}]`); if (flag === 'OVER') overBudget = true; } if (overBudget) { console.error('ACTION REQUIRED: budget exceeded'); process.exit(1); } } main();
Schedule both in CI:
# .github/workflows/sentry-audit.yml name: Sentry Monthly Audit on: schedule: [{ cron: '0 9 1 * *' }] workflow_dispatch: jobs: drift: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: bash scripts/audit-sentry-drift.sh env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} cost: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npx tsx scripts/check-team-quotas.ts env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
Output
shared package with enforced PII scrubbing, naming validation, and tier-based trace rate caps@company/sentry-config- Mandatory
chain: headers, credit cards, emails, and SSNs scrubbed before any custom logicbeforeSend - Naming standards enforced at init: release format, validated environments, kebab-case service names
- Required tags injected: service, team, tier, deployment, region
- CI workflow blocking hardcoded DSNs, PII collection, and direct
callsSentry.init() - Monthly drift audit checking data scrubber, IP scrubbing, sensitive fields, and naming compliance
- Per-team cost quota enforcement with weekly budget checks
Error Handling
| Error | Cause | Solution |
|---|---|---|
at init | Name not kebab-case or wrong length | Rename to lowercase kebab-case, 3-40 chars, letter start |
at init | Non-standard environment string | Use: production, staging, development, canary, or sandbox |
CI blocks | Direct call outside shared config | Replace with from |
| CI blocks hardcoded DSN | DSN string in source code | Move to environment variable |
Drift audit on data scrubber | Project settings changed in UI | Re-enable in Project Settings then Security and Privacy |
| Team over budget | Noisy errors or high trace volume | Add filters or lower |
scrubbing bypassed | Service using raw | Adopt which chains scrubbing first |
Examples
Minimal compliant init:
import { initSentry } from '@company/sentry-config'; initSentry({ serviceName: 'user-service', dsn: process.env.SENTRY_DSN!, version: '1.0.0', tags: { service: 'user-service', team: 'platform', tier: 'standard' }, });
Critical service with custom filtering:
import { initSentry } from '@company/sentry-config'; initSentry({ serviceName: 'checkout-api', dsn: process.env.SENTRY_DSN!, version: '3.2.0', tags: { service: 'checkout-api', team: 'payments', tier: 'critical' }, tracesSampleRate: 0.4, // capped to 0.5 for critical tier beforeSend(event) { if (event.exception?.values?.some(e => e.value?.includes('PaymentTimeout'))) { return null; // drop known timeout noise from payment processor } return event; }, });
Resources
- Sentry Organization Settings
- Data Scrubbing
- Sentry API Reference
- Quotas and Rate Limits
- Security Policy
Next Steps
- Publish
to internal registry and onboard first two services@company/sentry-config - Add the CI policy workflow to all service repositories via shared workflow or template
- Schedule the drift audit as a monthly cron and cost check as weekly
- Communicate per-team quotas to engineering leads with Slack budget alerts