线上问题报告(升级):C端 游客面 9 个接口泄漏测试数据

报告人:波波 | 时间:2026-09-24 18:5x | 状态:⏸️ 已取证完毕,等待裁定,prod 数据与代码零改动

严重等级:🟠 P1(对外形象事故,无资金损失)

关系:本报告升级并取代同日 18:2x 的《C端商品位展示测试套餐》报告——原报告只发现 1 个接口,

    本次实测扫描发现 9 个,原报告的根因分析与处置建议仍然有效,但影响面需按下表重估。


一、结论先行

项结果
C端 游客面 GET 列表接口总数94 个
🔴 泄漏测试数据的9 个
✅ 游客可达且干净27 个(含我本轮做的 /fleet/routes、/retail/products)
🔒 需鉴权(游客不可达,非泄漏面)57 个
⚠️ 异常1 个(/hotel/availability 需必填参数,422)
资金影响无(真实用户订单 0、核销 0)
泄漏接口中属我代码的0 个 —— 全部跨域,故我未改任何代码

二、9 个泄漏接口实测清单(游客无 token 直接可取)

按响应中测试标记出现次数排序(次数≈污染程度):

#接口标记出现次数泄漏内容实例代码位置归属
1GET /api/miniapp/promoters168推广员列表含测试用户promoters.py:8渔佳新媒体
2GET /api/miniapp/products150「测试套餐001」¥288 等 70 条miniapp_products.py:93小齐
3GET /api/miniapp/packages140「测试套餐001」tag_1 等packages.py:84渔佳新媒体
4GET /api/miniapp/products/curated60甄选页 = 测试套餐001~020miniapp_products.py:112小齐
5GET /api/miniapp/tasks33UGC 任务含冒烟数据tasks_api.py:29渔佳新媒体
6GET /api/miniapp/content/guide-articles30slug=test_seed_story_020 等 20 篇feed_demand.py:389小冈
7GET /api/miniapp/merchants2733 家商户中 26 家名称含「测试」miniapp_merchants.py:89小齐
8GET /api/miniapp/products/featured18首页推荐位 = 测试套餐001~006miniapp_products.py:106小齐
9GET /api/miniapp/recommendations/combo-cards8组合卡 pkg-7001「测试套餐001」feed_demand.py:108小冈

对外影响最重的三个


三、根本原因:这是系统性缺陷,不是单点疏漏

3.1 数据层:演示种子铺满了整个库

动态扫描 prod 84 张含 is_test 列的表:

指标数值
含 is_test 列的表84
其中存有 is_test=TRUE 数据39
其中「整表全是测试数据」29 ← 一旦 C端 不过滤就是整表泄漏

29 张全测试表包括:packages orders(138) order_items(174) merchants(部分) menu_items reviews(40) stories(20) guide_articles(20) guide_profiles app_messages(52) commission_entries(50) distribution_records(50) redemption_codes(95) redemption_events(95) key_tasks(20) key_task_claims(60) merchant_bills(20) withdrawal_requests(20) travel_demands(15) distributors(10) guest_bindings(30) 等。

数据来源:backend/scripts/seed_demo_data.py(WIP-285 演示种子脚本,设计要点第 7 条明写

「全量 is_test=true,与真实业务物理隔离」)。脚本 DATABASE_URL 默认指向 staging,

prod 有这批数据推断是有人显式指向 prod 跑的(代码无法确定是谁/何时/为何)。

⚠️ 种子脚本本身没有错——它正确地打了 is_test=TRUE。
错在 C端 查询侧不认这个标记,导致"物理隔离"只停留在数据层,没有落到接口层。

3.2 接口层:C端 查询普遍不过滤 is_test

以 #7 为例(miniapp_merchants.py:89,作者小齐):

rows = await pool.fetch(
    _BASE_SQL + " WHERE m.status = 'active' "
    "AND (m.destination_id = $1 OR m.destination_id IS NULL) ORDER BY m.id", ...)

只过滤 status,没有 is_test = FALSE。

对照:我本轮做的零售/车队 C端 只读接口都写了 is_test=FALSE 并做了反证验证

(先确认 is_test=t 时查不到,再临时置 f 确认查得到且字段不含机密),

所以 /retail/products、/fleet/routes 出现在「干净」列表里。规范是可行的,只是没被普遍执行。

3.3 附带发现:category 参数全线失效

list_products 签名只有 limit,没有 category 形参;v2 五个分类页

(category-package/hiking/vehicle/transport/hotel)都传 ?category=X,

FastAPI 对未声明 query 参数静默丢弃 ⇒ 五页显示同一份全量套餐,分类功能形同虚设。

v2 任务书原文写明该契约 ⇒ 前端按契约写的,后端没实现。

⚠️ 附加阻塞:packages 表没有 category 列(只有 tag,取值 tag_0~tag_4 是种子占位值);

