💡 今日學習目標:掌握在 SonarQube 介面中審查一筆資安問題的標準流程,學會把不安全的程式碼重構掉,並理解「修掉」與「標記」這兩條路各自該在什麼時候走。
經過昨天的掃描,我們在 SonarQube 的儀表板上拿到了專案報告:Security 面向有 1 筆待處理的問題。
今天就用它走完一次完整流程:解讀 ➔ 判斷 ➔ 重構 ➔ 重新掃描 ➔ 歸檔。
遇到這種告警,新手最常犯的兩個錯誤是:
這兩件事我們今天都會避開。
先處理一件會讓你困惑的事,不然你會一直覺得哪裡怪怪的。
Day 08 在 IDE 裡,那條 md5 的告警被歸類為 Security Hotspot(資安熱點)。 但你昨天掃進伺服器之後,它出現在 Issues(問題) 清單裡,而不是獨立的 Hotspots 區塊。
點進去看,你會看到這個標籤:

former-hotspot,直譯就是「曾經是 Hotspot」。SonarQube 自己承認了這件事。
Sonar 官方正在進行一項轉換:把過去歸類為 Security Hotspot 的規則,逐步改成直接產出資安問題。用官方文件的說法:
Rules that previously raised security hotspots will start raising vulnerabilities (Standard Experience) or security issues (MQR Mode).
(過去產出資安熱點的規則,將開始產出漏洞(標準模式)或資安問題(MQR 模式)。)
📖 資料來源:Sonar Documentation — Managing Security Hotspots
而且不只是規則層級的調整:在 v26 的左側選單裡,整個 Security Hotspots 頁面已經被掛上 Deprecated 標籤(步驟 4 的那張截圖就看得到)。這條路 Sonar 是真的打算收掉。
Hotspot 這個設計原本的用意很好:「這段程式碼碰到了敏感領域,但我不知道你的用途,你自己判斷」。問題是它多了一層心智負擔,很多團隊看到 Hotspot 就不知道該不該當一回事,久了就整批忽略。
改成 Issue 之後,處理方式跟其他問題一致:要嘛修掉,要嘛明確標記為「已接受」並寫下理由。少一種特殊分類,就少一個被推託的藉口。
🔑 不過概念本身沒有消失。 「工具只能指出敏感用法,判斷用途的責任在你」這件事永遠成立,這也是 Day 08 心法 3 講的。變的只是它在介面上叫什麼名字。
在儀表板點擊 Security 底下的 1 Open issues,會進到 Issues 清單。點那筆問題,右側會展開完整說明。

畫面上有幾個地方值得看清楚:
javascript:S4790:點它會連到官方規則說明,那是最權威的出處。Where is the issue?(問題在哪)、Why is this an issue?(為什麼這是問題)、How can I fix it?(怎麼修)、Activity(處理紀錄)、More info(延伸資料)。請務必把 Why is this an issue? 讀完再動手。 這幾分鐘是整個流程裡最有價值的部分。你不是在應付一個紅字,而是在讀一份寫得很好的資安教材。
我們要修的就是 Day 09 建立的那個 checksum.js,它在計算檔案的完整性校驗碼:
const crypto = require("node:crypto");
// 計算檔案的完整性校驗碼(checksum)
function generateChecksum(fileBuffer) {
// ⚠️ MD5 已被證實可以人為製造碰撞
return crypto.createHash("md5").update(fileBuffer).digest("hex");
}
module.exports = { generateChecksum };
import hashlib
def generate_checksum(file_bytes: bytes) -> str:
# ⚠️ MD5 已被證實可以人為製造碰撞
return hashlib.md5(file_bytes).hexdigest()
⚠️ 風險解析:MD5 與 SHA-1 在密碼學上都已被證實存在**雜湊碰撞(Collision)**缺陷:攻擊者有辦法構造出兩份內容完全不同、但雜湊值一模一樣的檔案。
一旦如此,「用雜湊值確認檔案沒被掉包」這件事就失效了:惡意檔案可以偽裝成正常檔案通過你的校驗。
依照 How can I fix it? 分頁的建議,把弱雜湊換成現代標準的 SHA-256:
const crypto = require("node:crypto");
function generateChecksum(fileBuffer) {
// 🛡️ 改用 SHA-256,目前沒有已知的實用碰撞攻擊
return crypto.createHash("sha256").update(fileBuffer).digest("hex");
}
module.exports = { generateChecksum };
import hashlib
def generate_checksum(file_bytes: bytes) -> str:
# 🛡️ 改用 SHA-256
return hashlib.sha256(file_bytes).hexdigest()
🧭 順帶釐清一組很常被搞混的概念:
- 校驗碼(Checksum):只是要確認「內容有沒有被改動」,用 SHA-256 這種強雜湊就夠了,不需要金鑰。
- 簽章(Signature):要證明「這份資料確實出自持有金鑰的人」,就必須有金鑰,該用的是 Day 08 提過的 HMAC。
換句話說,如果你的函式名字裡有「signature」,那光把 MD5 換成 SHA-256 是不夠的。你缺的不是更強的雜湊,而是根本還沒有金鑰參與。
審查一筆資安問題,最後一定會走到兩條路的其中一條。分清楚這兩條路,是這整篇最重要的觀念。
sonar-scanner 指令。因為程式碼裡已經沒有 md5 了,規則自然不再命中。這種情況你不需要去按任何按鈕,什麼都不用標記。
💡 這也是為什麼儀表板上的數字會自己變好。你要做的不是「處理掉那個數字」,是處理掉那段程式碼。
有些時候,工具點出的敏感用法在你的情境裡是無害的。例如:這個 MD5 只是用來為快取產生一個鍵值,跟安全性完全無關。
這時候才輪到標記功能上場。在問題詳情頁點開狀態下拉選單(預設是 Open),會跳出 Change issue status:

四個選項,但實際上你只該用前兩個:
| 選項 | 官方說明 | 什麼時候用 |
|---|---|---|
| Accept | Won't fix immediately, but will no longer affect the quality gate | 風險已知、已評估,且在這個情境下可以接受 |
| False Positive | Analysis is incorrect | 規則誤判,例如它把測試用的假資料當成真金鑰 |
| 已標示 Deprecated | 不要用 | |
| 已標示 Deprecated | 不要用 |
Accept 與 False Positive 的差別很重要:前者是「我知道它是問題,我選擇承擔」,後者是「它根本不是問題」。用錯會誤導下一個看報告的人。
⚠️ 請把 Accept 的官方說明再讀一次:「will no longer affect the quality gate」。
按下去的那一刻,這筆問題就不再擋你的品質關卡了。這正是它危險的地方:它是一個可以把紅燈變成綠燈的按鈕。 所以下面那句「一定要寫理由」不是形式主義,那是唯一能攔住這個按鈕被濫用的東西。
不論選哪一個,一定要在留言欄寫下判斷理由。一個夠格的理由要能回答三件事:這段程式碼實際在做什麼、為什麼在這個用途下無害、誰在什麼時候做的判斷。像這樣:
此 MD5 僅用於產生本地快取的鍵值,不參與身分驗證、完整性校驗或任何安全決策,輸入也不受外部使用者控制。經團隊評估後接受此風險。確認日期:2026-xx-xx
反過來說,下面這幾種寫法等於沒寫:「不影響」「這個沒問題」「之後再處理」「PM 說先上線」。它們共同的問題是沒有描述用途,而用途正是這整個判斷唯一的依據。
🛑 這一步不能省。 一個沒有理由的
Accept,對三個月後的你或接手的同事來說,跟沒審查過完全一樣,他無從判斷你是真的想過,還是只是想把數字清成零。而 Day 06 說過的那個判準在這裡依然適用:你是真的解決了風險,還是只是讓工具閉嘴?
📊 順帶一提,SonarQube 有幫你盯著這件事。 儀表板上有一個獨立指標叫 Accepted issues,說明文字寫著「Valid issues that were not fixed(有效但未修復的問題)」。
這個數字如果一路往上長,代表你的團隊正在累積技術債而不自知。它被單獨拉出來顯示,就是為了不讓「標記完就忘記」變成常態。
一個都不要選。
我們的 checksum.js 是拿 MD5 去做檔案完整性校驗,這正好就是 MD5 碰撞會讓防線直接失效的場景。它不是誤判,也不是可以接受的風險,它就是一個該修的問題。所以我們走路徑 A:改成 SHA-256、重新掃描,讓它自己從清單上消失。
上面那張下拉選單的截圖,是點開來看、然後關掉的。
認識這個選單長什麼樣子是必要的,但認識它真正的目的,是為了知道自己什麼時候不該用它。在你伸手去按 Accept 之前,先問一次:我是沒辦法修,還是不想修?
恭喜!到今天為止,我們完成了 階段二:資安是檢查出來的(Day 06 - Day 11)。
這六天,我們一路走過:
你現在擁有的,是一條實實在在、一毛錢沒花的檢查防線。
但別忘了 Day 03 戴明說的那句話:「檢查已經太遲了。品質的好壞,早就已經在產品裡面了。」
我們今天修掉的這個問題,其實是幾天前就已經寫進去的。如果在敲下那行程式碼的當下就寫對,這些重掃、重構、重新審查的時間,通通可以省下來。
從 Day 12 開始,我們要往品質演進的下一階前進,探討卡內基梅隆大學 SEI 的 Secure Coding 十大法則,以及 OWASP Top 10 的逐項攻防實戰,讓漏洞從一開始就沒有機會被寫出來。
💬 明日預告:【Day 12】既然事後要修,為什麼不一開始就寫好?SEI Secure Coding 核心哲學
明天我們將進入階段三,學習 SEI 提出著名的 Secure Coding 哲學與法則!