FR-177 C1 掃描報告:SonarQube connector(單檔 1,711 行)

⚠️ 歷史紀錄:本報告描述的問題已於 2026-11-07 全部修正並合回 Agent 主線(修正卡 CM-3559~3564、3573~3576、3584,驗收紀錄見母卡 CM-3507)。文中的檔名與行號是掃描當下(commit bab2aa3)的版本,修正後已變動,請勿拿來定位現行程式。

§1

一句話結論

沒有新的高風險問題。 工具找到 1 個問題(中):repo_url 沒過授權目標清冊。這條和已登記的總表 #224(B5 F2)是同一件事,不重報,只是從 connector 內部再確認一次。人工逐條查了卡上 7 個重點,沒有新的高風險,但補到 3 個值得記下的小面向(都是低):

  1. 源碼包裡的 sonar-project.properties 沒被刪掉,違反本 repo 規則 SR-04.10;
  2. 逾時或取消時只殺主行程,沒殺整個 process group;
  3. pull 模式的 project_key 沒限制只能讀 Agent 自己建的專案。
§2

這一棒在檢查什麼

Agent 代客戶跑 SonarQube 有三種模式:

  • scan:從 git 拉源碼;
  • upload:用上傳的源碼包;
  • pull:只讀 SonarQube 上已有的結果。

本棒看 connector 拿到參數之後有沒有再過濾,以及哪些欄位會進到外部指令(git clone、sonar-scanner)或 HTTP 請求。

範圍只有 guidant_agent/scan/adapters/connectors/sonarqube.py 這 1 檔。為了查證,翻了範圍外的 guidant_agent/shared/archive_safety.py(只為查證、不當發現)。

§3

總覽表

