Guidant AI 資安檢視總報告 · 連線清單

C15 api → GitHub/GitLab(意見回饋轉問題單,依設定)

使用者在產品裡送出「意見回饋」後,系統帶著公司的 GitHub/GitLab 通行證去開發團隊的問題單系統開一張單、上傳附件、之後同步狀態走的那條線。方向是本系統 → 外部問題單系統,流的是回饋內容、附件,與那把通行證。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

依設定 帶原廠通行證出門 威脅 2 種 掃描命中 2 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位,把這條線的特性、掃描在這條線上抓到的攻擊手法、以及由此該有的防線整理成一頁。個別問題修了沒不在這頁講,請點條目連回 STRIDE 頁。

§1

一、連線圖

%%{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
C15 — api 到 GitHub/GitLab。設定全系統一份存最上層:網址、專案、通行證。使用者送意見回饋 → api 建本地紀錄 → 帶通行證去外部開單、傳附件;刪回饋時順手去外部關單。紅色是兩個被打過的點:連線曾明寫關閉憑證驗證(GitHub)、刪除動作曾不比對是不是本人就跨出去關單。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 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 不同,因為這組是平台管理員維護的)。

§3

三、哪些功能會走這條線

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

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

掃描在這條線上抓到 2 條問題(M23-4、M23-2),歸納起來是兩種攻擊手法。

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

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

§5

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

對應兩種威脅與這條線的本質,防線分五條。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 卡滿兩分鐘。

§6

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

  • GitLab client 無逾時(D5):gitlab.Gitlab(url, private_token, api_version) 三個參數,沒 timeout;GitLab 掛了開清單會卡到 gunicorn 砍(120 秒)。
  • GitLab 網址可填內網:url 由平台管理員填、沒驗證格式——平台管理員是可信角色,但 http:// 也收,與 C13 D6、C19 的 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。