# SonarQube Security Hotspots 逐條審查報告（CM-890）

- **日期**：2026-07-25
- **Server**：http://192.168.50.171:9000（SonarQube Community Build **26.7.0.124771**）
- **專案**：`guidant-ai-backend`（最後分析 2026-07-25T03:27Z）、`guidant-ai-fe`（2026-07-25T03:31Z）
- **背景**：07-24 第一棒拉 hotspots 時 token 回 Insufficient privileges，本次補審——這是掃描入 CI/CD 前唯一未看過的安全面盲區。

## 1. 結論摘要

| 項目 | guidant-ai-backend | guidant-ai-fe |
|------|-------------------:|--------------:|
| Security Hotspots（TO_REVIEW） | **0** | **0** |
| Security Hotspots（REVIEWED） | 0 | 0 |
| `security_hotspots` metric | 0 | 0 |
| 等價審查面：SECURITY-impact open issues | **7** | **0** |

- **權限已通**：本次 token 呼叫 `api/hotspots/search` 正常回應（07-24 的 Insufficient privileges 已不復現，推測為權限已補或新 server 預設權限不同），無需雷門再補權限。
- **hotspots = 0 是結構性為零，不是漏掃**（判定依據見 §2）。
- 等價審查面（SECURITY-impact issues）BE 7 條逐條判定：**7/7 安全無虞或已有既存追蹤**，無新風險、無需開新 case（見 §3）。
- **標 SAFE 數 = 0**（沒有 hotspot 可標；`api/hotspots/change_status` 本次未使用）。待裁決清單：無。

## 2. 為何兩專案 hotspots 都是 0（驗證過程）

逐層排除「假 0」的可能：

1. **權限**：`api/hotspots/search?project=guidant-ai-backend` 回 200、`paging.total=0`（非 Insufficient privileges）。
2. **有掃到**：兩專案 `api/project_analyses/search` 皆有 2026-07-25 的分析紀錄；`ncloc` BE 90,383 / FE 84,696。
3. **規則面（root cause）**：
   - py/js/ts 三個 quality profile 內 **active 的 SECURITY_HOTSPOT 規則數 = 0**（py profile 全部 active 規則 362 條，type facet：CODE_SMELL 232 / BUG 112 / VULNERABILITY 62 / SECURITY_HOTSPOT 0）。
   - **全 server** SECURITY_HOTSPOT 規則僅 2 條（cs=1、vbnet=1），與本案兩專案語言無關。
   - 抽查傳統 hotspot 規則現況：`python:S1313`（硬編碼 IP）→ CODE_SMELL / SECURITY:LOW；`python:S5332`（http 明文）→ VULNERABILITY / SECURITY:LOW；`python:S2245`（偽隨機）→ VULNERABILITY / SECURITY:MEDIUM；`python:S4830`（憑證驗證停用）→ VULNERABILITY / SECURITY:HIGH。
4. **結論**：SonarQube 2025.x Community Build（26.7）採 MQR（Multi-Quality Rule）模式，**已把絕大多數傳統 Security Hotspot 規則重分類為帶 SECURITY impact 的一般 issue**。因此「hotspot 待審清單」在這個版本上天然為空，原本會落在 hotspots 的項目全數改走 issues 流——而 issues 流我們在 07-24/07-25 兩輪已全數審過（見 `2026-07-25-rescan-medium-analysis.md` §5）。
5. **CI Quality Gate 影響**：`security_hotspots=0` ⇒ 「hotspots reviewed 比例」條件恆為滿足（無存量可滾），接 CI 不會因 hotspots 打紅 gate。安全面守門實際落在 SECURITY-impact issues / VULNERABILITY 條件上。

## 3. 等價審查面逐條判定（SECURITY-impact open issues）

Hotspot 清單為空，故將「本應是 hotspot」的 SECURITY-impact open issues 逐條讀程式碼現場判定，補齊人工審查盲區。

### guidant-ai-backend（7 條）

| # | 規則 | 位置 | 內容 | 判定 |
|---|------|------|------|------|
| 1 | S1313（MINOR/LOW） | `config/config.py:9` | `AWD_DEFAULT_URL = "https://192.168.50.189/"` dev 預設 URL 硬編碼內網 IP | **安全無虞（風險接受）**。dev config 內網預設值，與 §5 既有結論「dev config 風險接受」同型；且屬 https。camunda/內網 IP 硬編碼已有既存 case 追蹤，不重複開。 |
| 2 | S1313（MINOR/LOW） | `scripts/backfill_2026-06-17_inject_job_execution_uid.py:32` | 一次性 backfill script 連 dev DB（192.168.50.188:25432） | **安全無虞**。非產品程式碼、僅本機執行的一次性維運腳本；密碼取自 `.env` 未硬編碼。 |
| 3 | S1313（MINOR/LOW） | `scripts/gen_e2e_excel_asia_airlines.py:150` | 測試資料假 IP `10.10.20.5` | **安全無虞（誤報）**。E2E 測試 fixture 的虛構資產清冊資料。 |
| 4 | S1313（MINOR/LOW） | `scripts/gen_e2e_excel_asia_airlines.py:151` | 測試資料假 IP `10.10.30.10` | 同上，**安全無虞（誤報）**。 |
| 5 | S1313（MINOR/LOW） | `scripts/gen_e2e_excel_asia_airlines.py:152` | 測試資料假 IP `10.10.40.20` | 同上，**安全無虞（誤報）**。 |
| 6 | S6890（CRITICAL/LOW） | `app/report/common/common_utils.py:4` | `import pytz`（建議改用 zoneinfo） | **非安全風險（現代化建議）**。pytz 時區處理在 Python 3.9+ 建議改 stdlib `zoneinfo`，屬 refactor backlog，非漏洞；不在本任務（不改產品碼）範圍。 |
| 7 | S6890（CRITICAL/LOW） | `common/util/common_util.py:10` | `import pytz` | 同上，**非安全風險（現代化建議）**。 |

### guidant-ai-fe（0 條）

SECURITY-impact open issues = 0，無需審查。（FE 既有 VULN 8 條已於 §5 前輪全數判誤報並處理。）

## 4. 統計

- Hotspots 總數：BE 0 + FE 0 = **0**（TO_REVIEW 0 / REVIEWED 0）
- 標 SAFE：**0**（無標的）
- 等價審查面判定：BE 7 條 → 安全無虞/風險接受 5、誤報型 5（含重疊）、現代化建議 2；**真風險 0、開新 case 0、待裁決 0**
- FE：0 條

## 5. 後續建議（不在本任務執行）

1. **S6890 pytz → zoneinfo**：兩處 import 屬技術債，建議併入未來 refactor batch（非資安急件）。
2. **CI Quality Gate 設計**：hotspots reviewed 條件在此版本形同虛設，gate 條件應以 `new_violations` / SECURITY impact 為主。
3. 若日後 Sonar 升級或加裝 plugin 使 hotspot 規則回歸，需重跑本審查流程。

---
*審查方式：SonarQube Web API（hotspots/search、measures/component、rules/search、issues/search）+ 逐條讀取對應程式碼現場。API 回應均落檔 /private/tmp 後以 jq 解析。含 token 之暫存檔已清除。*
