mcpField
S
sentry-backend-bugs

sentry-backend-bugs

github.com/getsentry测试与质量45.7k84 次安装今天更新
代码仓库

关于 sentry-backend-bugs

面向 Sentry 后端代码审查的技能,依据实际生产问题归纳常见缺陷模式。它要求追踪代码路径,并仅报告高或中置信度的问题及修复建议。

主要功能

  1. 01按缺陷类别检查代码
  2. 02追踪调用与数据流
  3. 03覆盖常见运行时和数据问题
  4. 04仅报告中高置信度发现
  5. 05提供安全处理模式

适用场景

  1. 01审查当前分支的后端改动
  2. 02检查 ORM 查询的缺失记录风险
  3. 03排查输入解析与数据库完整性问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/getsentry/sentry --skill sentry-backend-bugs

安装后在 Claude Code 中输入/sentry-backend-bugs确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
references/
concurrency-bugs.md
data-validation.md
database-integrity.md
integration-errors.md
missing-records.md
null-and-type-errors.md
query-validation.md
url-safety.md

详细介绍

Sentry 后端 Bug 模式审查

通过检查最常导致真实生产错误的模式,发现 Sentry 后端代码中的 Bug。

此技能编码了来自 638 个真实生产问题的模式(393 个已解决、220 个未解决、25 个已忽略),这些问题在 65,000+ 受影响用户中生成了超过 2700 万个错误事件。这些不是理论上的风险——它们是实际最常发布的 Bug,并带有来自已解决问题的已知修复方案。

范围

审查用户、Warden 或当前分支 diff 提供的代码。如果用户未提供目标,则审查当前分支 diff。从更改的 hunk 或文件开始,然后仅在需要确认行为时向外读取。

  1. 根据以下模式检查分析更改的代码。
  2. 在需要时,使用 Read 和 Grep 追踪初始 diff 之外的数据流。沿着函数调用、调用方、序列化器、任务和 ORM 边界追踪,直到行为得到确认。
  3. 仅报告 HIGH 和 MEDIUM 置信度的发现。
置信度标准操作
HIGH已追踪代码路径,确认该模式与已知 Bug 类别匹配报告并提供修复
MEDIUM模式存在,但上下文可能减轻其影响报告为需要验证
LOW理论上的,或在其他地方已减轻不报告

步骤 1:对代码进行分类

确定你正在审查的内容,并加载相关参考。

代码类型加载参考
ORM 查询、模型查找、.objects.get()、FK 访问references/missing-records.md
类型转换、None 处理、选项读取、序列化器返回值references/null-and-type-errors.md
数据输入解析、字段长度、请求体、解压缩references/data-validation.md
get_or_create、save()、唯一约束、整数溢出references/database-integrity.md
集成 webhook、外部 API 调用、SentryApp 钩子references/integration-errors.md
字典迭代、共享状态、并发访问references/concurrency-bugs.md
Snuba 查询、指标订阅、搜索过滤器references/query-validation.md
重定向 URL、URL 构造、路由references/url-safety.md

如果代码跨越多个类别,请加载所有相关参考。

步骤 2:检查主要 Bug 模式

以下按真实生产数据中的综合频率和影响排序。

检查 1:指标订阅查询错误——113 个问题,3,035,640 个事件

告警和指标订阅引用了目标数据集中不存在的标签或函数。这些订阅一旦创建就会持续触发。

危险信号:

  • 使用 SubscriptionData 创建 Snuba 订阅时,使用用户提供的查询字符串而未进行验证
  • 在指标数据集的 p95/p99 函数中引用 transaction.duration(在该数据集中它是字符串类型)
  • 将自定义标签名(例如 customerType)用作过滤维度,但未检查它们是否存在
  • 在未验证数据集是否支持阈值参数的情况下调用 resolve_apdex_function

安全模式:

  • 在创建订阅之前,根据数据集 schema 验证查询字段
  • 用 try/except SubscriptionError 包裹 _create_in_snuba 调用,并将订阅标记为无效
  • 在构建指标订阅查询之前,使用 IncompatibleMetricsQuery 检查
检查 2:记录缺失 / 陈旧引用 -- 81 个问题,1,403,592 个事件

代码在 Django 模型上调用 .get(),假设记录存在,但该记录已被删除、合并或从未创建。

危险信号:

  • Model.objects.get(id=some_id) 没有针对 DoesNotExist 的 try/except
  • 工作流引擎中的 Detector.objects.get(id=detector_id) 未处理删除情况
  • 监控/cron 消费者中的 Environment.objects.get(name=env_name)
  • 计费任务中的 Subscription.objects.get(id=sub_id)
  • 使用来自 Snuba 查询结果的 ID 调用 Group.objects.get()(分组可能已被删除/合并)
  • 链式查找中第二个 .get() 失败

