---
title: C19 api → 規則包網址
eyebrow: Guidant AI 資安檢視總報告 · 連線清單
h1: C19 api → 規則包網址（url 型檢測基準，第一次抽取）
lede: 客戶建立「檢測基準」時不上傳壓縮檔、改填一個網址，系統去那個網址把規則包下載回來解析走的那條線。它是**系統替使用者打任意網址**的原語——下載回來的東西會被交給外部程式（cinc-auditor）解析，解析出的清單給人看，網址之後還會隨派工交給客戶機房的代理程式自己再下載一次（[C24](../DFD/dfd-level0.html#c24)）。這一頁回答：這條線**怎麼連**、**哪些功能走它**、它帶來**什麼威脅、駭客怎麼打**、我們**要怎麼防、目前做到哪**。
chips:
  - { text: "url 型 profile", kind: plain }
  - { text: "替人打任意網址", kind: warn }
  - { text: "威脅 3 種", kind: accent }
  - { text: "掃描命中 3 條", kind: plain }
---

> **這一頁怎麼來的**：[DFD Level 0](../DFD/dfd-level0.html#c19) 把產品運作時的連線編成 C01～C25；[STRIDE 六頁](../STRIDE/S-spoofing.html)每一條問題都標了發生在哪幾條線。這裡以**連線**為單位整理。3 條命中裡 M03-1 是「下載回來的內容交給外部程式時被當程式碼執行」——它對上傳型與網址型兩種來源都成立，STRIDE 頁同時標 C02 與 C19；M03-5 同時標 C19 與 C24（代理程式那條）。個別問題修了沒不在這頁講。

## 一、連線圖

```{.mermaid cap="C19 — api 到規則包網址。客戶管理員填網址建基準 → 背景抽取：safe_http_fetch 五道防線（只准 https、解析後每顆 IP 不得落私有段、用檢查過的 IP 連線、邊收邊算 50MB 上限、重導向 ≤3 跳每跳重驗）→ 算 sha256 → 共用解析層（三道防炸彈上限、擋 ERB 樣板）→ cinc-auditor。紅色是這條線的兩個特性入口：網址由人填、下載回來的內容會被當程式讀。"}
%%{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
    ADM(["👤 客戶管理員<br/>（建檢測基準）"])
    subgraph MAIN["🏠 主系統 api"]
        direction TB
        REG["登記 url 型基準<br/>只收 https"]
        FETCH["safe_http_fetch<br/>五道防線・60 秒・50MB"]
        SHA["算 sha256<br/>隨派工下發給 agent"]
        LIM["共用解析層<br/>檔數／解開總量／膨脹倍數<br/>inspec.yml 含 <% 拒收"]
        CINC["cinc-auditor<br/>（外部程式，讀 inspec.yml 當 ERB 樣板）"]
        REG --> FETCH --> SHA --> LIM --> CINC
    end
    URL(["🌐 規則包網址<br/>（客戶填的任何 https 位址）"])
    INTRA(["🏢 客戶內網／雲端 metadata"])
    AGENT(["🤖 客戶機房 agent<br/>C24 自己再下載一次"])
    ADM -- "C02 · 填網址" --> REG
    FETCH == "C19 · HTTPS GET（IP 釘住）" ==> URL
    FETCH -. "被擋：私有段／http／file://" .-> INTRA
    SHA -. "派工帶 url＋sha256" .-> AGENT
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class ADM,URL,INTRA,AGENT ext
    class REG,CINC open
```

## 二、這條線怎麼連

| 項目 | 現況 |
|---|---|
| **誰 → 誰** | `guidant-api`（抽取在背景執行緒，不握 DB 連線）→ 客戶管理員填的網址。沒有網域白名單（決策者裁示：新增來源就要改設定，維護成本大於收益） |
| **協定／埠** | **只准 https**（`ALLOWED_SCHEMES = ("https",)`，登記與下載兩處都用同一份白名單）；埠預設 443、可自填。`http://` 登記時回 `DETECTION_TOOLS_400013`；升級前既有的 http 基準不強制失效，抽取時寫醒目警告 |
| **五道防線** | ① scheme 白名單；② 解析後的**每一顆** IP 都不得落私有／保留段（`ipaddress` 內建旗標：RFC1918、loopback、link-local 含雲端 metadata `169.254.169.254`、multicast、保留、IPv4-mapped IPv6 先攤回 v4）；③ **IP 釘住**——用檢查過的那顆 IP 連線、`Host` 與 SNI 另帶原 hostname，憑證驗證對原 hostname 生效（防 DNS rebinding）；④ 邊收邊算、超過 50MB 即斷（`DETECTION_PROFILE_MAX_MB`）；⑤ 重導向 ≤3 跳、每跳重跑前四道 |
| **逾時** | 下載 60 秒；清單頁的「內容變了沒」探測（HEAD，零 body）5 秒，走同一套防線 |
| **下載後** | 算 `sha256` 落庫，隨派工 `build_payload()` 下發給代理程式對帳；交共用解析層 `_read_entries()`——三道防炸彈上限（檔數 10,000／解開 500MB／膨脹 200 倍，`archive_limits.py`）；`_repack_flat()` 交 cinc-auditor 前讀 `inspec.yml`，含 `<%` 拒收 |
| **錯誤回顯** | 所有失敗收斂成 `SafeFetchError` ＋自己寫的中文訊息，httpx 的原始錯誤（含內部位址）只進 log——因為抽取失敗訊息會回顯到前端，是一條資訊回傳通道 |
| **誰能建** | 能力點 `detection-profile.*`（軸④，`is_platform=false`，租戶管理員能建自己的基準）；公版要平台管理員（service 層 `_guard_system_writable`）；另有軸⑥授權守門 `require_license("detection-profile")` |
| **何時存在** | url 型 profile 建立／改版／手動重新解析時；清單頁開啟時另發一次 HEAD |

依據：套件 `jedi_detection/common/safe_http_fetch.py`（模組 docstring 五道防線、`_validate_and_pin()`、`_is_blocked_ip()`、`_read_with_limit()`、`DEFAULT_TIMEOUT_SEC`／`DEFAULT_MAX_REDIRECTS`）、`app/service/detection_profile_extraction_service.py`（`_download()`、`source_sha256`、session 之外執行）、`app/service/detection_profile_service.py:960`（登記只收 https）、`common/archive_limits.py`、`common/profile_extractor/inspec.py`（`_ERB_MARKER`）、`common/detection_profile_ref.py`（`build_payload` 帶 sha256）、`api/routes/detection_profile_route.py`（守門三層）；主專案 `config/config.py:200-206`（四個上限）。

**DFD 對照**：DFD 寫「HTTPS（只收 https，CM-2057）；第一次抽取、算 sha256」與實況相符。補充 DFD 沒寫的：清單頁開啟時另有一次 HEAD 探測（也走這條線、同一套防線）；私有段 IP 與 DNS rebinding 的防護在這一支。

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

| 群 | 功能 | 對威脅的意義 |
|---|---|---|
| **① 建立／改版 url 型基準** | `POST /detection-tool-profiles`（網址型走 JSON）、`/{uid}/new-version`、`PATCH /detection-tool-profile-versions/{uid}/source` → 背景下載＋解析 | 觸發者是客戶管理員；填的網址決定系統去連誰。下載回來的東西會進外部解析程式 |
| **② 手動重新解析** | `POST /detection-tool-profile-versions/{uid}/extraction` | 同一條下載路徑再跑一次；併行上限 4（`DETECTION_PROFILE_EXTRACTION_MAX_CONCURRENT`，M03-10 修後） |
| **③ 清單頁探測** | `list_controls()` 對 url 型來源發 HEAD 比對 ETag，一樣就直接顯示現有清單 | 零 body 但同樣是「替人打網址」；SSRF 風險與 GET 相同，少一道防線就是換動詞繞過 |
| **④ 派工下發** | `build_payload()` 把 `url`＋`sha256` 交給代理程式，代理程式走 [C24](../DFD/dfd-level0.html#c24) 自己下載執行 | 這條線算的 sha256 是代理程式對帳的唯一依據；**代理端實際比對尚未做**（CM-2402） |

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

掃描在這條線上抓到 3 條問題（[M03-3](../STRIDE/D-denial-of-service.html#m03-3)、[M03-1](../STRIDE/E-elevation-of-privilege.html#m03-1)、[M03-5](../STRIDE/E-elevation-of-privilege.html#m03-5)），歸納起來是**三種攻擊手法**。前提：攻擊者有一個能建檢測基準的帳號（客戶管理員）；T3 的第二階段攻擊者站在客戶機房的網路路徑上。

| # | 風險 | 威脅 | 駭客怎麼打 | 得手什麼 | STRIDE | 實例 |
|---|---|---|---|---|---|---|
| T1 | 🟡 中 | **網址那條路繞過上傳那條路的上限** | ① 有建立檢測基準權限的帳號；② 在自己的網站放一個 45MB、解開數十 GB 的壓縮檔；③ 新增檢測基準時選「網址」、填這個網址；④ 系統下載回來直接解開，上傳那條路的三道檢查完全沒跑（修前）；⑤ 伺服器記憶體被吃光，所有客戶一起停擺 | 讓整個產品對所有客戶停擺 | [D 讓服務停擺](../STRIDE/D-denial-of-service.html) | [M03-3 網址型規則包完全繞過壓縮檔的三道上限](../STRIDE/D-denial-of-service.html#m03-3) 🟡 |
| T2 | 🟠 高 | **下載回來的設定檔被外部程式當程式碼執行** | ① 任一客戶的管理員；② 做一包檢測規則，在 `inspec.yml` 寫一段 Ruby 樣板（`<% … %>`）；③ 放在網址上讓系統下載（或直接上傳），結構全部正常；④ 系統排解析，cinc-auditor 一讀設定檔就以**後端程序身分**把那段程式跑起來；⑤ 讀出系統密鑰、把資料庫身分提權成超級管理員，改寫全平台客戶的資料 | 整台後端主機的權限；改寫全平台客戶資料 | [E 權限提升](../STRIDE/E-elevation-of-privilege.html)、[T 竄改資料](../STRIDE/T-tampering.html) | [M03-1 客戶上傳的檢測規則包會被外部工具當成程式碼執行](../STRIDE/E-elevation-of-privilege.html#m03-1) 🟠 |
| T3 | 🟡 中 | **網址不加密、沒指紋，代理程式拿到什麼就跑什麼** | ① 客戶管理員用一個 http 網址建了檢測基準（修前收 http）；② 攻擊者站在**代理程式**下載的網路路徑上；③ 代理程式領到掃描工作去下載時，回一包換過的規則；④ 代理程式手上有客戶主機帳密，直接執行那包規則，掃描報告也一起造假；⑤ 因為不加密，連破解都不用 | 在客戶內網的代理程式上執行任意程式碼；偽造掃描結果 | [T 竄改資料](../STRIDE/T-tampering.html)、[E 權限提升](../STRIDE/E-elevation-of-privilege.html) | [M03-5 網址型規則包放行不加密連線、也不記指紋](../STRIDE/E-elevation-of-privilege.html#m03-5) 🟡 |

**三種手法的共同點**：這條線**把「外部網址的內容」變成「系統信任的規則」**——T1 是內容太大、T2 是內容有程式、T3 是內容被換。三者都是「內容不可信」的不同面向，而「網址由人填」只是讓攻擊者更容易把內容送進來。有一種 SSRF 手法（填內網或雲端 metadata 位址讓系統替你打）在掃描範圍內**沒有實例**，因為五道防線在掃描前就已到位——列在第五段 D4。

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

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

| # | 防線 | 擋哪種威脅 | 目前做到哪 |
|---|---|---|---|
| D1 | **資源上限放在兩種來源唯一共用的那一層**，不在各入口各補一次。檔數、解開總量、膨脹倍數三道；下載本身另有 50MB 邊收邊算 | T1 | ✅ `archive_limits.py` 接進 `_read_entries()`，上傳與網址兩路、未來第三路都天然涵蓋；刻意**不**在網址分支另補（補第二條路下次加來源照樣漏）。驗證：隨包規則包重打包前後一致；2MB 宣告 500MB 被擋。另：併行解析上限 4（M03-10） |
| D2 | **交給外部程式之前看內容，不只驗結構**。`inspec.yml` 含 ERB 樣板記號就拒收；擋點在兩路共用層 | T2 | ✅ `_repack_flat()` 讀 `inspec.yml`，含 `<%` 拒收並回明確訊息；開工前實查隨包 13 筆基準與 8 支內建規則包都沒用樣板。⚠️ **已知打折**：只看 `inspec.yml` 一個檔，若 cinc-auditor 對 `controls/*.rb` 也做樣板渲染則擋不住；**長期正解「把外部程式關進沙箱」未做**——cinc-auditor 仍以後端程序身分跑 |
| D3 | **網址只收 https，第一次抽取記指紋，派工帶給代理程式對帳** | T3 | ✅ 登記白名單取 `safe_http_fetch.ALLOWED_SCHEMES` 不另立一份，http 回 `DETECTION_TOOLS_400013`；既有 http 基準不強制失效但抽取時寫警告；`source_sha256` 落庫、`build_payload()` 下發。⚠️ **代理程式端實際比對指紋尚未做**（CM-2402 另開小卡）——目前指紋只是「記了」，沒有人在對 |
| D4 ◇ | **SSRF 五道防線：scheme 白名單、每顆 IP 查禁區、IP 釘住、大小上限、重導向每跳重驗**；HEAD 探測走同一套 | 填內網或雲端 metadata 位址讓系統替你打（掃描範圍內無實例） | ✅ `safe_http_fetch.py` 全部到位，模組 docstring 逐條說明「少了會怎樣」；DNS rebinding 以 IP 釘住擋、SNI 帶原 hostname 憑證驗證仍生效（已實測改 SNI 會 `Hostname mismatch`）；多顆 A record 全部都要過；錯誤收斂成中文訊息不回顯 httpx 原文。這是全 codebase 唯一一支完整的 SSRF 防護，[C13](C13-api-to-external-ai.html) D6、[C15](C15-api-to-issue-tracker.html) 的網址欄都還沒用它 |
| D5 ◇ | **外部程式關進沙箱**。cinc-auditor 以獨立容器、無網路、唯讀檔案系統、最小權限跑，不以後端程序身分 | T2 的根治（D2 是治標） | ⚠️ **沒有**。證據拆檔那條已有 `guidant-sandbox` 容器（[C08](../DFD/dfd-level0.html#c08)），cinc-auditor 解析沒套同一個模式。M03-1 修正時明寫「長期正解未做」 |
| D6 ◇ | **url 型基準的內容會漂，清單頁要說得出「這是哪一刻的快照」** | 使用者看到的清單與代理程式實際跑的不一致 | ✅ 清單頁 HEAD 探測 ETag，變了就提示重新解析；`sha256` 記的是「平台解析的是哪一份」。⚠️ 探測只比 ETag／Last-Modified，對方伺服器不給這兩個標頭就比不出來 |

**最便宜的一步**：D3 的代理端比對——sha256 已經隨派工送到代理程式手上，差的是代理程式下載後算一次、不同就拒跑；這是 CM-2402 的範圍。D5 最有價值但是一個獨立的基礎建設工作。

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

- **`controls/*.rb` 的樣板渲染**（D2）：M03-1 修正自承打折——只擋 `inspec.yml`。該實際做一包只有 `controls/x.rb` 含 `<%` 的規則包餵 cinc-auditor，看它跑不跑。
- **代理程式端的指紋比對**（D3）：CM-2402 的範圍，掃描時尚未做；做完前 T3 的「換內容」面仍開（只是 https 讓換起來難）。
- **cinc-auditor 沙箱化**（D5）：T2 的根治；目前解析以後端程序身分跑、能讀 `.env`。
- **其他吃網址的欄位是否共用這支防線**（D4）：`safe_http_fetch` 是全 codebase 唯一完整的 SSRF 防護；AI 服務位址（[C13](C13-api-to-external-ai.html)）、GitLab 網址（[C15](C15-api-to-issue-tracker.html)）、Drive webhook 公開網址（`GOOGLE_DRIVE_APP_CONFIG.public_base_url`）都是人填的網址、都沒走它。
- **既有 http 基準的實際數量**：升級前存的 http 基準不強制失效，STG／POC 有幾筆沒盤。

---

*依據：STRIDE 六頁信任邊界連線標記（CM-2403／2404 驗收後版本）、套件 `jedi-detection`（`common/safe_http_fetch.py`、`common/archive_limits.py`、`common/profile_extractor/inspec.py`、`common/detection_profile_ref.py`、`app/service/detection_profile_extraction_service.py`、`app/service/detection_profile_service.py`、`api/routes/detection_profile_route.py`）、主專案 `config/config.py`、DFD Level 0。M03-1 同時標 C02（上傳型），M03-5 同時標 C24（代理程式下載），本頁各歸一種手法。*
