FR-081 · 需求索引 · 本頁由 build 掃資料夾生成

FR-081 問題追蹤(jedi-issue)資安掃描

狀態:📋 已開卡待派(2026-09-09)。母卡 CM-1612,子卡 CM-1613~1618(六棒)。決策者裁一次只派一棒,六棒都尚未啟動 文件 0 份

🔴 一頁看完

第六個資安掃描 arc,目標是「問題追蹤」這條鏈。 六棒都已開卡、尚未啟動。決策者裁一次只派一棒。

§1

這在做什麼(白話)

使用者在系統裡送「意見回饋」,系統會把它變成一張問題單。這張單子可以只存在本地,也可以同時拿公司的權杖去 GitLab 或 GitHub 開一張真的 issue,還會把使用者上傳的附件一起送過去。

要檢查的是:使用者打的字與上傳的檔,一路流到第三方服務的過程中會不會出事——權杖會不會外洩、檔案會不會被放到不該放的地方、有沒有人能看到不該看的東西。

§2

🔴 三個發現改變了原本的評估

① 套件檔頭自稱「58% 是死碼」——這個說法不成立

jedi_issue/api/__init__.py:11-13 白紙黑字寫「GitLab/GitHub 整合目前產品沒有在用,約佔套件 58%」。

首腦從宿主端查證,是錯的app/feedback/service/feedback_service.py156/171/210/222/246/252 六處實際呼叫 create_gitlab_issuecreate_github_issueupdate_*set_*_issue_closed,由 _is_gitlab_enable()_is_github_enable() 開關控制。IssueProviderCode 使用統計:LOCAL 9 次、GITLAB 4 次、GITHUB 3 次

這正是工具已知盲點二(會被程式碼註解說服)的教科書形狀——研究員讀到那段 docstring 就會跳過 58% 的程式碼,而那半邊恰好是風險最高的(外部連線+憑證)。FR-075 的 S2 就是這樣八檔零發現卻漏掉真洞。六張子卡每張都帶了這個警告。

② 已經看到一個現成的洞,不必等掃描

# jedi_issue/infra/github.py:22
return Github(auth=auth, per_page=100, timeout=10, verify=False)
# jedi_issue/infra/issue/adapter/github/github_issue_adapter.py:43  同一行

帶著存取權杖走不驗憑證的 TLS,中間人可直接攔下 token。token 來自 os.getenv("GITHUB_PRIVATE_TOKEN")github.py:16)。

更該注意的是前科CM-1573 就是「jedi-issue PAT 外洩處置」——這個模組的權杖外洩過一次。現在同一個模組還在用 verify=False 傳它。首腦初判 HIGH,交由 I1 棒的面板驗證。

(本專案這是同款病第四處:CM-1560 LDAP TLS、CM-1565 Redis TLS、CM-1606 SMTP starttls。)

③ 宿主那一半藏在 feedback/ 底下,用檔名 grep issue 會漏掉

BE 側真正的攻擊面是「使用者送意見回饋 → 系統拿公司 PAT 去第三方開 issue 並夾帶使用者上傳的檔案」,程式碼在 api/feedback/app/feedback/domain/feedback/infra/feedback/

這是 FR-079 換來的教訓的直接應用——排名表的檔數只算套件本身,不含宿主接線;排任何一支之前要先問「這支有沒有宿主那一半」。

§3

怎麼拆(六棒,按攻擊面切不按目錄大小平均切)

順序 檔數 scanRoot 為什麼獨立
1 CM-1613 I1 第三方整合與憑證 25 jedi 🔴 風險最高:憑證+外部連線+外洩前科三者疊加
2 CM-1614 I2 宿主接線 31 BE scanRoot 不同;使用者輸入與守門都在這
3 CM-1615 I3 附件上傳下載 14 jedi 三種後端各自實作,一份漏做就是洞
4 CM-1616 I4 對外面與插件契約 20 jedi 唯一一條 route 吐成員名冊且只有 @jwt_required()
5 CM-1617 I5 app 與 domain 層 40 ⚠️ jedi 授權判定;邊界測試,見下
6 CM-1618 I6 持久層 28 jedi 租戶隔離的最後防線

合計 158 檔(套件 127 + 宿主 31)。按實測每棒約 70 分鐘算,約 7 小時純掃描。

