---
title: C15 api → GitHub／GitLab
eyebrow: Guidant AI 資安檢視總報告 · 連線清單
h1: C15 api → GitHub／GitLab（意見回饋轉問題單，依設定）
lede: 使用者在產品裡送出「意見回饋」後，系統帶著公司的 GitHub／GitLab 通行證去開發團隊的問題單系統開一張單、上傳附件、之後同步狀態走的那條線。方向是**本系統 → 外部問題單系統**，流的是回饋內容、附件，與那把通行證。這一頁回答：這條線**怎麼連**、**哪些功能走它**、它帶來**什麼威脅、駭客怎麼打**、我們**要怎麼防、目前做到哪**。
chips:
  - { text: "依設定", kind: plain }
  - { text: "帶原廠通行證出門", kind: warn }
  - { text: "威脅 2 種", kind: accent }
  - { text: "掃描命中 2 條", kind: plain }
---

> **這一頁怎麼來的**：[DFD Level 0](../DFD/dfd-level0.html#c15) 把產品運作時的連線編成 C01～C25；[STRIDE 六頁](../STRIDE/S-spoofing.html)每一條問題都標了發生在哪幾條線。這裡以**連線**為單位，把這條線的特性、掃描在這條線上抓到的攻擊手法、以及由此該有的防線整理成一頁。個別問題修了沒不在這頁講，請點條目連回 STRIDE 頁。

## 一、連線圖

```{.mermaid cap="C15 — api 到 GitHub／GitLab。設定全系統一份存最上層：網址、專案、通行證。使用者送意見回饋 → api 建本地紀錄 → 帶通行證去外部開單、傳附件；刪回饋時順手去外部關單。紅色是兩個被打過的點：連線曾明寫關閉憑證驗證（GitHub）、刪除動作曾不比對是不是本人就跨出去關單。"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB'}}}%%
flowchart LR
    USR(["👤 任一登入使用者"])
    ADM(["👤 平台管理員"])
    subgraph MAIN["🏠 主系統"]
        direction TB
        CFG[("ISSUE_INTEGRATE_CONFIG<br/>全系統一份，存 ROOT<br/>GITLAB: url・project・token<br/>GITHUB: project・token")]
        FB["意見回饋服務<br/>新增・修改・刪除・附件"]
        CFG --> FB
    end
    GL(["🦊 GitLab<br/>（網址由設定填）"])
    GH(["🐙 GitHub"])
    MITM(["🕵 站在路上的人"])
    USR -- "C02 · 送回饋／改／刪" --> FB
    ADM -- "C02 · 改設定" --> CFG
    FB == "C15 · HTTPS + PAT<br/>開單・傳附件・關單" ==> GL
    FB == "C15 · HTTPS + PAT" ==> GH
    MITM -. "不驗憑證時可假扮" .-> GH
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class USR,ADM,GL,GH,MITM ext
    class FB open
```

## 二、這條線怎麼連

| 項目 | 現況 |
|---|---|
| **誰 → 誰** | `guidant-api` → GitLab（網址由設定填，預設 `https://gitlab.com`）或 GitHub（固定 `api.github.com`）。兩家各一個開關，可同時開 |
| **協定／埠** | HTTPS。GitLab 用 `python-gitlab`、GitHub 用 `PyGithub`（`timeout=10`、`per_page=100`）。兩個 SDK 的憑證驗證都是**預設開**，程式沒有關閉的參數（M23-4 修後） |
| **用誰的身分** | 一把**個人存取權杖（PAT）**，全系統共用、存 `ISSUE_INTEGRATE_CONFIG` 那組設定在 ROOT 租戶。它是原廠（或客戶 IT）在問題單系統上的身分——拿到它等於能讀寫那個專案的程式庫與問題單 |
| **傳什麼** | 回饋標題、內容、附件檔；關單指令；讀回問題單狀態與附件清單同步回本地 |
| **誰能改設定** | 能力點 `issue-integrate-config.{create,read,update,delete}`，由 `core/plugins/system_core.py` 的 group 對照表在通用系統設定端點分流守門；設定全系統一份 |
| **通行證存哪** | `system_configs` 的 `private_token` 欄位，**明文 JSON 落庫**；讀取 API 會把它刪掉再回（套件 `mask_secret_value()`：`ISSUE_INTEGRATE_CONFIG` 在遮罩群組、`private_token` 在遮罩鍵名裡） |
| **何時存在** | 依設定（`enable: true` 且有 token）。出廠預設兩家都關 |

依據：套件 `jedi_issue/infra/github.py`、`jedi_issue/infra/gitlab.py`、`jedi_issue/infra/issue/adapter/{github,gitlab}/*_issue_adapter.py`（建構子）、`jedi_issue/app/feedback/service/feedback_service.py`（`delete_feedback` 順序）；主專案 `core/plugins/issue.py`（`get_integrate_config` 讀 ROOT 繞 RLS）、`core/plugins/system_core.py`（group → 能力點對照）、套件 `jedi_system_core/migrations/003-system-configs-seed.sql`（預設值）。

**DFD 對照**：DFD 寫「PAT（`ISSUE_INTEGRATE_CONFIG`）」與實況相符。補充 DFD 沒寫的：GitLab 網址由設定填、GitHub 固定官方；守門是能力點不是總部層級（與 SMTP／LDAP 不同，因為這組是平台管理員維護的）。

## 三、哪些功能會走這條線

| 群 | 功能 | 對威脅的意義 |
|---|---|---|
| **① 送回饋 → 開單** | 使用者按「意見回饋」送出 → 本地建紀錄 → 若開關開著，去 GitLab／GitHub 開單並上傳附件 | 觸發者是**任一登入使用者**；內容與附件由他決定，會原樣出現在開發團隊的問題單裡 |
| **② 改回饋 → 更新單** | 改標題內容、加附件 → 同步到外部單 | 改的是誰的回饋，決定誰的單被改 |
| **③ 刪回饋 → 關單** | 刪除本地回饋前，先去外部把對應的單關掉（失敗不擋本地刪除） | **跨出本系統、一旦送出收不回**——開發團隊正在處理的單會被關掉 |
| **④ 列表 → 讀回狀態** | 開清單時一次撈 open＋closed 兩批外部單，在記憶體比對狀態 | 每次開清單都打一次外部；外部慢或掛了清單跟著慢 |

## 四、這條線會帶來什麼威脅、駭客怎麼打

掃描在這條線上抓到 2 條問題（[M23-4](../STRIDE/I-information-disclosure.html#m23-4)、[M23-2](../STRIDE/R-repudiation.html#m23-2)），歸納起來是**兩種攻擊手法**。

| # | 風險 | 威脅 | 駭客怎麼打 | 得手什麼 | STRIDE | 實例 |
|---|---|---|---|---|---|---|
| T1 | 🟡 中 | **站在路上假扮 GitHub，收走通行證** | ① 攻擊者站在我們後端與 GitHub 之間（例如劫持網址解析）；② 使用者照常送出一筆意見回饋；③ 系統去連「GitHub」，攻擊者回一張自簽憑證，系統**明寫關閉驗證**（修前）照連；④ 公司的 GitHub 通行證連同問題單內容一起送到攻擊者手上。這個模組的通行證先前已外流過一次 | 公司的 GitHub 通行證（可讀寫對應的程式庫與問題單） | [I 資料外洩](../STRIDE/I-information-disclosure.html)、[S 冒充身分](../STRIDE/S-spoofing.html) | [M23-4 連 GitHub 時把憑證驗證關掉](../STRIDE/I-information-disclosure.html#m23-4) 🟡 |
| T2 | 🟡 中 | **用別人的回饋去關開發團隊的單** | ① 一般員工登入，打開意見回饋清單看到別人送的回饋；② 對別人的回饋改內容或按刪除；③ 系統不比對歸屬就執行，刪除時還把開發團隊正在處理的那張外部單一起關掉；④ 操作日誌記下的是一個合法登入的人做了合法操作 | 竄改或刪掉任何人的回饋，並關掉外部問題系統上正在處理的單 | [R 事後無法追查](../STRIDE/R-repudiation.html)、[T 竄改資料](../STRIDE/T-tampering.html) | [M23-2 任何員工都能改刪別人的意見回饋並關掉外部問題單](../STRIDE/R-repudiation.html#m23-2) 🟡 |

**兩種手法的共同點**：這條線帶的是**原廠（或客戶 IT）在另一個系統上的身分**，而觸發它的是**任一登入使用者**。T1 是「出門不確認對方是誰」（與 [C10](C10-api-to-smtp.html)～[C13](C13-api-to-external-ai.html) 同病，但這條是明寫 `verify=False` 不是「沒主動開」）；T2 是「跨出本系統的動作，沒先確認呼叫者有資格」——外部系統只認那把通行證，不認我們的使用者，所以我們這端的守門是唯一一道。

## 五、我們要怎麼防、目前做到哪

對應兩種威脅與這條線的本質，防線分五條。D1、D2 直接對應掃描抓到的手法；**標 ◇ 的（D3～D5）是依這條線的特性補的標準防線，掃描範圍沒涵蓋、沒有實例**。「目前」欄寫程式與設定裡**實際有的機制**；⚠️ 表示只靠慣例或只守到局部。

| # | 防線 | 擋哪種威脅 | 目前做到哪 |
|---|---|---|---|
| D1 | **永遠不關憑證驗證**。用 SDK 預設；程式裡不得出現 `verify=False`、`ssl_verify=False` | T1 | ✅ 兩處 `Github(..., verify=False)` 已拿掉（CM-2066），GitLab 那半查過本來就有驗。⚠️ 靠 code review 不讓它回來；沒有 lint 規則或守衛測試掃 `verify=False` |
| D2 | **跨出本系統的動作之前先比對歸屬，非本人一次外部呼叫都不送**。改、刪、刪附件疊 `_assert_owner`；六支端點補能力點；清單加「我的／全部」範圍 | T2 | ✅ `delete_feedback()` 的 `_assert_owner` 放在關外部單**之前**（程式註解明寫理由）；非本人 403 `FEEDBACK_403002`，取不到身分一律擋；能力點 `feedback.{manage,view,create,update,delete}`。驗證：六支端點 × 三種身分 18 組合 |
| D3 ◇ | **通行證加密落庫、讀取遮罩**。比照 AI 金鑰與 Drive 密鑰的三件套（遮罩／補回／加密） | 讀得到設定表或設定 API 的人 | ✅ 讀取遮罩有：套件 `mask_secret_value()` 對 `ISSUE_INTEGRATE_CONFIG` 刪掉 `private_token` 再回，dict 與 DTO 兩種形狀都遮；寫入時密碼欄沒改會從既有列補回。⚠️ **落庫是明文**——AI 金鑰與 Drive 密鑰有加密（`AI_PROVIDER_ENCRYPTION_KEY`／Fernet），這組與 SMTP、LDAP、Discord 一樣沒有；能讀 DB 或 DB 備份的人直接拿到 token |
| D4 ◇ | **外部單裡不帶客戶的敏感內容**。回饋內容與附件是使用者自填，可能夾著客戶內部資料、截圖含個資；進外部系統前該提醒或過濾 | 客戶資料經這條線流到開發團隊的問題單系統（第三方 SaaS） | ⚠️ 沒有過濾或提醒；回饋表單沒有「此內容會送到外部系統」的說明。合規問卷會問 |
| D5 ◇ | **外部呼叫有逾時、失敗不拖垮本地操作**。開清單一次撈兩批外部單，外部慢清單就慢 | 外部系統掛了讓本地功能跟著掛 | ✅ GitHub `timeout=10`；關單失敗只記 warning 不擋本地刪除。⚠️ GitLab client 建構沒帶 timeout（`python-gitlab` 預設不逾時）；清單同步每次都打外部，沒有快取 |

**最便宜的一步**：D5 的 GitLab 逾時——`gitlab.Gitlab(..., timeout=10)` 一個參數，與 GitHub 那半對齊；外部掛了不至於把 gunicorn worker 卡滿兩分鐘。

## 六、依這些防線，掃描還沒看過的地方

- **GitLab client 無逾時**（D5）：`gitlab.Gitlab(url, private_token, api_version)` 三個參數，沒 `timeout`；GitLab 掛了開清單會卡到 gunicorn 砍（120 秒）。
- **GitLab 網址可填內網**：`url` 由平台管理員填、沒驗證格式——平台管理員是可信角色，但 `http://` 也收，與 [C13](C13-api-to-external-ai.html) D6、[C19](C19-api-to-profile-url.html) 的 scheme 白名單不一致。
- **附件內容流向第三方**（D4）：使用者貼的截圖可能含客戶畫面與個資，掃描沒看「回饋表單該不該提醒」。

---

*依據：STRIDE 六頁信任邊界連線標記（CM-2403／2404 驗收後版本）、套件 `jedi-issue`（`infra/github.py`、`infra/gitlab.py`、`infra/issue/adapter/`、`app/feedback/service/feedback_service.py`）、主專案 `core/plugins/issue.py`、`core/plugins/system_core.py`、套件 `jedi_system_core/migrations/003-system-configs-seed.sql`、DFD Level 0。*
