---
title: 落地版對外連線盤點與停用方式
feature: FR-065
card: CM-1262 (T-3.5)
date: 2026-08-17
status: 完成
---

# 落地版對外連線盤點與停用方式

> **用途**：客戶手冊（T-4.1）的「網路需求／封閉網路部署」章節素材，以及裝機前的
> 防火牆規劃依據。
>
> **盤點範圍**：BE 主專案（`api/` `app/` `common/` `config/` `core/` `infra/` `domain/`）、
> FE 全部原始碼與 `index.html`、jedi-\* 套件層（`jedi_*/` 下所有 `.py`）。
> 掃描方式：grep 全域 URL 字面值 + 逐項開檔確認是「執行期真的會連」還是「字串常數」。

## 結論一句話

**落地版（air-gap）唯一「不處理就會壞掉」的對外連線是 Cloudflare Turnstile 人機驗證，
本卡已將其做成 BE/FE 一對的開關並在落地版預設停用。** 其餘對外連線全部屬於
「功能不啟用就不會連」或「本來就是 best-effort、連不到只是少一項紀錄」。

## 一、必須處理的（不處理就有功能壞掉）

| 連線目標 | 用途 | 不處理的後果 | 本卡處置 |
|---|---|---|---|
| `challenges.cloudflare.com` | 登入頁人機驗證（Turnstile）。FE 載 script、BE 打 siteverify | 🔴 **全站沒有人登入得了**。FE 端 script 載不到 → `onload` 不觸發 → 拿不到 token；BE 端連不到 siteverify 時 jedi-captcha 的處理是 `raise UnauthorizedError`（**不是放行**） | ✅ 已做 BE/FE 一對開關，落地版預設停用（見下方「停用方式」） |

### 為什麼這一項特別危險

它的失效方式是**登入直接失敗**而不是「驗證被跳過」。而且症狀出現在客戶裝完機、第一次
要登入的那一刻——最沒有除錯線索的時間點。BE log 會看到 `CAPTCHA_400001 驗證碼錯誤`，
但真因是「連不到 Cloudflare」，訊息完全指向錯誤的方向。

## 二、功能不啟用就不會連（客戶不設定即不外連）

| 連線目標 | 用途 | 觸發條件 | 不啟用時的行為 |
|---|---|---|---|
| `api.anthropic.com`（Anthropic SDK） | AI 助理／證據分類等 AI 功能 | **使用者主動操作**才觸發（送出對話、按分類）；需設 `ANTHROPIC_API_KEY` | 沒人點就不會連。⚠️ 但**未設 key 時並非不初始化 client**——`AiBotService` 是拿到請求才建 client、直接呼叫，失敗被 `except Exception` 接住回「系統錯誤，請稍後再試。」。即 air-gap 下按了 AI 功能會**等到 timeout 才回一句籠統錯誤**（不會壞掉別的功能，但體驗差）。建議客戶手冊註明「未購買／未設定 AI 功能者請勿使用該入口」，或後續另開卡在無 key 時直接回明確訊息 |
| `oauth2.googleapis.com`／`www.googleapis.com`／`drive.google.com` | Google Drive 證據同步（OAuth 換 token、讀檔） | 需在後台完成 Drive 整合授權（要 OAuth client id/secret） | 未整合即無任何 Drive 同步 job，不發連線 |
| `api.telegram.org`（jedi-notification） | Telegram 通知管道 | 需在通知設定填 bot token | 未設定即不使用該管道 |
| SMTP 伺服器（客戶自填） | 系統寄信（密碼重設、到期通知） | 需在後台填 SMTP 設定 | 未設定即不寄信。⚠️ **這通常是客戶內網的郵件伺服器，不算外網** |
| 檢測工具目標主機（SSH／WinRM） | 組態檢測 | 客戶自行新增檢測目標 | ⚠️ 這是**對客戶自己內網主機**的連線，不是對外網 |

## 三、best-effort，連不到不影響功能

| 連線目標 | 用途 | 連不到時 |
|---|---|---|
| License Center（`LICENSE_ACTIVATION_SERVER_URL`） | ① 線上開通（序號換照）② 防竄改事件回報 | ① 已有離線開通路徑（機器碼 → 原廠簽照 → 匯入），FE 明確引導改走離線<br>② `common/integrity/lc_report.py` 是 best-effort：短 timeout ＋ 全域 `except`，**絕不阻塞也不影響鎖定流程**（D9 紅線） |

🔴 **不要為了「讓回報更可靠」把 ② 改成硬依賴或加長 timeout**——那會讓防竄改鎖定流程
在 air-gap 環境卡住。這是 FR-064 D9 明確定下的紅線，本次盤點確認**維持原狀未動**。

