Sentry 隐私与安全 #

为什么隐私与安全很重要? #

在错误监控过程中,可能会意外收集敏感信息,这会带来严重的法律和声誉风险。

text
┌─────────────────────────────────────────────────────────────┐
│                    隐私与安全风险                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 敏感数据泄露                                            │
│     - 用户密码、信用卡号                                    │
│     - 个人身份信息 (PII)                                    │
│     - API 密钥、令牌                                        │
│                                                             │
│  2. 合规性问题                                              │
│     - GDPR(欧盟)                                          │
│     - CCPA(加州)                                          │
│     - HIPAA(医疗)                                         │
│                                                             │
│  3. 安全风险                                                │
│     - 凭证泄露                                              │
│     - 内部信息暴露                                          │
│     - 攻击面扩大                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

数据脱敏 #

beforeSend 过滤 #

javascript
// JavaScript - 过滤敏感数据
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
  
  beforeSend(event, hint) {
    // 过滤请求体中的敏感字段
    if (event.request?.data) {
      event.request.data = sanitizeData(event.request.data);
    }
    
    // 过滤请求头中的敏感信息
    if (event.request?.headers) {
      delete event.request.headers.authorization;
      delete event.request.headers.cookie;
    }
    
    // 过滤 URL 中的敏感参数
    if (event.request?.url) {
      event.request.url = sanitizeUrl(event.request.url);
    }
    
    // 过滤面包屑
    if (event.breadcrumbs) {
      event.breadcrumbs = event.breadcrumbs.map(sanitizeBreadcrumb);
    }
    
    return event;
  },
});

function sanitizeData(data) {
  const sensitiveFields = [
    "password",
    "passwd",
    "secret",
    "token",
    "apiKey",
    "api_key",
    "creditCard",
    "credit_card",
    "ssn",
    "socialSecurityNumber",
  ];
  
  if (typeof data === "object" && data !== null) {
    const sanitized = { ...data };
    
    for (const field of sensitiveFields) {
      if (field in sanitized) {
        sanitized[field] = "[Filtered]";
      }
    }
    
    // 递归处理嵌套对象
    for (const key in sanitized) {
      if (typeof sanitized[key] === "object") {
        sanitized[key] = sanitizeData(sanitized[key]);
      }
    }
    
    return sanitized;
  }
  
  return data;
}

function sanitizeUrl(url) {
  // 移除 URL 中的敏感参数
  const sensitiveParams = ["token", "key", "secret", "password"];
  const urlObj = new URL(url);
  
  for (const param of sensitiveParams) {
    if (urlObj.searchParams.has(param)) {
      urlObj.searchParams.set(param, "[Filtered]");
    }
  }
  
  return urlObj.toString();
}

Python 数据脱敏 #

python
import sentry_sdk
import re

SENSITIVE_FIELDS = {
    "password",
    "passwd",
    "secret",
    "token",
    "api_key",
    "credit_card",
    "ssn",
}

def sanitize_data(data):
    if isinstance(data, dict):
        return {
            key: "[Filtered]" if key.lower() in SENSITIVE_FIELDS else sanitize_data(value)
            for key, value in data.items()
        }
    elif isinstance(data, list):
        return [sanitize_data(item) for item in data]
    elif isinstance(data, str):
        # 脱敏信用卡号
        data = re.sub(r"\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b", "[Filtered]", data)
        # 脱敏邮箱
        data = re.sub(r"\b[\w\.-]+@[\w\.-]+\.\w+\b", "[Filtered]", data)
        # 脱敏 SSN
        data = re.sub(r"\b\d{3}-\d{2}-\d{4}\b", "[Filtered]", data)
    return data

def before_send(event, hint):
    # 过滤请求数据
    if "request" in event:
        request = event["request"]
        
        if "data" in request:
            request["data"] = sanitize_data(request["data"])
        
        if "headers" in request:
            headers = request["headers"]
            for header in ["authorization", "cookie", "set-cookie"]:
                if header in headers:
                    headers[header] = "[Filtered]"
    
    return event

sentry_sdk.init(
    dsn="https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
    before_send=before_send,
)

正则表达式脱敏 #

javascript
// 常见敏感数据的正则表达式
const patterns = {
  // 信用卡号
  creditCard: /\b(?:\d{4}[- ]?){3}\d{4}\b/g,
  
  // 邮箱
  email: /\b[\w\.-]+@[\w\.-]+\.\w+\b/g,
  
  // SSN(美国社会安全号)
  ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
  
  // API 密钥(常见格式)
  apiKey: /\b[a-zA-Z0-9]{32,}\b/g,
  
  // JWT Token
  jwt: /\beyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*\b/g,
  
  // 手机号(中国)
  phoneCN: /\b1[3-9]\d{9}\b/g,
  
  // 身份证号(中国)
  idCardCN: /\b\d{17}[\dXx]\b/g,
};