安全模式:

  • Model.objects.filter(...).first() 配合 None 检查
  • try/except DoesNotExist 并返回优雅的回退(404、跳过、记录日志)
  • 在 .get() 之前用 Queryset 的 .exists() 检查
  • 在 API 端点中:对 DoesNotExist 返回 404,对验证错误返回 400。绝不要建议故意返回 500。

不是 bug——不要标记:

  • 基础设施不变量:.get() 强制执行部署前置条件(例如,“单组织模式下默认组织必须存在”)应当崩溃——500 表示配置错误,而非代码缺陷。
  • 已由父类验证:如果端点基类验证了对象(例如,OrganizationEndpoint 解析组织),不要标记对相关记录的 .get(),除非存在真正的竞态或删除窗口。在报告前阅读端点的父类。
  • 配置查找:加载必需配置对象的代码(get_default()、基于设置的查找)在配置错误时预期会硬失败。
检查 3:搜索查询验证 -- 57 个问题,2,001,330 个事件

来自用户提供或订阅存储的查询过滤器引用了无效值、已删除的问题或未解析的标签时出现 InvalidSearchQuery。

危险信号:

  • by_qualified_short_id_bulk() 被调用时传入来自存储订阅的短 ID,而这些订阅引用了已删除/重命名的项目
  • _issue_filter_converter 对用户提供的问题短 ID 调用 Group.objects.get()
  • resolve_tag_key() 被调用时未检查标签是否存在于目标数据集
  • 将来自告警规则订阅的未验证 query 字符串传递给 Snuba

安全模式:

  • 在传递给 by_qualified_short_id_bulk() 之前验证短 ID——处理 Group.DoesNotExist
  • 将 _issue_filter_converter 包裹在 try/except 中,对无效过滤器返回空结果
  • 根据数据集 schema 预先验证标签是否存在
检查 4:值验证错误 -- 45 个问题,1,000,624 个事件

输入验证不足导致的 ValueError:解包错误、无效枚举值、缺少预期对象,以及 Pydantic/DRF 验证失败。

危险信号:

  • 在操作构建器中调用 SentryAppInstallation.objects.get(),假设恰好存在 1 个结果
  • 按 ID 查找 AlertRuleWorkflow 而未处理 DoesNotExist
  • 对可能分隔符较少的字符串进行元组解包(a, b, c = value.split(":"))
  • 整数枚举查找未捕获 ValueError(例如 DetectorPriorityLevel(value))

安全模式:

  • 在 .get() 之前验证预期数量:使用 .filter() 并检查 .count()
  • 将元组解包包裹在 try/except ValueError 中,或先验证长度
  • 对面向用户的枚举转换使用 try: EnumClass(value) except ValueError:
  • 在 API 端点中:对 ValueError 和验证失败返回 400。
检查 5:类型错误 -- 28 个问题,740,106 个事件

传给函数的类型错误:迭代不可迭代对象、无效的 dict 键、本应为对象的地方传入了 None。

危险信号:

  • orjson.dumps(payload),其中 payload dict 可能包含非字符串键
  • list(setting),其中 setting 可能是 int(来自 project.get_option())
  • result["key"] = value,其中 result 可能为 None
  • 迭代来自 DB/config 的值,该值可能是 int 或 None 而不是 list

安全模式:

  • 迭代前进行类型检查:在 list(value) 之前检查 isinstance(value, (list, str))
  • JSON 序列化前验证 dict 键:确保所有键都是字符串
  • 下标赋值前进行 None 防护:if result is not None:
  • 带类型检查的防御性选项读取
  • 在 API 端点中:对用户输入的类型不匹配返回 400。
检查 6:内部 API 请求错误 -- 71 个问题,829,753 个事件

服务之间内部 Sentry API 调用产生的 ApiError,通常由过期的订阅参数引起。

危险信号:

  • 指标告警图表渲染中的 api.client.get() 调用未处理 400 响应
  • 转发过期订阅查询的内部 API 调用(引用了已移除的标签/指标)
  • incidents/charts.py 中的 fetch_metric_alert_events_timeseries() 未处理 ApiError

安全模式:

  • 用 try/except ApiError 包装内部 API 调用并返回优雅降级
  • 在发起内部 API 请求前验证查询参数
  • 在图表/可视化代码中显式处理 400/500 响应
检查 7:数据库约束冲突 -- 22 个问题,2,962,198 个事件

由整数溢出、外键冲突和唯一约束冲突引起的 IntegrityError 和 DataError。

