GitHub AI Scan 實測:找出兩處 Shell 注入風險,安全檢查卻顯示 Success

今天,我在一個公開測試儲存庫送出了 PR #1,刻意放進兩段有風險的 Bash 程式碼。大約 81 秒後,github-advanced-security[bot] 在 Files changed 留下兩則逐行評論,兩個問題都被指出來。

可是點開 GitHub Actions 執行紀錄 #38835686338,github-advanced-security 這個 job 卻顯示 Success。漏洞提醒是真的,綠色檢查也是真的。

這次測試的前一天,GitHub 才宣布 AI Scan for pull requests 正式 GA。我想釐清的不是「AI 掃描到底有多厲害」,而是更直接的問題:既然指出了危險程式碼,為什麼 PR 的安全檢查仍是成功?

關鍵在於:AI Scan 執行成功不代表程式碼通過安全審查;目前它的發現屬於建議,不是強制合併門檻。

兩段 Bash 程式碼,實際找出了什麼

我只在暫時性 PR 的 ai-scan-test/ 目錄放測試檔,沒有動到正式環境的業務程式碼,也沒有使用真實憑證或伺服器。完整檔案保留在 固定提交 29e24722d3。下面的寫法是刻意保留的危險範例,不要用在正式環境:

eval "systemctl restart ${service_name}"

cmd="curl -fsSL http://${host}/${path} -o /tmp/svc.conf"
$cmd

第一處在重新啟動服務的函式中。外部提供的 service_name 被串進 eval,Shell 會把組好的文字再次當作指令解析。只要服務名稱混入惡意 Shell 語法,問題就不只是傳錯服務名稱。機器人在第 17 行的評論 將它歸類為 CWE-78,建議移除 eval、直接以引號保護參數,並限制可重新啟動的服務。

第二處看似只是少了引號,機制卻不同。程式先把整條 curl 指令放進 cmd,接著直接執行未加引號的 $cmd。變數展開時的單字拆分,讓可控制的 host 或 path 有機會改變 curl 收到的參數與選項。第 27 行的評論 也提醒透過未加密的 http:// 下載設定檔,可能遭到傳輸途中竄改。

位置AI Scan 實際發現原始評論
第 17 行,eval輸入可能被重新解讀成 Shell 指令(CWE-78)查看評論
第 27 行,$cmd參數/選項注入,另有明文 HTTP 風險查看評論

兩者不能都簡化成同一種任意指令執行。第一處會讓輸入重新進入 Shell 語法解析;第二處目前確認的主要是參數/選項注入,不能只因為變數沒加引號,就斷言可以執行獨立的任意 Shell 指令。

還有個必須誠實交代的細節:這份測試樣本沒有隱藏風險。README 和程式註解明寫 INTENTIONAL VULNERABILITY (CWE-78),也標了預期的 String injection 類別。機器人提出的具體利用例子不在原始碼中,但這些預先標示可能影響辨識效果;所以這次 2/2 命中,不等於對未標示真實漏洞的偵測率。

時間線也有公開紀錄可查。PR #1 建立於 2026-10-16 01:32:31 UTC,兩則評論都在 01:33:52 UTC 發出,相隔 81 秒。這是單次 PR 從建立到評論的觀察值,不是多次掃描的平均耗時。

兩個刻意植入的問題都收到評論;我並沒有另外建立完全正常的程式碼 PR 作為陰性對照,因此也沒有量測到誤報率。

有兩則漏洞評論,為什麼 Actions 還是綠色

在 動態掃描執行 #38835686338 中,工作流程名稱是 Code scanning AI findings on PR #1。GitHub 記錄顯示,github-advanced-security job 早在 01:33:34 UTC 就以 success 結束,兩則機器人評論直到 01:33:52 UTC 才出現,晚了 18 秒。

先有綠色檢查,後有漏洞評論。這個先後順序已說明,job 的 Success 不能解讀成「沒有安全發現」。

GitHub AI Scan PR #1 的機制示意:兩處有預先標註的 Bash 風險收到建議性評論,Job 於 01:33:34 成功,評論在 01:33:52 出現,合併門禁須另外設定

圖:依公開 PR #1 與 Actions 執行紀錄 繪製的機制示意,不是 GitHub 網頁截圖。81 秒代表 PR 建立到評論出現的間隔,不是掃描本身的耗時。各語言頁面使用對應版本的 SVG。

