Skip to content
sentry-policy-guardrails logo

Sentry Policy Guardrails

sentry-policy-guardrails

Enforce organizational governance and policy guardrails for Sentry usage.

SKILL.md

Full skill instructions

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

  • @sentry/​node v8+ installed in target services
  • Internal npm registry available (GitHub Packages, Artifactory, or similar)
  • Team structure and project ownership defined in Sentry
  • SENTRY_AUTH_TOKEN with org:read and project:read scopes
  • 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

  • @company/​sentry-config shared package with enforced PII scrubbing, naming validation, and tier-based trace rate caps
  • Mandatory beforeSend chain: headers, credit cards, emails, and SSNs scrubbed before any custom logic
  • 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 Sentry.init() calls
  • Monthly drift audit checking data scrubber, IP scrubbing, sensitive fields, and naming compliance
  • Per-team cost quota enforcement with weekly budget checks

Error Handling

ErrorCauseSolution
Invalid service name at initName not kebab-case or wrong lengthRename to lowercase kebab-case, 3-40 chars, letter start
Invalid environment at initNon-standard environment stringUse: production, staging, development, canary, or sandbox
CI blocks Sentry.init()Direct call outside shared configReplace with initSentry() from @company/​sentry-config
CI blocks hardcoded DSNDSN string in source codeMove to SENTRY_DSN environment variable
Drift audit [FAIL] on data scrubberProject settings changed in UIRe-enable in Project Settings then Security and Privacy
Team over budgetNoisy errors or high trace volumeAdd ignoreErrors filters or lower tracesSampleRate
beforeSend scrubbing bypassedService using raw Sentry.init()Adopt @company/​sentry-config 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

Next Steps

  • Publish @company/​sentry-config to internal registry and onboard first two services
  • 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

More skills from jeremylongshore

openrouter-compliance-review logo
jeremylongshore/claude-code-plugins-plus-skills

openrouter-compliance-review

Review OpenRouter integration for regulatory compliance (SOC2, GDPR,

2.8K 0
View
maintainx-data-handling logo
jeremylongshore/claude-code-plugins-plus-skills

maintainx-data-handling

Data synchronization, ETL patterns, and data management for MaintainX.

2.8K 0
View
groq-core-workflow-a logo
jeremylongshore/claude-code-plugins-plus-skills

groq-core-workflow-a

Execute Groq primary workflow: chat completions with tool use and JSON

2.8K 0
View
validating-csrf-protection logo
jeremylongshore/claude-code-plugins-plus-skills

validating-csrf-protection

Validate CSRF protection implementations for security gaps. Use when

2.8K 0
View
api-contract logo
jeremylongshore/claude-code-plugins-plus-skills

api-contract

Configure this skill should be used when the user asks about "API contract",

2.8K 0
View
ideogram-incident-runbook logo
jeremylongshore/claude-code-plugins-plus-skills

ideogram-incident-runbook

Execute Ideogram incident response with triage, mitigation, and postmortem.

2.8K 0
View
gamma-sdk-patterns logo
jeremylongshore/claude-code-plugins-plus-skills

gamma-sdk-patterns

Reusable patterns for the Gamma REST API (no SDK exists).

2.8K 0
View
deepgram-sdk-patterns logo
jeremylongshore/claude-code-plugins-plus-skills

deepgram-sdk-patterns

Apply production-ready Deepgram SDK patterns for TypeScript and Python.

2.8K 0
View
openrouter-upgrade-migration logo
jeremylongshore/claude-code-plugins-plus-skills

openrouter-upgrade-migration

Migrate to OpenRouter from direct provider APIs or upgrade between SDK/model

2.8K 0
View
openrouter-common-errors logo
jeremylongshore/claude-code-plugins-plus-skills

openrouter-common-errors

Diagnose and fix common OpenRouter API errors. Use when encountering

2.8K 0
View
linear-cost-tuning logo
jeremylongshore/claude-code-plugins-plus-skills

linear-cost-tuning

Optimize Linear API usage, reduce unnecessary calls, and maximize

2.8K 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View