危险信号:

  • 递增 times_seen 计数器时未做边界限制(约 21 亿时整数溢出)
  • 删除 MonitorCheckIn 记录时未处理来自 MonitorIncident 的 FK 约束
  • 对 GroupOpenPeriod 日期范围执行 UPDATE,其中下界可能超过上界
  • 对具有唯一约束的模型调用 save() 时未处理 IntegrityError

安全模式:

  • 更新前对整数字段设上限:对 32 位 int 列使用 min(value, 2_147_483_647)
  • 批量删除前使用 CASCADE 或处理 FK 约束
  • 保存前验证日期范围边界
  • try/except IntegrityError 并回退到 get() 或 update_or_create()
检查 8:数据解析与反序列化 -- 19 个问题,272,812 个事件

解析外部数据或存储的压缩数据时产生的 JSONDecodeError 和 ZstdError。

危险信号:

  • json.loads(response.body) 未捕获 JSONDecodeError
  • zstd.decompress(data) 未处理损坏的帧描述符
  • 解析可能被截断的 VSTS/Azure DevOps webhook 请求体
  • 假定 API 响应始终是有效 JSON(可能是 HTML 错误页面)

安全模式:

  • 始终用 try/except 包装 JSON/压缩操作并提供优雅降级
  • 解析前检查 content-type 头
  • 解析请求体前验证响应状态
检查 9:缺少键访问(KeyError) -- 25 个问题,155,020 个事件

在未检查是否存在的情况下访问字典键或 HTTP 头。

危险信号:

  • webhook 处理器中的 request.META["HTTP_X_GITLAB_TOKEN"](头可能不存在)
  • Bitbucket webhook 处理器中的 request.META["HTTP_X_EVENT_KEY"]
  • 使用 HANDLERS[event_type] 进行 dict 键查找时未检查事件类型是否已注册
  • 从头值进行元组解包时使用可变长度分割

安全模式:

  • 使用 request.META.get("HTTP_X_GITLAB_TOKEN") 并进行 None 检查
  • 使用 HANDLERS.get(event_type) 并为未知事件类型提供回退
  • 在处理前于 webhook 处理器顶部验证必需的头
检查 10:并发与运行时 Bug —— 23 个问题,38,443 个事件

迭代期间修改字典、共享可变状态,以及未实现的代码路径。

危险信号:

  • for key in self._dict: 而另一个线程在修改它(RuntimeError)
  • 向不断增长、无上限的共享 KafkaPublisher 字典发布
  • 对正在被另一个线程迭代的字典执行 dict.pop() 或 dict[key] = value
  • 新的搜索表达式类型缺少 NotImplementedError 处理

安全模式:

  • 迭代前先 dict.copy()
  • 对共享可变状态使用 threading.Lock
  • 在启用功能前实现所有代码路径
  • 需要在迭代时修改,使用 list(dict.keys()) 进行安全迭代
检查 11:逻辑正确性 —— 并非基于模式

在检查上述所有已知模式后,对变更代码本身进行推理:

  • 每条代码路径是否都返回正确的类型?
  • 条件语句的所有分支是否都已处理(尤其是 else / 默认情况)?
  • 是否有任何输入(None、空列表、0、空字符串)会导致意外行为?
  • 循环、切片或范围检查中是否存在差一错误?
  • 如果此代码并发运行,共享状态是否受到保护?

只有当你能够追踪到触发该 bug 的具体输入时才报告。不要报告理论上的顾虑。

不是 bug —— 不要标记:

  • 用于强制基础设施不变量的 assert 语句 —— Sentry 不会以 python -O 运行,因此断言始终处于激活状态。在不变量被违反时崩溃是有意为之。
  • 推测性的输入顾虑(例如“这个 URL 可能太长”、“这个 header 可能格式错误”),除非你能证明该输入确实未经验证就到达代码路径。在报告之前检查是否存在现有验证(host 检查、schema 验证、DRF 序列化器)。

如果没有任何检查产生潜在发现,请停止并报告零发现。不要为了填充报告而捏造问题。当代码中没有匹配这些模式的 bug 时,空结果就是正确的输出。

每个代码位置应在最具体的匹配模式下报告一次。不要在多个检查下标记同一行。

步骤 3:报告发现

对于每个发现,提供评审框架所需的证据:

  • 精确位置
  • 严重程度和置信度
  • 具体的触发输入或状态
  • 根本原因和后果
  • 可用的匹配生产先例
  • 具体的代码修复,当框架支持时最好以 unified diff 形式提供

修复建议必须包含实际代码。绝不要建议把注释或 docstring 作为修复。

在为 API 端点建议修复时,使用适当的 HTTP 状态码(not found 用 404,bad input 用 400,conflicts 用 409)。绝不要建议有意返回 500。

不要规定你自己的输出格式 —— 响应结构由评审框架控制。

评论

支持 Markdown