這不代表掃描程式出錯。GitHub 的 AI Scan 官方文件 寫明:這些發現屬於 advisory,不會使 code scanning check 失敗,也不能直接靠 Ruleset 的要求阻止 PR 合併。它是在提醒審查者注意風險,不是替團隊做合併決策。

GitHub 上的訊號它實際代表的事
AI Scan 逐行評論可能有風險,交由作者與審查者處理;本身不直接阻擋合併
動態 AI Scan job 的 Success掃描任務已完成;即使產生風險評論仍可能顯示綠色
CodeQL 等支援強制檢查的掃描與 Ruleset可以按照儲存庫實際設定限制合併,但應驗證規則是否真的生效
Required checks 與人工審查檢查失敗、缺失或未通過審查能否合併,取決於儲存庫設定

GitHub 另有 Code scanning merge protection 設定文件。如果想達成「高風險問題未處理就不能合併」,必須確認使用的是有強制力的設定,不能把 AI Scan 的建議性評論當成 required check。

我沒有拿這份危險 PR 實際測試 Ruleset 是否阻擋合併,因此不會由 Success 推論 GitHub 允許所有有漏洞的合併。這次能確定的是:綠色 AI Scan 任務與兩則安全建議確實同時存在。

啟用前先看 CodeQL、儲存庫資格與計費

這次使用個人帳號底下的公開儲存庫。依 2026 年 10 月 GA 後的官方說明,個人公開儲存庫可以啟用 AI Scan,個人私人儲存庫不支援;組織公開儲存庫可用,組織 private 或 internal 儲存庫則須符合 GitHub Code Security/GitHub Advanced Security 的授權條件。使用 AI Scan 本身不要求額外購買 Copilot 授權。

CodeQL default setup 不是前置條件。測試時我記錄到的狀態是 not-configured,AI Scan 依然留下了兩則評論。官方文件 也明確說,AI Scan 不依賴 CodeQL default setup 或 CodeQL 分析是否成功。

它也不是掃描整個儲存庫的歷史漏洞。AI Scan 主要檢查符合條件的 PR 變更,也包含 Shell/Bash、Terraform HCL、Dockerfile 等 CodeQL 尚未涵蓋的部分情境。來自 Fork 或 Dependabot 的 PR 不在這次公告的支援範圍;如果團隊大量使用這類 PR,還是需要其他掃描與審查方式。

啟用時請到儲存庫的 Code scanning 設定查看 AI Scan 開關;組織儲存庫還要確認組織政策及相關安全功能。

GA 後採 Code Security AI Credits 計費。由誰承擔費用、是否含可用額度,以及超額後預算如何限制,請以 GitHub 的計費規則 為準。這次測試的實際扣費沒有最終核實,所以不會寫成 $0。PR 活躍的儲存庫,最好先到 Billing 檢查額度和硬性預算。

要真正擋住危險合併,該怎麼做

先處理程式碼。兩則評論 的修正方向很直接:不要把參數再交給 eval,也不要把一整條 Shell 指令串成字串後交由展開機制處理。呼叫方式可調整成:

# 调用形式示意:service_name 和 url 仍需单独校验
systemctl restart "$service_name"
curl -fsSL "$url" -o /tmp/svc.conf

這只是修正方向,不是已重新驗證的安全修補。service_name 還需要允許清單;下載網址則須限制 HTTPS、目標主機與重新導向行為,並視設定檔的重要程度驗證完整性。

儲存庫層面,可以將 CodeQL 或其他真正支援阻擋的掃描接上合適的 Ruleset/required checks,然後實際驗證受保護分支會拒絕失敗或缺失的檢查。AI Scan 的評論則交由審查者逐一判斷,不要看到動態掃描 job 是綠色就直接略過。

測試結束後,我關閉 PR #1,沒有合併,也刪除了測試分支。公開儲存庫的 main 仍是原本的 87624c26a5e6;所有者 REST 查詢收據顯示 AI Scan 開關已恢復為 disabled。關閉 PR 不會刪除歷史評論與固定提交,公開連結仍可逐項核對。

這次實測證明,AI Scan 能在這個很小、明確標記的陽性樣本中提供有用的逐行提醒,但掃描任務成功和合併安全是兩道不同的關卡。前者幫你發現問題,後者還得靠團隊的規則與審查流程。

如果你也在意 Git 推送前的審查是否會失敗放行,可以再看 OpenQodex 本地 pre-push Hook 實測;那是另一個執行位置,也有不同的門禁風險。

實測日期:2026 年 10 月 16 日(UTC)。