## 四、看起來像對外連線、實際不是（避免誤判成待處理項）

| 字面值 | 實際是什麼 |
|---|---|
| `www.omg.org/spec/BPMN/...`、`camunda.org/schema/...`、`bpmn.io/schema/...` | BPMN XML 的 **namespace 宣告字串**。XML namespace 只是唯一識別碼，解析器不會去連 |
| `csrc.nist.gov/ns/oscal`、`ietf.org/rfc/rfc4122` | 同上，OSCAL／RFC 的 namespace 與識別碼字串 |
| `drive.google.com/drive/folders/<id>` | 給使用者點的**顯示連結**（新視窗開啟），由使用者的瀏覽器發出，不是伺服器連線。air-gap 環境下點了打不開屬預期 |
| `omnitruck.cinc.sh/install.sh` | **build 期**安裝 CINC 檢測引擎到 image 內用的，只出現在文件與 Dockerfile；客戶執行期不連 |
| `developers.cloudflare.com/...` | 註解裡的說明文件連結 |

## 五、外部資源載入（字型／CDN）

✅ **零項**。FE 全部字型與 JS 皆打包進 image，`index.html` 無任何 CDN／Google Fonts
引用。實測掃描 `fonts.googleapis`／`fonts.gstatic`／`cdn.`／`unpkg`／`jsdelivr` 皆無命中。

這點對 air-gap 很關鍵：外部字型的失效方式是「頁面能開但排版跑掉或載入卡住」，
屬於難以歸因的問題。

## 停用方式（Turnstile）

**這是 BE/FE 一對的開關，只關一邊都會壞：**

- 只關 FE、BE 還在驗 → BE 永遠收到空 token → 一樣登不進去
- 只關 BE、FE 還在載 script → air-gap 下前端卡在載入

| 端 | 變數 | 落地版值 | 設定位置 |
|---|---|---|---|
| BE | `TURNSTILE_ENABLED` | `false` | `guidant.env`，**由 install.sh 自動寫入**（不依賴人記得） |
| FE | `VITE_TURNSTILE_ENABLED` | `false` | `.env.onprem`，**build 期烙進產物**（`npm run build:ONPREM`） |

兩者**預設值皆為啟用**，這是刻意的：SaaS 版靠它擋自動化攻擊，若預設關閉，任何忘記
設定的雲端部署都會靜默失去防護——失效方向必須朝向「保留防護」而不是「靜默放行」。

### 若某客戶環境其實有外網、想啟用

1. `guidant.env` 改 `TURNSTILE_ENABLED=true`，並填入真實的 `TURNSTILE_SECRET_KEY`
2. FE 需以 `VITE_TURNSTILE_ENABLED=true` ＋ 真實 site key 重新 build image

（FE 的值是 build 期烙印，不能只改執行期環境變數。）

## 防火牆規劃建議（給客戶手冊）

**完全封閉網路（air-gap）可正常運作**，前提是：

1. Turnstile 已停用（installer 自動處理）
2. 不使用 AI 功能、Google Drive 同步、Telegram 通知
3. 授權走離線開通（機器碼 → 原廠簽照 → 匯入）

**若客戶要開放部分外網**，按需求逐項開通即可；除 Turnstile 外沒有任何「必須開通否則
系統不能用」的對外連線。

## 驗證紀錄（2026-08-17）

| 項目 | 結果 |
|---|---|
| `TURNSTILE_ENABLED=false` ＋ 請求**完全不帶** `cf_turnstile_token` | ✅ 登入成功、取得 JWT |
| 未設 `TURNSTILE_ENABLED`（預設啟用）＋ 不帶 token | ✅ 被擋 `401 CAPTCHA_400001`（防護未被弱化） |
| 未設 `TURNSTILE_ENABLED` ＋ 帶測試 token | ✅ 登入成功 |
| FE `.env.onprem` build | ✅ `VITE_TURNSTILE_ENABLED=false`，停用時 script 不注入、容器不渲染 |

> ⚠️ 實作時發現的一個連帶問題：`cf_turnstile_token` 原本在 request schema 是
> `required=True`，停用時 FE 不會帶這個欄位 → marshmallow 會在**進到 route 之前**
> 就回 422，route 內的停用判斷永遠執行不到（症狀是「明明關掉了還是登不進去」）。
> 已改為選填；啟用時仍會把 `None` 交給驗證函式，空 token 一樣驗不過——**擋人機驗證
> 的是那道驗證，不是 schema 的 required 旗標**。
