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

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

✅ 六棒全數掃完並經首腦驗收(2026-09-10);發現已全數處理——7 條(總表 M23-1~7)全部已修(1.21.0 出貨:CM-2049/FR-114.1-7/CM-2051/CM-2066/鎖定檔入版控/CM-2230+CM-2252);原 CM-1629~1634 六張早期卡已作廢,改照內化版問題總表派工。總結報告見 SUMMARY.md

狀態:✅ 已完成 文件 7 份

這是六個資安掃描 arc 之一。 六個 arc 合起來的結論、修正卡狀態、還沒開卡的待辦與覆蓋率地圖,在跨 arc 總表:security-scan-consolidated/——要問「總共掃出什麼、還有哪些要修」看那份,本頁只講這一支。

🔴 一頁看完

第六個資安掃描 arc,目標是「問題追蹤」這條鏈。 六棒已全部掃完並驗收(2026-09-10,當時裁一次只派一棒);發現已全數處理,見下方修正卡段。

§1

這在做什麼(白話)

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

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

§2

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

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

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

首腦從宿主端查證,是錯的:app/feedback/service/feedback_service.py 的 156/171/210/222/246/252 六處實際呼叫 create_gitlab_issue/create_github_issue/update_*/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 low + focus attack-surface(附帶密鑰專項,是免費收益)
  • 每棒 ≤30 檔(I5 例外,見上)
  • runner 不啟動掃描——指令交決策者親手打
  • 🔴 續跑後必須核對併檔的條數與標題(FR-079 B2 換來的:save_result.py 遇同 shard 會靜默擇一、不報錯)
§5

進度

順序 棒 卡 檔 面板 範圍內淨新增 報告
1 I1 第三方整合與憑證 CM-1613 25 ✅ 9 票全投 2 報告
2 I2 宿主接線(BE) CM-1614 31 ✅ 30 票、verified 5(含唯一 HIGH) 報告
3 I3 附件上傳下載 CM-1615 14 ✅ 9 票全投 0 +首腦補 1 報告
4 I4 對外面與插件契約 CM-1616 20 ✅ 12 票全投 0(兩項查證首腦補做) 報告
5 I5 app 與 domain CM-1617 40 ✅ 15 票全投 1(明文套件庫) 報告
6 I6 持久層 CM-1618 28 ✅ 9 票全投 0 +首腦查出六表無 RLS 報告

六棒面板全部完整跑完、零漏投、零不完整、一次都沒撞額度——這是本專案六個掃描 arc 以來第一次。

§6

修正卡(6 張早期卡 09-21 統一作廢,問題處理現況如下)

總結報告:SUMMARY.md — 決策者要看的那份,開頭一句話回答「要修幾條」。

卡號 修什麼 嚴重度 建議順序
CM-1629 資料庫+Redis 密碼散在 249 個版控文件(含 POC 完整連線字串)+堵住再發生的路 🔴 HIGH 1
CM-1630 意見回饋六條端點只有匯出掛守門;改/刪不驗歸屬 🟡 MEDIUM 2
CM-1631 Google Drive OAuth 密鑰與 Fernet 金鑰外洩(需 Google 主控台重設) 🟡 MEDIUM 3
CM-1632 連 GitHub 關閉 TLS 憑證驗證(兩處) 🟡 MEDIUM 4
CM-1634 內部套件庫走明文 HTTP 且為主要來源(26 個 repo) 🟡 MEDIUM 5
CM-1633 附件刪除守門只用了一半 ⚪ LOW 6

另有 3 條(JWT/Turnstile 金鑰、舊套件 .env)併入既有的 CM-1607,不另開卡。

現況:六張早期卡已作廢,問題改照內化版問題總表(docs/security-report/M23-issue.md)派工,全數已修(1.21.0 出貨):CM-1629→CM-2049(249 檔殘留字串已清,密碼換發排在正式環境上版前)、CM-1630→FR-114.1-7、CM-1631→CM-2051、CM-1632/1633→CM-2066、CM-1634→版本鎖定檔入版控(連線維持 http,決策者裁倉庫與打包機都在內網)。另 3 條併入的 CM-1607 衛生項亦已清(CM-2048)。

§7

座標

  • 首腦手冊:.claude/skills/security-scan-lead/SKILL.md
  • 六個 arc 共用現況:docs/features/FR-075-2609-jedi-package-security-audit/handoff/security-scan-STATE.md
  • 前案:FR-075/FR-076/FR-077(暫停)/FR-078/FR-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,不要切

2026-09-16 起本 FR 的後續在 FR-101:FR-099 把主專案的意見回饋與標籤整組搬進套件後,本頁 I2 掃的 31 檔主專案側已全部刪除,CM-1630 等發現的新落點在套件裡;套件 151 檔中有變動的 93 支由 FR-101 三棒重掃,未變動的 57 支沿用本頁六份報告的結論。

§8

文件

以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。

設計

文件 類型 標題 最後更新
scan-I4-plugin-contract / md API 規格 I4 檢查結果:對外的 API 與插件裝配 2026-09-10

交付與收口

文件 類型 標題 最後更新
SUMMARY / md 收口總結 FR-081 總結報告 — 問題追蹤功能資安檢查 2026-09-10

證據與盤點

文件 類型 標題 最後更新
scan-I1-third-party-integration / md 盤點證據 I1 檢查結果:跟 GitHub/GitLab 連線的部分 2026-09-10
scan-I2-host-wiring / md 盤點證據 I2 檢查結果:主專案的「意見回饋」功能 2026-09-10
scan-I3-attachments / md 盤點證據 I3 檢查結果:附件上傳下載 2026-09-10
scan-I5-app-domain / md 盤點證據 I5 檢查結果:業務邏輯層 2026-09-10
scan-I6-persistence / md 盤點證據 I6 檢查結果:資料存取層 2026-09-10
§9

Notion 卡

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

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