function sanitizeString(str) {
  let result = str;
  
  for (const [name, pattern] of Object.entries(patterns)) {
    result = result.replace(pattern, `[${name} Filtered]`);
  }
  
  return result;
}

用户隐私 #

最小化用户信息 #

javascript
// ✅ 好的做法:只收集必要的用户信息
Sentry.setUser({
  id: user.id,  // 唯一标识符
});

// ⚠️ 谨慎使用:可能包含 PII
Sentry.setUser({
  id: user.id,
  email: user.email,  // 可选,谨慎使用
  username: user.username,  // 可选,谨慎使用
});

// ❌ 不要这样做:包含敏感信息
Sentry.setUser({
  id: user.id,
  email: user.email,
  password: user.password,  // 绝对不要!
  ssn: user.ssn,  // 绝对不要!
});

IP 地址处理 #

javascript
// 不收集 IP 地址
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
  sendDefaultPii: false,  // 不发送默认 PII
});

// 或者手动设置 IP
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
  
  beforeSend(event) {
    if (event.request) {
      // 移除 IP 地址
      delete event.request.headers?.["x-forwarded-for"];
      delete event.request.headers?.["x-real-ip"];
    }
    return event;
  },
});

用户同意 #

javascript
// 在用户同意后才初始化 Sentry
function initSentryIfConsented() {
  const hasConsent = localStorage.getItem("analyticsConsent") === "true";
  
  if (hasConsent) {
    Sentry.init({
      dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
    });
  }
}

// 用户同意后
function onUserConsent() {
  localStorage.setItem("analyticsConsent", "true");
  initSentryIfConsented();
}

// 用户撤回同意
function onUserRevokeConsent() {
  localStorage.setItem("analyticsConsent", "false");
  // 关闭 Sentry
  Sentry.close();
}

Session Replay 隐私 #

配置隐私选项 #

javascript
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
  integrations: [
    Sentry.replayIntegration({
      // 遮罩所有文本
      maskAllText: true,
      
      // 阻止所有媒体
      blockAllMedia: true,
      
      // 自定义遮罩选择器
      mask: [
        ".sensitive-data",
        "[data-sentry-mask]",
        "input[type='password']",
        "input[type='email']",
        "input[type='tel']",
      ],
      
      // 自定义阻止选择器
      block: [
        ".blocked-element",
        "[data-sentry-block]",
        "iframe",
        "video",
        "audio",
      ],
      
      // 忽略特定元素
      ignore: [
        ".ignore-element",
        "[data-sentry-ignore]",
      ],
    }),
  ],
});

HTML 元素标记 #

html
<!-- 遮罩文本(显示为灰色块) -->
<div data-sentry-mask>
  <p>Credit Card: 4111-1111-1111-1111</p>
</div>

<!-- 完全阻止(不录制) -->
<div data-sentry-block>
  <img src="profile.jpg" alt="Profile Photo">
</div>

<!-- 忽略(不录制交互) -->
<div data-sentry-ignore>
  <canvas id="animation"></canvas>
</div>

网络请求隐私 #

javascript
Sentry.init({
  integrations: [
    Sentry.replayIntegration({
      // 只捕获特定 URL 的请求详情
      networkDetailAllowUrls: [/api\.example\.com/],
      
      // 不捕获请求体和响应体
      networkCaptureBodies: false,
      
      // 脱敏请求头
      networkRequestHeaders: ["content-type"],
      networkResponseHeaders: ["content-type"],
    }),
  ],
});

合规性 #

GDPR 合规 #

javascript
// GDPR 合规配置
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",
  
  // 不发送默认 PII
  sendDefaultPii: false,
  
  // 数据处理
  beforeSend(event) {
    // 移除所有 PII
    return removePII(event);
  },
  
  // 面包屑过滤
  beforeBreadcrumb(breadcrumb) {
    // 不记录可能包含 PII 的面包屑
    if (breadcrumb.category === "http" && breadcrumb.data?.url?.includes("login")) {
      return null;
    }
    return breadcrumb;
  },
});

// 数据主体权利
// 1. 访问权:用户可以请求查看其数据
// 2. 删除权:用户可以请求删除其数据
// 3. 携带权:用户可以请求导出其数据

数据保留策略 #

yaml
# Sentry 数据保留配置(在 Sentry 控制台)
data_retention:
  # 事件数据保留时间
  event_retention: 30  # 天
  
  # 附件保留时间
  attachment_retention: 30  # 天
  
  # Session Replay 保留时间
  replay_retention: 30  # 天
  
  # 性能数据保留时间
  performance_retention: 30  # 天

