---
title: FR-081 問題追蹤（jedi-issue）資安掃描
status: ✅ **六棒全數掃完並經首腦驗收（2026-09-10）**。面板六棒全部完整跑完（本專案六個 arc 以來第一次）。19 條發現去重後歸成 **6 張修正卡（1 HIGH／4 MEDIUM／1 LOW）**，CM-1629～1634，依裁定全部歸檔待 PM 安排。**總結報告見 [SUMMARY.md](SUMMARY.md)**
notion:
  parent: {id: CM-1612, title: "FR-081 jedi-issue 問題追蹤鏈資安掃描（六棒，只掃不修）", url: "https://app.notion.com/p/FR-080-jedi-issue-3d6346da4cd0817c8f3ff06272cb44ee"}
  children:
    - {id: CM-1613, title: "I1 第三方整合與憑證（25 檔）", url: "https://app.notion.com/p/FR-080-I1-jedi-issue-25-jedi-monorepo-Opus-1M-low-3d6346da4cd081d59d90c1109fc7c4bf", status: 修正待驗證}
    - {id: CM-1614, title: "I2 宿主接線（31 檔，BE repo）", url: "https://app.notion.com/p/FR-080-I2-jedi-issue-31-BE-repo-Opus-1M-low-3d6346da4cd0819cb9c1e20495550ac6", status: 修正待驗證}
    - {id: CM-1615, title: "I3 附件上傳下載（14 檔）", url: "https://app.notion.com/p/FR-080-I3-jedi-issue-14-jedi-monorepo-Opus-1M-low-3d6346da4cd0810f9357c66a5768af23", status: 修正待驗證}
    - {id: CM-1616, title: "I4 對外面與插件契約（20 檔）", url: "https://app.notion.com/p/FR-080-I4-jedi-issue-20-jedi-monorepo-Opus-1M-low-3d6346da4cd081078d7fd9ab59c4ac4e", status: 修正待驗證}
    - {id: CM-1617, title: "I5 app 與 domain 層（40 檔）", url: "https://app.notion.com/p/FR-080-I5-jedi-issue-app-domain-40-jedi-monorepo-Opus-1M-low-3d6346da4cd081189bfcd25885799fdf", status: 修正待驗證}
    - {id: CM-1618, title: "I6 持久層（28 檔）", url: "https://app.notion.com/p/FR-080-I6-jedi-issue-28-jedi-monorepo-Opus-1M-low-3d6346da4cd0818a8181f97117dfefde", status: 修正待驗證}
relates: [FR-075, FR-076, FR-077, FR-078, FR-079]
---

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

# 🔴 一頁看完

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

## 這在做什麼（白話）

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

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

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

### ① 套件檔頭自稱「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 就是這樣八檔零發現卻漏掉真洞。**六張子卡每張都帶了這個警告。**

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

```python
# 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 換來的教訓的直接應用**——排名表的檔數只算套件本身，不含宿主接線；排任何一支之前要先問「這支有沒有宿主那一半」。

## 怎麼拆（六棒，按攻擊面切不按目錄大小平均切）

| 順序 | 卡 | 棒 | 檔數 | 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 卡已要求報告回答這個母卡層級的問題。

## 共同設定

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

## 進度

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

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

## 修正卡（6 張，全部歸檔待 PM 安排）

**總結報告：[SUMMARY.md](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**，不另開卡。

**依決策者既有裁定，六張全部是歸檔不是派工**，由 PM 收集所有掃描結果後統一安排。

## 座標

- 首腦手冊：`.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-075-2609-jedi-package-security-audit/)／[FR-076](../FR-076-2609-license-chain-security-scan/)／[FR-077](../FR-077-2609-remote-agent-security-scan/)（暫停）／[FR-078](../FR-078-2609-notification-security-scan/)／[FR-079](../FR-079-2609-bulletin-security-scan/)
- **相關前案：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`，**不要切**