# 這是什麼問題 出事會怎樣 要先有什麼 在哪裡 怎麼修 嚴重度
F1(=#224,不重報) repo_url 只檢查開頭是不是 http/https,沒過授權目標清冊 Agent 被當跳板,去 git clone 內網任意主機;git 的錯誤原文最多 500 字回給使用者,可以拿來探測內網 能派 SonarQube scan 任務(雲端或主控台 run-local-scan)、Agent 主機裝有 git sonarqube.py:836-838(檢查在 :430-443) 把 repo_url 納入清冊檢查,或設允許的 Git 主機清單;clone 加 -c http.followRedirects=false 中
M1(人工) 源碼包或 repo 裡的 sonar-project.properties 沒刪 包內設定可以改 sonar.exclusions/inclusions/scm.*,讓掃描少掃一塊,報告看起來乾淨 能提供源碼的人(本來就控制源碼) sonarqube.py:950-970、:1054-1060 解壓或 clone 後刪掉該檔,或命令列明示 exclusions= 等關鍵參數 低
M2(人工) 逾時或取消時只對主行程送 SIGTERM/SIGKILL sonar-scanner 若由殼層腳本再開 JVM,JVM 可能變成孤兒繼續吃 CPU 和記憶體 掃描逾時或被取消 sonarqube.py:1180(Popen 沒設 start_new_session)、_terminate start_new_session=True 加 os.killpg(記得容器 PID 1 的限制) 低
M3(人工) pull 模式的 project_key 是使用者填的,沒有限制範圍 能讀到客戶 SonarQube 上 token 看得到的其他專案結果,寫進證據報告 能派 pull 任務;範圍由 token 權限決定 sonarqube.py:424-428 改成產品決策:限制 Guidant-AI- 前綴,或要求客戶給最小權限 token 低
§4

每條詳述

F1 — repo_url 不過清冊(=總表 #224)

白話: 使用者填的 Git 位址只檢查開頭是不是 http:// 或 https://(:438),然後直接交給 git clone(:836-838)。「只准連哪些主機」的授權目標清冊(allowed_targets.iter_targets)只看 target/urls 這類欄位,不看 repo_url。

實際拼出的指令:

git -c credential.helper= clone --depth 1 --quiet <repo_url> <workspace>/source

env 為 {**os.environ, "GIT_TERMINAL_PROMPT": "0"}。

三位檢查員 3:0 成立。已登記 #224,修法照那條走。

M1 — sonar-project.properties 沒刪

白話: sonar-scanner 會讀源碼目錄裡的這支設定檔。命令列已經明示 sonar.host.url、projectKey、projectBaseDir、sources,命令列的 -D 參數優先於檔案內容,所以沒辦法用它把 token 送到別的伺服器(卡上擔心的那條不成立)。但 exclusions/inclusions/scm.* 沒有鎖死。

為什麼是低: 工具面板 3:0 判不成立,理由是能放這支檔的人本來就控制源碼。但這違反本 repo .claude/rules/security/external-calls-and-tools.md 的明文規則(關鍵參數命令列鎖死,或掃描前刪掉包內設定檔)。對稽核來說,「客戶自己把漏洞藏起來」是真實的動機,所以仍記為低。

M2 — 沒殺整個 process group

Popen(command, stdout=..., stderr=..., env=env) 沒有 start_new_session,_terminate 用的是 process.terminate()/kill()。官方 sonar-scanner 啟動腳本最後通常用 exec java,同一個 PID 就殺得到。所以這條只在打包版本不同時才會成立,未實機驗證。

M3 — pull 模式可讀其他專案

scan/upload 模式的 key 都強制加 Guidant-AI- 前綴並走字元白名單(:445-463),pull 模式不加。這比較接近設計取捨,交首腦判斷要不要開卡。

§5

重點逐條人工查證

卡上重點 人工結果
:836-842 git clone:ext::、file://、-u 開頭、--upload-pack、--、ssh、env 不成立(除 #224)。 開頭必須是 http(s),所以 ext::、file://、ssh://、git@、- 開頭都進不來。少一個 -- 也不會被當成選項;--upload-pack 只能從選項位置進,URL 位置不行。http(s) 不走 SSH,不會帶出 SSH 金鑰。GIT_SSH_COMMAND、GIT_CONFIG_* 沒清,但它們只來自 Agent 自己的環境,使用者控制不到。剩下的是 redirect:git 預設跟第一次 redirect,已含在 #224 的修法
:1054-1064 -D 參數被當 Java properties 解析、project_key 白名單、源碼包內 sonar-project.properties 部分成立,見 M1。 -D 是 argv 的一個元素,Java 當成系統屬性的鍵值直接用,不會解析 \u 或換行;scan/upload 的 key 有白名單。branch 不進 scanner(只用在 pull 的 HTTP 參數,httpx 會編碼)。包內設定檔改不了 host.url,但改得了 exclusions,見 M1
:1064 繼承整個 os.environ;輸出檔有沒有遮 token 不成立。 繼承的是 Agent 自己的 env,子行程是 Agent 信任的工具,沒有新的外送路徑。token 走 SONAR_TOKEN 環境變數,不進 argv。scanner 不會把 token 印出來;錯誤訊息只取 ERROR 行、最多 1000 字。輸出檔落在 workspace,結束時刪除
:348-355 httpx:verify、redirect、錯誤原文 不成立(另見 #232)。 沒寫 verify=,預設就是驗證。follow_redirects=True,但 httpx 跨主機 redirect 時會拿掉 Authorization,token 不會跟著跳。base_url 沒填 scheme 時預設補 http://(:336),面板 3:0 判為管理員設定,不算漏洞。錯誤訊息帶 server 原話,是 SonarQube 的 msg,不含 token
:475-492 源碼包:archive_safety、symlink、workspace 刪除 不成立。 解壓走 archive_safety:tar 用 filter="data",zip 有路徑消毒,逃逸型 symlink 被擋(tar 計數 DoS 同 #226)。workspace 由 tempfile.mkdtemp 建、finally 用 rmtree 刪(:767-781)。本機上傳檔(keep_archive=True)刻意保留到保留期滿,屬設計
:1162-1221 _run_with_cancel 殺不殺得到整組 可能成立,見 M2(低)
pull 模式讀其他專案 成立但屬設計取捨,見 M3(低)
§6

可信度兩層

  • 工具層: stamp 為 verified。3 個候選,9 張票。F1 3:0 成立;另兩條(包內設定檔、http 預設)各 0:3 被否決。研究員派出 2 位、回來 2 位,沒有被看門狗砍(journal.jsonl 零 failed)。
  • 人工層: 卡上 7 個重點逐條開檔看過(上表)。M1~M3 是人工補的,沒有經過面板投票,也沒有實機執行。
§7

執行概況(照 stamp 抄)

  • run ID:wf_e0943356-2d7,effort low,focus attack-surface,revision 3d5aa2a
  • verification:status=verified、candidates=3、candidates_deduped=3、panel_votes=9、panel_reviewed_findings=1、panel_quorum_findings=1、unreviewed_candidate_sites=0、incomplete_panel_candidates=0、researchers_dispatched=2、researchers_returned=2、reason=null
  • 耗時約 28 分鐘
  • 歸檔:docs/security-scan/FR-177/runs/CLAUDE-SECURITY-20261106-025642/