权限链路复核:gfc 到底是怎么拿到 openKylin 组织 admin 的

权限链路复核:gfc 到底是怎么拿到 openKylin 组织 admin 的

复核时间:2026-11-09 | 对象:openKylin 论坛 #173855、「权限攫取」一节
新增证据源:Gitee PR API、公开 Jenkins 构建日志(jenkins.openkylin.top/job/sig-changed/*/consoleText)、openkylin/docs 官方流程文档、openkylin/community 仓库配置。


0. 一句话结论

不是 Gitee 平台漏洞,不是账号被盗,不是 bot 被入侵,也不是「轻量级 PR 自动合并」。

是 openKylin 自己把「sig/<SIG>/sig.yaml 里的 maintainer 名单」当成了权限授予的唯一凭据,并把「PR 被标记为 approved/check_pass」当成了唯一授权闸门。sig-bot.py 在 PR 合并之前就直接调 Gitee API 给仓库加 admin,permissions/gitee.json 只是事后的结果快照。官方流程文档写明的 CLA、技术委员会审核、例会投票三道人工程序,一道都没有被实现成技术门禁。

更致命的一点:第一批 33 个 admin 里就包含 openkylin/community 治理仓库自己——拿到治理仓库的写权限后,就能自己合并自己的 PR,形成自举闭环。


1. 真实链路(与原帖有两处实质出入)

① 任意 Gitee 用户(非组织成员、未签 CLA)
   改 sig/<SIG>/sig.yaml 的 maintainers,+1 行,提交 PR
        ↓
② PR 事件 open → Jenkins「sig-changed」→ python3 sig-bot.py -r gitee
   = dry run,输出「将 add/remove 谁到哪个仓库」的清单,(dry run skip) 不实做
        ↓
③ PR 状态变为 approved / check_pass      ← ★ 唯一的授权闸门
        ↓
④ Jenkins「sig-changed」再次运行 sig-bot.py
   实做:逐仓调 Gitee API,add/remove 协作者,直接给 admin
        ↓ 同一分钟
⑤ cibot 提交 sig/sig.yaml + permissions/gitee.json(结果快照,不是输入)
        ↓
⑥ PR 合并 → 下一次全量重算,把之前所有未审批的 sig.yaml 变更一起结算

出入一:因果顺序反了。 原帖(以及我上一条回帖)写的是「权限文件自动同步 → Gitee 仓库权限自动下发」。Jenkins #5260 的实际顺序是:

01:50:40  PR action: approved/check_pass
01:50~    add xodjqe_admin_admin_admin to openkylin/<33 个仓库>  ← 含 community
01:52     提交 9043624d「update permissions/gitee.json file」      ← 事后记录
01:52     合并 !2814

permissions/gitee.json 是 bot 写下的结果,不是下发权限的原因。这一条影响整改方案:只盯住权限文件是不够的,得盯住 sig-bot.py 的实做调用。

出入二:「轻量级 PR 自动合并」这个归因不成立。 Gitee 官方定义,轻量级 PR 只是「无需 Fork、网页端改文件即可提 PR」的便捷方式,仍须作者审核通过后合入。API 也显示只有 !2814 是轻量级 PR(head=N/A、标题带【轻量级 PR】),!2823/!2824/!2825 都是普通 fork PR(head=master)。所以「自动」不来自轻量级 PR,来自 openKylin 自己的 Jenkins / bot 配置。


2. 逐环节证据

环节 证据 出处
起点:零权限外部人 10-29 afd01746、11-04 b6877138 快照中该账号无任何权限记录;!2814 由 gfcbjh8 提交,标签 openkylin-cla/no,body 为空,diff 仅 maintainers +1 行 Gitee API pulls/2814;git show 01193873
入口无保护 community 仓库 .gitee/ 下只有 PR/Issue 模板,无 CODEOWNERS/OWNERS;PR 模板要求填「主题 / 原因」,4 个 PR 均未按要求填写 .gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
官方流程写得其实很严 《9_SIG组基本信息变更流程》:「如新增成员,则新成员需先签署cla」「技术委员会将提前审核相关信息,并在下一次例会上进行进一步沟通」;《2_SIG组加入流程》:「等待该 SIG 组核心成员投票表决通过后,申请 PR 审核通过,晋升……并获得相应的仓库权限」;《12_SIG组Owner增选、撤销规范》:2/3 投票 + 报备 TC,TC 一票否决 openkylin/docs 三份文档(原文见第 7 节)
实际门禁只有 CI 格式校验 4 个 PR 全部 openkylin-cla/no,bot 只发了一条「You have not signed the CLA」评论,未阻断;Sonar 报 1835 条 CodeSmell 却写「目前状态为: OK」;can_merge_check=false;PR 元数据里 assignees/testers 含申请人本人(!2825 的 testers 只有他一人);评论区无任何人类评审发言 Gitee API pulls/{2814,2823,2824,2825} + .../comments
闸门 = PR 状态 Jenkins #5275 第 31 行:sig-bot.py[line:571] - DEBUG: PR action: approved/check_pass;#5260 同样 jenkins.openkylin.top/job/sig-changed/5275/consoleText
执行器 = sig-bot.py 构建脚本 python3 sig-bot.py -r gitee;日志 line:511 remove X to openkylin/Y、line:519 add X to openkylin/Y;bot 源码仓库 openkylin/ci-bots(私有,匿名不可读) 同上
合并前就写权限 #5275:07:43:35 触发 → 07:44:07~07:45:05 逐条 add 63 个仓库 → 07:45:07 PR 才合并;#5260 同样先 add 33 个再合并 Jenkins 日志 + Gitee API merged_at
权限文件只是快照 permissions/README 原文:「保存openkylin仓库权限,由cibot根据sig与ppm信息自动生成」;packages/sample.yaml 里 maintainers[].name 直接就是 Gitee 账号 community 仓库
自举:首批就含治理仓库 #5260 的 33 条 add 里明确有 openkylin/community,以及 oauth-backend/oauth-web/api-server/official-web;此后 11-06 08:40–09:18(UTC)五个 merge 的 author 是 gfc 本人 Jenkins #5260;git log --merges
全量重算 #5275 的 add/remove 清单里混着大量正常成员变更(add tjujiyan to qa2026、remove suchangzheng…),说明每次 PR 都把整个 SIG 体系重算一遍,之前埋进 kernel SIG 的 3 个包也在这次一起结算 → 33→96 Jenkins #5275
移除同样全自动 11-07 13:22 UTC 提交 7d57bd9c,13:25 快照归零 permissions/gitee.json 快照

3. 时间线(UTC;北京时间 = +8)

时间 事件 admin 数
10-30 13:42 !2814 创建(轻量级 PR、CLA=no、无说明、+1 行 Community SIG) 0
11-06 01:50 Jenkins #5260 approved/check_pass,bot 直接给 33 个仓库加 admin(含 community) 0 → 33
11-06 01:52 !2814 合并;cibot 写 9043624d 权限快照 33
11-06 08:40–09:18 他本人作为 merge author 连合 5 个 PR;08:43 自加 kernel SIG(未即时结算) 33
11-07 06:44–07:43 自加 KDE(后撤)、qt、Networking;删 TC 三份 PDF 夹带进 PR 33
11-07 07:43–07:45 Jenkins #5275 approved/check_pass,逐条 add 63 个仓库 33 → 96
11-07 07:45 !2823 合并 96
11-07 12:02 / 12:55 !2824(2 分 26 秒)、!2825(1 分 53 秒)合并 → 104 → 115 115
11-07 13:22 / 13:25 7d57bd9c 移出;全量重算归零 0
11-09 02:49 !2832 恢复 TC 纪要(= 北京时间 10:49) 0

4. 是哪方面问题 —— 责任分层

层面 判定 依据
Gitee 平台 无问题,按仓库配置正常执行 轻量级 PR 需人工审核;权限 API 由持有凭据的 bot 正常调用
治理规则(文档) 规则本身完整甚至偏严(CLA + TC 审核 + 例会 + 2/3 投票 + README 同步修改) openkylin/docs
「规则 → 技术」的落地 核心缺陷:三道人工程序没有任何一道做成阻断式门禁 CLA 只发评论、Sonar 状态 OK、can_merge_check=false、无 CODEOWNERS、sig/** 非受保护路径
权限模型设计 核心缺陷:低信任输入直通高权限输出 maintainers[].name ≡ Gitee admin;全量重算;合并前就下发;首批含治理仓库自身;无阈值/延迟/告警
CI / Bot 未被入侵,严格按设计执行;但 dry-run 清单无人消费 #5276/#5278 的 PR action: open 会打印 (dry run skip) 的 add 清单,本该是给审查人看的告警,1~2 分钟后即被 approved 实做
人(维护者) 批量合并时未识别出异常 PR,属于流程后果,不是主因 !2814 与 !2813/!2815/!2817/!2820 同批在 01:47–01:52 合并

安全分类:CWE-862 Missing Authorization / CWE-269 Improper Privilege Management;本质是 trust boundary 缺失 + 自动化信任链过长(“excessive automation / missing human-in-the-loop”)。
定性:这是社区治理 + DevSecOps 的授权设计/配置问题,不是代码漏洞,不是 0day,也不构成供应链投毒(原帖自己也确认未发现恶意代码)。


5. 为什么 8 天没人发现

  1. permissions/gitee.json 只记结果、不记原因,看不出「谁批准了这次加权限」。
  2. 没有单账号维度的突增告警:0 → 33 → 96 → 104 → 115 全程无拦截。
  3. dry-run 的 add 清单混在几十条正常成员变更里(#5275 同时有 add tjujiyan、remove suchangzheng、add apenper 等),63 条异常 add 并不显眼。
  4. 单看每个 PR 的 diff 都只有 +1 行,无害;危险来自「+1 行」与「bot 全量重算并直写 Gitee 权限」的组合,而这两者分属不同系统,没人把它们连起来看。

6. 整改清单(按优先级)

  1. 把 sig/**、packages/** 设为受保护路径:必须 SIG owner + 技术委员会双人 approve,禁止申请人出现在 assignees/testers(!2825 的 testers 只有他本人);引入 CODEOWNERS。
  2. CLA 从「提示」改为「阻断」:成员变更 PR 必须签 CLA,并强制填写模板里的「原因」、同步修改对应 SIG 的 README.md(官方流程已要求,加 CI 校验即可)。
  3. 切断「合并前下发」:sig-bot.py 的实做只允许在 PR 已合并 后由独立 job 触发。现在只要 PR 被打上 approved/check_pass,哪怕最后被关闭/拒绝,权限也可能已经加上了。
  4. 给 add 操作加阈值与延迟:单账号单次 >N 个仓库、跨 SIG、或涉及 linux/curl/oauth-*/community 等敏感仓,必须人工二次确认 + 冷却窗口。
  5. 把 community 治理仓库自身从 SIG 自动授权范围中排除 —— 这是自举闭环的关键一环。
  6. 权限变更留审计日志(谁、依据哪个 PR、哪个 approve),接入告警;permissions/gitee.json 增加「变更来源」字段。
  7. 顺带:Jenkins consoleText 目前匿名可读,暴露内部仓库地址、凭据 ID jenkins-ssh-gitee 与全量成员变更清单,建议收敛访问。

7. 附:官方文档原文(本次新查)

openkylin/docs → 1入门与参与/1_2社区贡献/04_社区兴趣小组(SIG)/

  • 9_SIG组基本信息变更流程.md:「团队成员/成员权限变更……(如新增成员,则新成员需先签署cla),填写好相关信息后,技术委员会将提前审核相关信息,并在下一次例会上进行进一步沟通。」
  • 2_SIG组加入流程.md:「当开发者参与SIG组贡献满足以下 2 个及以上条件,可以申请SIG组核心成员:提交 5 个及以上有效 commit / 提出 5 个及以上有效 issue / 修复 5 个及以上 issue / 积极参与 SIG 会议……等待该SIG组核心成员投票表决通过后,申请PR审核通过,晋升为该SIG组核心成员并获得相应的仓库权限。」
  • 12_SIG组Owner增选、撤销规范.md:「需要三分之二或以上投赞同票……投票结果需报备技术委员会,技术委员会……拥有一票否决权。」
  • community/README.md:「组内重大决策须由全体成员以超过 2/3 的票数投票决议并报备技术委员会。」
  • community/permissions/README:「保存openkylin仓库权限,由cibot根据sig与ppm信息自动生成。」

对照实际:6 次自加全部是 1 file changed, 1 insertion(+),仅改 sig.yaml、不碰 README,commit message 统一为 update sig/<x>/sig.yaml.,无申请说明、无 CLA、无投票、无 TC 记录,最快 1 分 53 秒完成合并。而历史上正常的同类提交会写明事由(如 update: 申请qt维护者权限、qt-sig新增成员 renjianquan),部分还同步改了 sig/qt/README.md。


8. 可复现入口

https://gitee.com/api/v5/repos/openkylin/community/pulls/2814      # head=N/A、CLA=no、body 空
https://gitee.com/api/v5/repos/openkylin/community/pulls/2823      # assignees 含申请人本人
https://gitee.com/api/v5/repos/openkylin/community/pulls/2825      # testers 只有申请人本人
https://gitee.com/api/v5/repos/openkylin/community/pulls/2832      # TC 恢复,merged_at 2026-11-09T10:49:16+08:00

https://jenkins.openkylin.top/job/sig-changed/5260/consoleText     # 33 条 add,含 community
https://jenkins.openkylin.top/job/sig-changed/5275/consoleText     # approved/check_pass + 63 条 add
https://jenkins.openkylin.top/job/sig-changed/5276/consoleText     # PR action: open, dry run skip
https://jenkins.openkylin.top/job/sig-changed/5278/consoleText

git clone --depth=1 https://gitee.com/openkylin/docs.git           # 官方流程文档

Gitee commit 页有登录墙,但以上 API、Jenkins console、git clone 均匿名可读。

2 个赞