数据处理协议 #

text
┌─────────────────────────────────────────────────────────────┐
│                    数据处理清单                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 数据最小化                                              │
│     □ 只收集必要的数据                                      │
│     □ 不收集敏感信息                                        │
│                                                             │
│  2. 数据脱敏                                                │
│     □ 实施数据脱敏策略                                      │
│     □ 定期审查脱敏规则                                      │
│                                                             │
│  3. 访问控制                                                │
│     □ 限制数据访问权限                                      │
│     □ 实施审计日志                                          │
│                                                             │
│  4. 数据保留                                                │
│     □ 设置合理的数据保留期限                                │
│     □ 定期清理过期数据                                      │
│                                                             │
│  5. 用户权利                                                │
│     □ 提供数据访问机制                                      │
│     □ 提供数据删除机制                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

安全最佳实践 #

1. 保护 DSN #

javascript
// ❌ 不要在客户端暴露私有 DSN
// 私有 DSN 应该只用于服务端

// ✅ 使用公开 DSN(前端)
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",  // 公开 DSN
});

// ✅ 服务端使用私有 DSN
// 通过环境变量管理
Sentry.init({
  dsn: process.env.SENTRY_DSN,
});

2. 限制域名 #

javascript
// 在 Sentry 项目设置中限制允许的域名
// Settings > Projects > [Project] > Client Keys (DSN) > Domain Whitelist

// 只允许特定域名发送事件
allowedDomains: [
  "example.com",
  "*.example.com",
]

3. 使用环境变量 #

javascript
// ✅ 使用环境变量
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
});

// ❌ 不要硬编码
Sentry.init({
  dsn: "https://xxxxxxxx@o123456.ingest.sentry.io/1234567",  // 硬编码
});

4. 审计日志 #

javascript
// 记录敏感操作
function sensitiveOperation(data) {
  Sentry.addBreadcrumb({
    category: "security",
    message: "Sensitive operation performed",
    level: "info",
    data: {
      operation: "data_export",
      userId: data.userId,
      timestamp: new Date().toISOString(),
    },
  });
  
  // 执行操作...
}

5. 定期审查 #

markdown
## 安全审查清单

### 每月审查
- [ ] 检查是否有敏感数据泄露
- [ ] 审查用户权限设置
- [ ] 检查告警配置

### 每季度审查
- [ ] 更新数据脱敏规则
- [ ] 审查数据保留策略
- [ ] 检查合规性要求

### 每年审查
- [ ] 全面安全审计
- [ ] 更新隐私政策
- [ ] 员工安全培训

安全事件响应 #

发现数据泄露时 #

text
┌─────────────────────────────────────────────────────────────┐
│                    安全事件响应流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 立即行动                                                │
│     ├── 识别泄露的数据类型和范围                            │
│     ├── 隔离受影响的系统                                    │
│     └── 通知安全团队                                        │
│                                                             │
│  2. 缓解措施                                                │
│     ├── 删除 Sentry 中的敏感数据                            │
│     ├── 更新脱敏规则                                        │
│     └── 重新生成泄露的凭证                                  │
│                                                             │
│  3. 通知                                                    │
│     ├── 通知受影响的用户                                    │
│     ├── 通知监管机构(如需要)                              │
│     └── 记录事件                                            │
│                                                             │
│  4. 后续                                                    │
│     ├── 根本原因分析                                        │
│     ├── 改进安全措施                                        │
│     └── 更新培训材料                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

删除敏感数据 #

bash
# 使用 Sentry CLI 删除敏感事件
sentry-cli events delete EVENT_ID

# 删除整个 Issue
sentry-cli issues delete ISSUE_ID

# 批量删除(通过 API)
curl -X DELETE \
  -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  "https://sentry.io/api/0/projects/org/project/events/?query=sensitive_data"

总结 #

隐私与安全检查清单 #

markdown
## 实施前检查

- [ ] 配置 beforeSend 过滤敏感数据
- [ ] 配置 beforeBreadcrumb 过滤敏感面包屑
- [ ] 设置 sendDefaultPii: false
- [ ] 配置 Session Replay 隐私选项
- [ ] 审查用户信息收集范围
- [ ] 设置数据保留策略
- [ ] 配置域名白名单
- [ ] 使用环境变量管理配置
- [ ] 准备用户同意机制
- [ ] 制定安全事件响应计划

下一步 #

恭喜你完成了 Sentry 文档的学习!现在你已经掌握了从入门到专家的 Sentry 知识,可以开始在实际项目中应用了。

最后更新:2026-03-29