真正品类语义在 package_components.type(hotel/guide/driver/ticket/meal)

⇒ 想实现 category 过滤必须先定品类数据来源,属产品决策。


四、影响面评估(查库为证 ⇒ 等级从 P0 降 P1)

✅ 无资金损失

核查项结果
orders 表138 条全部 is_test=TRUE(paid 40/completed 36/in_service 27/booked 14/refunding 8/refunded 7/cancelled 6)
真实用户订单0 条
引用测试套餐的真实订单0 条
verifications 核销记录0 条
用户构成81 个用户中 79 测试 / 2 真实

🟠 但对外形象受损

⚠️ 止损制约(关键,决定不能直接 purge)

小冈今天 13:3x 刚交付「b端工作台静态演示画廊」(commit 6ac36879),明确用这批 is_test 数据给刘总演示:

「演示账号=测试商户001号(is_test),演示数据小冈自造入库:8001开通hotel+restaurant业态授权 + 3房型/12房间/4预订(挂真实orders)/3客人/2挂账/8菜品/8桌台/4餐饮订单(全is_test)」

⇒ 直接 seed_demo_data.py --purge 会连带删掉他的演示环境。故 purge 未执行。


五、处置建议

选项 A:C端 查询层统一补 is_test = FALSE ⭐我推荐

做什么:给 9 个泄漏接口的查询补 AND is_test = FALSE(约 9 处 SQL,各加一个条件)。

选项 B:A + 实现 category 过滤

选项 C:purge 演示数据

选项 D(根本解):录入真实内容

我的建议顺序:A(立即止血)→ D(根本解)→ B(category 另立任务)


六、请你裁定

#问题我的倾向
1是否授权我跨域改这 9 处(小齐/渔佳新媒体/小冈 名下)?还是转交各归属人自行修?建议授权我统一改(同一类缺陷,分散改易漏、口径易不一致),改完在回执逐处列明便于复核
2C端 这些位置变空白是否可接受?需你判断。空白诚实但对外形象要考虑;若不可接受,则 D(录真实内容)必须先做
3category 品类数据来源怎么定?(加列 / components 推导 / tag)需你定产品口径
4小冈的演示画廊数据是否保留?建议保留,我不动 purge
5是否知会小齐、小冈、渔佳新媒体(三人代码各有泄漏点)?建议知会,避免各自重复排查;我已在回执登记
6是否要把「C端 查询必须过滤 is_test」立为门禁?建议立(见 §七)

七、附带产出:一个可复用的实测扫描工具

scripts/scan_public_test_leak.py(本次新写,已入库)

为什么不用静态分析:我先写了静态正则判据 check_istest_leak.py,反证时发现它不可信:

—— 因其 SQL 是 _BASE_SQL + " WHERE ..." 拼接的,表名在变量里,正则扫单个字面量必然漏

fleet.py 查 merchants(按 id 查存在性)也算成泄漏

⇒ 静态判据对 SQL 拼接无能为力,判据不可信就不能当结论。

改用实测判据(可靠,直接看真实行为,不受拼接/ORM/视图影响):

  1. 从 /openapi.json 取全部 GET 路由(运行时真相)
  2. 只留 C端 游客面(/api/miniapp/**,排除 admin / merchant-console)
  3. 不带 Authorization 调用 → HTTP 200 即证明游客可达
  4. 响应含测试标记(测试/冒烟/演示/seed_/test_seed/b3demo/demo_)→ 判定泄漏
  5. 频控保护:串行 + 0.12s 间隔 + 只发 GET + 遇 429 立即停

已反证有效:跑出的 9 个泄漏中包含我先前用 curl 单独实测确认的 /merchants(26 家测试商户)。

已声明局限(诚实标注,不夸大):


八、取证纪律声明

动作方式是否改数据/代码
查 84 表 is_test 分布psql DO $$ … RAISE NOTICE(只读动态扫描)❌ 只读
实测 94 个 C端 GET 接口curl / urllib GET,无 token❌ 只读
追溯种子来源grep 代码❌ 只读
查真实订单/核销psql SELECT❌ 只读
查 9 处代码归属git blame❌ 只读
prod 数据零改动、9 处代码一行未改、purge 未执行。 全部等你裁定。

九、与前一份报告的关系

18:2x 报告本报告(18:5x)
发现范围1 个接口(products)9 个接口
根因分析缺陷 A(无 is_test 过滤)+ 缺陷 B(category 失效)同上,并升级为系统性缺陷(84 表 / 29 表全测试)
处置建议A/B/C 三选项同上,新增选项 D(录真实内容)与门禁建议
归属只指出 miniapp_products.py 属小齐9 处归属逐一 blame 定位(小齐4/渔佳新媒体3/小冈2,我 0)
工具手工 curlscan_public_test_leak.py 可复用(含反证与局限声明)

前一份报告的结论仍然成立,本报告是范围扩大后的完整版。