三組要放在一起看的關係

  1. I1 + I2 是同一條鏈的兩半 — I1 是套件端「怎麼打第三方」,I2 是宿主端「輸入怎麼進來、權杖從哪讀、誰能觸發」。驗收時要對著看(FR-078 N1/N2 的經驗)。
  2. I4 的 fail-closed 宣稱 ↔︎ I2 的守門接線 — 套件檔頭說「缺接線要拒絕掛載不是跳過,跳過的後果是成員清單變公開端點」,接線實作在 I2 的 issue_plugin_wiring.py兩棒都要驗,不要互相假設對方驗過了。
  3. I3 的就地 new repo ↔︎ I6 的 session 紀律local_issue_attachment.py:63 有就地 IssueUploadFilesRepoImpl()

⚠️ I5 是一次邊界測試(40 檔)

每棒 ≤30 檔是實測邊界(FR-078 的 30/18 檔跑完、FR-077 的 42 檔全滅)。I5 的 40 檔刻意略超——app/domain/ 拆開會把「用例編排」與「業務規則」切在接縫上,而權限漏洞正活在那裡。40 檔中 18 個是空的或極短的 __init__.py,實質約 22 檔。

若面板未完整跑完 → 不要重跑整棒,回報首腦決定是否拆成 app/(22)+domain/(18)。這一輪的數字會決定往後 30~40 檔區間的切棒尺。

合理的停損點:I3 之後

I1+I2+I3 共 70 檔,涵蓋絕大部分風險面(憑證、輸入流、檔案)。若額度吃緊可在此收一輪。但沒跑完六棒就不能宣稱「jedi-issue 掃過了」——I6 卡已要求報告回答這個母卡層級的問題。

§4

共同設定

  • 主 session 用 Opus 5 (1M context),不要用 Sonnet
  • effort lowfocus attack-surface(附帶密鑰專項,是免費收益)
  • 每棒 ≤30 檔(I5 例外,見上)
  • runner 不啟動掃描——指令交決策者親手打
  • 🔴 續跑後必須核對併檔的條數與標題(FR-079 B2 換來的:save_result.py 遇同 shard 會靜默擇一、不報錯)
§5

進度

順序 狀態 報告 首腦驗收
1 I1 第三方整合與憑證 CM-1613 ⬜ Not started
2 I2 宿主接線 CM-1614 ⬜ Not started
3 I3 附件上傳下載 CM-1615 ⬜ Not started
4 I4 對外面與插件契約 CM-1616 ⬜ Not started
5 I5 app 與 domain 層 CM-1617 ⬜ Not started
6 I6 持久層 CM-1618 ⬜ Not started
§6

修正卡

(尚無——六棒都還沒跑。依決策者 2026-09-08 裁定,掃描結果產生的修正卡一律先不派,由 PM 收集所有掃描結果後統一安排。)

§7

座標

  • 首腦手冊:.claude/skills/security-scan-lead/SKILL.md
  • 六個 arc 共用現況:docs/features/FR-075-2609-jedi-package-security-audit/handoff/security-scan-STATE.md
  • 前案:FR-075FR-076FR-077(暫停)/FR-078FR-079
  • 相關前案:CM-1573「jedi-issue PAT 外洩處置」(已 Done,jedi monorepo ed62d8a)——同一模組的權杖外洩前科
  • 報告格式範例:../FR-079-2609-bulletin-security-scan/scan-B2-host-wiring.md
  • branch:BE repo 與 jedi monorepo 都在 feature/FR-075不要切
§8

Notion 卡

卡片內容(決策紀錄、驗收條件)以 Notion 為準,本頁只記座標。

關係 卡號 標題 狀態
母案 CM-1612 FR-081 jedi-issue 問題追蹤鏈資安掃描(六棒,只掃不修)
子卡 CM-1613 I1 第三方整合與憑證(25 檔) Not started
子卡 CM-1614 I2 宿主接線(31 檔,BE repo) Not started
子卡 CM-1615 I3 附件上傳下載(14 檔) Not started
子卡 CM-1616 I4 對外面與插件契約(20 檔) Not started
子卡 CM-1617 I5 app 與 domain 層(40 檔) Not started
子卡 CM-1618 I6 持久層(28 檔) Not started