[安全測試] 001 SAST 靜態分析入門:讓工具幫你審程式碼——SonarQube、Semgrep 工具使用與 IDE 整合實戰
「人的眼睛會累、會遺漏,但工具不會。
靜態分析不是取代 Code Review,而是幫你在 Review 之前,先過濾掉那些不該犯的錯。」
— SSDLC by 飛飛
一、SAST 是什麼?為什麼它是你的「自動驗屋師」?
在 SSDLC 的蓋房子旅程中,我們已經走過了安全需求定義(確認要防什麼)、安全設計(畫好藍圖、標好消防通道)、安全實作(用防火建材施工、學會輸入驗證與輸出編碼、熟悉 OWASP Top 10)。
現在,我們來到了階段四:安全驗證(Verification)——房子蓋好了,要請結構技師來檢查。
想像你花了半年蓋好一棟房子。交屋前,你會做什麼?當然是請專業的驗屋師來檢查——看看牆壁有沒有裂縫、水管有沒有漏水、電線有沒有接錯。你不會等住進去之後才發現問題,對吧?
SAST(Static Application Security Testing,靜態應用程式安全測試)就是軟體世界的「自動驗屋師」。 它不需要把程式跑起來,直接看你的原始碼,找出裡面潛藏的安全漏洞。
跟動態測試(DAST)不同的是——DAST 是把系統跑起來然後從外面攻擊它(像小偷試著破門而入),而 SAST 是打開牆壁看裡面的結構(像 X 光檢查)。兩者互補,但 SAST 的最大優勢是:你可以在程式碼還沒部署之前就抓到問題。
| 比較項目 | SAST(靜態分析) | DAST(動態分析) |
|---|---|---|
| 比喻 | X 光檢查建築結構 | 派小偷來試著破門而入 |
| 檢查時機 | 寫程式時 / CI/CD 階段 | 系統部署後 |
| 需要執行程式嗎? | 不需要 | 需要 |
| 能看到原始碼嗎? | 能(白箱) | 不能(黑箱) |
| 擅長找什麼? | SQL Injection、XSS、硬編碼金鑰、不安全的函式呼叫 | 認證問題、設定錯誤、運行時漏洞 |
| 誤報率 | 偏高(但可調整) | 偏低 |
| 修復成本 | 低(問題還在開發者手上) | 高(可能要改架構) |
飛飛觀點:
我常跟團隊說,SAST 就像開車上路前的車輛檢查。你不會等車子在高速公路上拋錨才想到要檢查煞車吧?SAST 讓你在程式碼還在「車庫」的時候就把問題抓出來——修起來又快又便宜。
二、SAST 能幫你找到什麼?常見漏洞全覽
很多開發者聽到「靜態分析」,第一反應是:「不就是 Lint 嗎?ESLint 我早就在用了。」
ESLint 確實是靜態分析的一種,但它主要關注的是程式碼風格和基本錯誤。安全導向的 SAST 工具關注的是完全不同的維度——它們在找的是可以被攻擊者利用的漏洞。
以下是 SAST 工具常見的檢測類別,對應到我們之前學過的 OWASP Top 10:
| 漏洞類別 | SAST 能偵測的範例 | 對應 OWASP Top 10 |
|---|---|---|
| 注入攻擊 | SQL 字串拼接、Command Injection | A05: Injection |
| 跨站腳本 | 未編碼的使用者輸入直接輸出到 HTML | A05: Injection |
| 硬編碼機敏資料 | 密碼、API Key 直接寫在程式碼裡 | A04: Cryptographic Failures |
| 不安全的加密 | 使用 MD5/SHA1 雜湊密碼、弱加密演算法 | A04: Cryptographic Failures |
| 路徑穿越 | 使用者輸入直接拼接檔案路徑 | A01: Broken Access Control |
| 不安全的反序列化 | 直接反序列化不受信任的資料 | A08: Software or Data Integrity |
| 不安全的正規表達式 | ReDoS(正規表達式阻斷服務)風險 | A10: Mishandling of Exceptional Conditions |
| 缺少安全標頭 | 未設定 CORS、CSP 等安全標頭 | A02: Security Misconfiguration |
讓我們看一個真實的例子。以下這段 Node.js 程式碼,你能看出幾個問題?
// ❌ 這段程式碼有多少安全問題?
const express = require('express');
const mysql = require('mysql');
const app = express();
const DB_PASSWORD = 'MyS3cretP@ss!'; // 問題 1
app.get('/user', (req, res) => {
const userId = req.query.id;
const query = <code>SELECT * FROM users WHERE id = '${userId}'</code>; // 問題 2
connection.query(query, (err, results) => {
if (err) {
res.status(500).send(<code class="kb-btn">Error: ${err.message}</code>); // 問題 3
return;
}
res.send(results); // 問題 4
});
});
人工 Code Review 可能會漏掉一兩個,但 SAST 工具會一次全部標出來:
- 硬編碼密碼(<code>DB_PASSWORD</code> 直接寫在程式碼裡)
- SQL Injection(字串拼接組合 SQL 查詢)
- 錯誤資訊洩露(把資料庫錯誤訊息直接回傳給使用者)
- 過度資料暴露(回傳整個 <code>results</code> 物件,可能包含敏感欄位)
這就是 SAST 的價值——它不會累、不會忘記、不會因為趕 deadline 就放水。
三、工具選擇:SonarQube vs. Semgrep
市面上 SAST 工具百百種,但對於台灣的中小型開發團隊來說,有兩個工具特別值得認識:SonarQube 和 Semgrep。它們各有特色,適合不同的使用情境。
3.1 SonarQube:程式碼品質與安全的「全科醫生」
SonarQube 就像一位全科醫生——不只看安全漏洞,還會幫你檢查程式碼品質、重複程式碼、測試覆蓋率等整體健康狀況。
特色:
- 支援 30+ 種程式語言
- 提供 Web 介面的儀表板,視覺化呈現程式碼健康狀況
- 內建「品質門檻(Quality Gate)」機制,可以自動阻擋不達標的程式碼
- Community Build 版本免費,適合中小團隊入門
- 支援 IDE 外掛(SonarQube for IDE,支援 VS Code、JetBrains、Cursor 等)
適合場景:
- 想要一個「一站式」的程式碼品質管理平台
- 團隊有自己的伺服器或 Docker 環境可以部署
- 需要追蹤長期的程式碼品質趨勢
快速啟動(Docker):
# 使用 Docker 快速啟動 SonarQube Community Build
docker run -d --name sonarqube \
-p 9000:9000 \
sonarqube:community
# 啟動後開啟瀏覽器 http://localhost:9000
# 預設帳號密碼:admin / admin(首次登入會要求修改)
在 Node.js 專案中使用 SonarQube Scanner:
# 安裝 SonarQube Scanner
npm install -D sonarqube-scanner
// sonar-project.js — SonarQube 掃描設定
const sonarqubeScanner = require('sonarqube-scanner').default;
sonarqubeScanner(
{
serverUrl: 'http://localhost:9000',
token: process.env.SONAR_TOKEN, // 從環境變數讀取 Token
options: {
'sonar.projectKey': 'my-nodejs-app',
'sonar.projectName': 'My Node.js App',
'sonar.sources': 'src',
'sonar.tests': 'tests',
'sonar.javascript.lcov.reportPaths': 'coverage/lcov.info',
'sonar.exclusions': 'node_modules/**,coverage/**,dist/**',
},
},
() => process.exit()
);
// package.json — 加入掃描指令
{
"scripts": {
"sonar": "node sonar-project.js",
"test:coverage": "jest --coverage",
"security:scan": "npm run test:coverage && npm run sonar"
}
}
3.2 Semgrep:輕量、快速的「安全專科醫生」
如果 SonarQube 是全科醫生,Semgrep 就是安全專科醫生——它更專注在安全漏洞的偵測,而且規則的撰寫方式非常直覺,就像在寫程式碼一樣。
特色:
- 開源免費(Community Edition),商業版提供 AI 輔助分類
- 規則語法像程式碼,開發者容易理解和自訂
- 不需要編譯,掃描速度極快
- 內建 OWASP Top 10、CWE Top 25 等規則集
- 支援 30+ 種程式語言
- 2025 年入選 Gartner Magic Quadrant for Application Security Testing
適合場景:
- 想要快速在 CI/CD 中加入安全掃描
- 團隊偏好命令列工具,不想架設額外伺服器
- 需要自訂安全規則來符合公司特殊需求
- 想要針對 OWASP Top 10 做專項掃描
快速啟動:
# 安裝 Semgrep(需要 Python 3.8+)
pip install semgrep
# 或使用 Homebrew(macOS)
brew install semgrep
# 用 OWASP Top 10 規則掃描目前的專案
semgrep --config p/owasp-top-ten .
# 用 Node.js 安全規則掃描
semgrep --config p/nodejs .
# 同時使用多個規則集
semgrep --config p/owasp-top-ten --config p/nodejs --config p/secrets .
Semgrep 規則長什麼樣?
這是 Semgrep 最酷的地方——它的規則長得就像程式碼:
# custom-rules/no-sql-string-concat.yml
rules:
- id: sql-string-concatenation
patterns:
- pattern: |
$QUERY = <code>...${$USER_INPUT}...</code>
- pattern-not: |
$QUERY = <code>...${$SAFE_VALUE}...</code>
message: |
偵測到 SQL 字串拼接,可能導致 SQL Injection。
請使用參數化查詢(Parameterized Query)代替。
languages: [javascript, typescript]
severity: ERROR
metadata:
owasp: A05:2025 Injection
cwe: CWE-89
fix: 使用 $1 placeholder 和參數陣列
# 使用自訂規則掃描
semgrep --config custom-rules/ .
3.3 SonarQube vs. Semgrep:怎麼選?
| 比較維度 | SonarQube Community Build | Semgrep Community Edition |
|---|---|---|
| 主要用途 | 程式碼品質 + 安全 | 專注安全掃描 |
| 部署方式 | 需要伺服器(Docker/實體機) | 命令列工具,免部署 |
| Web 介面 | 有(功能完整的儀表板) | 有(Semgrep Cloud,可選用) |
| 自訂規則 | 較複雜(Java 撰寫或 XML 設定) | 簡單直覺(YAML,像寫程式碼) |
| 掃描速度 | 中等(需建立索引) | 快(不需編譯) |
| CI/CD 整合 | 需要額外設定 Scanner | 原生支援,一行指令搞定 |
| 品質門檻 | 內建(Quality Gate) | 需自行設定(exit code) |
| 適合團隊 | 中大型、有 DevOps 資源 | 任何規模、快速導入 |
| 費用 | Community Build 免費 | Community Edition 免費 |
飛飛觀點:
我的建議是:先用 Semgrep 快速上手,再用 SonarQube 做長期管理。Semgrep 可以在五分鐘內跑完第一次掃描,讓團隊立刻看到效果;SonarQube 則適合建立長期的品質追蹤機制。兩者不衝突,很多團隊會同時使用——Semgrep 在 PR 階段即時回饋,SonarQube 在後台追蹤整體趨勢。
四、實戰演練:從零開始掃描你的 Node.js 專案
讓我們用一個台灣電商系統的情境,從頭到尾走一遍 SAST 的流程。
4.1 準備一個有漏洞的範例專案
假設你正在開發一個台灣電商平台的會員系統,以下是幾個有安全問題的檔案:
// src/controllers/userController.js — 有多個安全問題的範例
const db = require('../db');
const jwt = require('jsonwebtoken');
const JWT_SECRET = 'feifei-super-secret-key-2025'; // 🚨 硬編碼金鑰
// 會員登入
async function login(req, res) {
const { email, password } = req.body;
// 🚨 SQL Injection
const query = <code>SELECT * FROM users WHERE email = '${email}' AND password = '${password}'</code>;
const user = await db.query(query);
if (user.length === 0) {
return res.status(401).json({ error: '帳號或密碼錯誤' });
}
const token = jwt.sign({ userId: user[0].id, role: user[0].role }, JWT_SECRET);
res.json({ token, user: user[0] }); // 🚨 回傳整個 user 物件(含密碼)
}
// 查詢會員資料
async function getUser(req, res) {
const userId = req.params.id; // 🚨 沒有驗證 userId 是否為數字
try {
const user = await db.query(<code class="kb-btn">SELECT * FROM users WHERE id = ${userId}</code>);
res.json(user[0]);
} catch (err) {
res.status(500).json({ error: err.message, stack: err.stack }); // 🚨 洩露錯誤堆疊
}
}
// 檔案下載
async function downloadInvoice(req, res) {
const filename = req.query.file;
const filepath = <code class="kb-btn">./invoices/${filename}</code>; // 🚨 Path Traversal
res.sendFile(filepath);
}
module.exports = { login, getUser, downloadInvoice };
4.2 用 Semgrep 掃描
# 第一步:安裝 Semgrep
pip install semgrep --break-system-packages
# 第二步:用 OWASP Top 10 規則掃描
semgrep --config p/owasp-top-ten --config p/nodejs src/
# 輸出結果範例(簡化版):
# ┌──────────────────────────────────────────────────────────┐
# │ Findings │
# ├──────────────────────────────────────────────────────────┤
# │ src/controllers/userController.js │
# │ │
# │ ❌ javascript.lang.security.audit.sqli.node-sqli │
# │ line 12: SQL injection risk from string concatenation │
# │ Severity: ERROR │
# │ │
# │ ❌ javascript.lang.security.hardcoded-secret │
# │ line 4: Hardcoded JWT secret detected │
# │ Severity: WARNING │
# │ │
# │ ❌ javascript.express.security.audit.path-traversal │
# │ line 32: Path traversal vulnerability │
# │ Severity: ERROR │
# │ │
# │ ❌ javascript.lang.security.audit.error-disclosure │
# │ line 27: Stack trace exposed to client │
# │ Severity: WARNING │
# └──────────────────────────────────────────────────────────┘
# 4 findings in 1 file
4.3 用 SonarQube 掃描
# 第一步:啟動 SonarQube(如果還沒啟動)
docker run -d --name sonarqube -p 9000:9000 sonarqube:community
# 第二步:等待啟動完成後,在 Web 介面建立專案
# 開啟 http://localhost:9000
# 建立新專案 → 取得 Token
# 第三步:使用 sonar-scanner 掃描
npx sonarqube-scanner \
-Dsonar.projectKey=taiwan-ecommerce \
-Dsonar.sources=src \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.token=你的Token
掃描完成後,打開 SonarQube 的 Web 介面,你會看到一個清楚的儀表板,顯示:
- Bugs:程式碼中的錯誤
- Vulnerabilities:安全漏洞
- Security Hotspots:需要人工確認的安全敏感程式碼
- Code Smells:影響可維護性的程式碼問題
- Coverage:測試覆蓋率
4.4 修復漏洞
根據掃描結果,讓我們修復上面的程式碼:
// src/controllers/userController.js — 修復後的安全版本
const db = require('../db');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const { param, body, query, validationResult } = require('express-validator');
// ✅ 從環境變數讀取金鑰
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET || JWT_SECRET.length < 32) {
throw new Error('JWT_SECRET 未設定或長度不足');
}
// ✅ 會員登入(修復版)
async function login(req, res) {
const { email, password } = req.body;
// ✅ 使用參數化查詢
const user = await db.query(
'SELECT id, email, password_hash, role FROM users WHERE email = $1',
[email]
);
if (user.length === 0) {
return res.status(401).json({ error: '帳號或密碼錯誤' });
}
// ✅ 使用 bcrypt 比對密碼雜湊
const isValid = await bcrypt.compare(password, user[0].password_hash);
if (!isValid) {
return res.status(401).json({ error: '帳號或密碼錯誤' });
}
const token = jwt.sign(
{ userId: user[0].id, role: user[0].role },
JWT_SECRET,
{ expiresIn: '1h' } // ✅ 設定 Token 過期時間
);
// ✅ 只回傳必要欄位,不回傳密碼
res.json({ token, user: { id: user[0].id, email: user[0].email } });
}
// ✅ 查詢會員資料(修復版)
const getUserValidation = [
param('id').isInt({ min: 1 }).withMessage('無效的使用者 ID')
];
async function getUser(req, res) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ error: '參數驗證失敗' });
}
const userId = parseInt(req.params.id, 10);
try {
// ✅ 參數化查詢 + 只選取必要欄位
const user = await db.query(
'SELECT id, email, name, created_at FROM users WHERE id = $1',
[userId]
);
if (user.length === 0) {
return res.status(404).json({ error: '查無此使用者' });
}
res.json(user[0]);
} catch (err) {
// ✅ 不洩露系統內部資訊
console.error('Database error:', err); // 記錄到伺服器日誌
res.status(500).json({ error: '系統處理時發生錯誤,請稍後再試' });
}
}
// ✅ 檔案下載(修復版)
const path = require('path');
const INVOICE_DIR = path.resolve('./invoices');
async function downloadInvoice(req, res) {
const filename = req.query.file;
// ✅ 白名單驗證檔名格式(只允許特定格式)
if (!/^INV-\d{8}-\d{4}\.pdf$/.test(filename)) {
return res.status(400).json({ error: '無效的發票檔名格式' });
}
// ✅ 防止 Path Traversal
const filepath = path.join(INVOICE_DIR, filename);
const resolvedPath = path.resolve(filepath);
if (!resolvedPath.startsWith(INVOICE_DIR)) {
return res.status(403).json({ error: '存取被拒絕' });
}
res.sendFile(resolvedPath);
}
module.exports = { login, getUser, getUserValidation, downloadInvoice };
修復完成後再跑一次掃描,確認所有問題都已解決。這就是 SAST 的工作流:掃描 → 修復 → 再掃描 → 確認乾淨。
五、整合 IDE:在寫程式的當下就抓到漏洞
等到 CI/CD 才發現問題,雖然比上線後才發現好很多,但還是要回頭改。最理想的情況是——你在寫程式的當下,IDE 就直接告訴你這行有問題。
就像蓋房子時,如果工人手邊有一台即時檢測儀,每放一塊磚就能知道角度對不對、強度夠不夠,那品質一定比事後全部檢查來得好。
5.1 VS Code + Semgrep 整合
# 方法一:從 VS Code 擴充功能市集安裝
# 搜尋 "Semgrep" → 安裝官方擴充功能
# 方法二:命令列安裝
code --install-extension semgrep.semgrep
安裝後,Semgrep 會在你儲存檔案時自動掃描,直接在程式碼中標示問題:
- 🔴 紅色波浪線:嚴重安全漏洞(如 SQL Injection)
- 🟡 黃色波浪線:安全警告(如硬編碼金鑰)
- 💡 燈泡圖示:提供修復建議
你可以在 <code>.semgrepconfig.yml</code> 中設定預設規則:
# .semgrepconfig.yml — 專案根目錄
rules:
- p/owasp-top-ten
- p/nodejs
- p/secrets
5.2 VS Code + SonarQube for IDE(原 SonarLint)
# 從 VS Code 擴充功能市集安裝
# 搜尋 "SonarQube for IDE" → 安裝官方擴充功能
SonarQube for IDE 可以獨立運行,也可以連接到你的 SonarQube Server(Connected Mode),確保 IDE 中的規則和 CI/CD 中的規則一致。
Connected Mode 設定(settings.json):
{
"sonarlint.connectedMode.connections.sonarqube": [
{
"serverUrl": "http://localhost:9000",
"token": "${env:SONAR_TOKEN}"
}
],
"sonarlint.connectedMode.project": {
"connectionId": "localhost",
"projectKey": "taiwan-ecommerce"
}
}
5.3 JetBrains IDE(WebStorm/IntelliJ)整合
如果你的團隊使用 JetBrains 系列的 IDE:
- SonarQube for IDE:直接從 Plugin Marketplace 搜尋安裝
- Semgrep:同樣從 Plugin Marketplace 安裝
飛飛觀點:
IDE 整合是我認為 SAST 最被低估的功能。很多團隊導入 SAST 只做到 CI/CD 階段——問題發現了,但開發者要切換到 Pipeline 報告去看。IDE 即時回饋完全不同,它讓安全意識「長在手指上」——你打完一行有問題的程式碼,下一秒就看到紅色波浪線。久而久之,你根本不會再寫出那種程式碼了。
六、CI/CD 整合:讓安全掃描成為部署的門檻
IDE 掃描是「個人防線」,CI/CD 掃描是「團隊防線」。不管個人有沒有裝 IDE 外掛、有沒有在本地跑掃描,只要程式碼要進 main branch,就一定要通過安全檢查。
6.1 GitHub Actions + Semgrep
# .github/workflows/security-scan.yml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
semgrep:
name: SAST Scan with Semgrep
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/nodejs
p/secrets
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
6.2 GitHub Actions + SonarQube
# .github/workflows/sonarqube.yml
name: SonarQube Analysis
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
sonarqube:
name: Code Quality & Security
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # SonarQube 需要完整的 Git 歷史
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies & run tests
run: |
npm ci
npm run test:coverage
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
- name: Check Quality Gate
uses: SonarSource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
6.3 設定品質門檻(Quality Gate)
品質門檻是 SAST 最重要的設定之一——它決定了「什麼程度的問題可以放行,什麼程度的問題必須阻擋」。
# SonarQube 建議的品質門檻
# 在 SonarQube Web 介面 → Quality Gates 中設定
New Code 條件(只檢查新增的程式碼):
- 安全漏洞(Vulnerabilities): 0(不允許任何新漏洞)
- Security Hotspots Reviewed: ≥ 100%(所有安全熱點都要審查)
- 程式碼覆蓋率(Coverage): ≥ 80%
- 重複程式碼比例(Duplications): ≤ 3%
# Semgrep 的品質門檻設定(透過 exit code)
# 在 CI/CD 中,Semgrep 預設在發現 ERROR 級別問題時會回傳非零 exit code
# 這會自動讓 CI/CD Pipeline 失敗
# 如果只想在特定嚴重度時失敗:
semgrep --config p/owasp-top-ten --severity ERROR .
# 只有 ERROR 級別的發現會導致掃描失敗
# 搭配 --error 旗標精確控制:
semgrep --config p/owasp-top-ten --error .
# 有任何發現都會失敗
飛飛觀點:
品質門檻的設定是一門藝術。設得太嚴,開發者會覺得綁手綁腳,甚至想辦法繞過;設得太鬆,等於沒設。我的建議是:對新程式碼嚴格,對舊程式碼寬容。SonarQube 的「New Code」策略就是這個精神——不要求你一次修完所有歷史債務,但新寫的程式碼一定要乾淨。
七、處理誤報:SAST 最讓人頭痛的問題
SAST 最大的缺點就是誤報(False Positive)——工具說有漏洞,但其實沒有。這就像驗屋師每次都說「這面牆不夠結實」,但你明明知道那是承重牆,設計得很好。
如果誤報太多,開發者會逐漸不信任工具,最後乾脆無視所有警告——這比不用工具還危險。
處理誤報的策略
策略一:標記為「已審查,非漏洞」
在 SonarQube 中,你可以把 Security Hotspot 標記為「Safe」;在 Semgrep 中,可以用 <code>nosemgrep</code> 注解:
// 這個 eval 是安全的,因為輸入來自信任的設定檔
// nosemgrep: javascript.lang.security.audit.eval-detected
const config = eval(trustedConfigString);
策略二:調整規則的嚴重度或排除特定規則
# .semgrepconfig.yml — 排除特定規則或路徑
exclude:
- "tests/**" # 測試程式碼不掃描
- "scripts/seed.js" # 資料填充腳本排除
策略三:逐步啟用規則
不要一次開啟所有規則。先從最重要的開始(SQL Injection、XSS、硬編碼金鑰),等團隊適應後再逐步增加。
| 導入階段 | 建議啟用的規則 | 預期誤報率 |
|---|---|---|
| 第一週 | 硬編碼金鑰 / 密碼 | 低 |
| 第二週 | SQL Injection、Command Injection | 低~中 |
| 第一個月 | XSS、Path Traversal | 中 |
| 第二個月 | 完整 OWASP Top 10 | 中 |
| 穩定期 | 加入自訂規則 | 低(已調整過) |
八、SAST 導入 Checklist
以下是一份可以直接帶回團隊使用的 Checklist:
## SAST 導入 Checklist
### 工具選擇
- [ ] 已評估團隊需求(專注安全 vs. 全面品質管理)
- [ ] 已選定至少一款 SAST 工具(Semgrep / SonarQube / 其他)
- [ ] 已確認工具支援團隊使用的程式語言
### IDE 整合
- [ ] 已在開發者 IDE 中安裝對應的擴充功能
- [ ] 已設定專案級別的掃描規則設定檔
- [ ] 已測試 IDE 即時掃描功能正常運作
### CI/CD 整合
- [ ] 已在 CI/CD Pipeline 中加入 SAST 掃描步驟
- [ ] 掃描在 PR 階段執行(不只是 merge 後)
- [ ] 掃描結果會直接顯示在 PR 的 Comment 中
### 品質門檻
- [ ] 已設定品質門檻(新程式碼不允許新漏洞)
- [ ] 嚴重漏洞會自動阻擋部署
- [ ] 團隊已同意門檻的標準
### 誤報管理
- [ ] 已建立誤報的審查與標記流程
- [ ] 已排除不需要掃描的目錄(如 node_modules、tests)
- [ ] 定期(每月)回顧並調整規則
### 團隊協作
- [ ] 團隊成員已了解 SAST 的用途與限制
- [ ] 已指定 Security Champion 負責維護掃描規則
- [ ] 掃描結果的修復已納入 Sprint 的工作項目
九、常見問題 FAQ
Q1:SAST 掃描很慢,會不會拖慢 CI/CD Pipeline?
不會太慢,但需要最佳化。 Semgrep 以速度著稱,掃描一個中型 Node.js 專案通常只需要 30 秒到 2 分鐘。SonarQube 因為需要建立索引,首次掃描可能需要 5-10 分鐘,但後續增量掃描會快很多。
最佳化建議:排除不需要掃描的目錄(<code>node_modules</code>、<code>dist</code>、<code>coverage</code>),在 PR 階段只掃描變更的檔案,完整掃描留在 nightly build。
Q2:我已經在做 Code Review 了,還需要 SAST 嗎?
絕對需要。 Code Review 和 SAST 是互補的,不是替代的。Code Review 擅長發現商業邏輯問題和架構設計缺陷,SAST 擅長發現模式化的安全漏洞。人的眼睛會累、會受趕 deadline 的壓力影響,但工具不會。
反過來說,SAST 也不能取代 Code Review——它不懂你的業務邏輯,不知道「這個使用者不應該能看到那個資料」這種語境相關的問題。
Q3:SonarQube Community Build 和 Server 版本差在哪裡?
Community Build 是免費開源版本,適合中小團隊入門使用。它提供基礎的 SAST 和程式碼品質檢測功能。Server 的付費版本(Developer、Enterprise)多了進階 SAST(如 taint analysis 污點追蹤)、SCA 依賴掃描、更多語言支援、分支分析等企業功能。
對於剛開始導入 SAST 的團隊,Community Build 已經綽綽有餘了。
Q4:Vibe Coding 用 AI 生成的程式碼,SAST 掃得出來嗎?
掃得出來,而且特別需要掃。 AI 生成的程式碼經常包含安全問題——字串拼接 SQL、缺少輸入驗證、硬編碼測試用的金鑰等。SAST 不管程式碼是人寫的還是 AI 寫的,一律用相同的規則掃描。
建議:把 SAST 當成 AI 生成程式碼的「安全審查員」。AI 幫你寫完程式碼後,先用 Semgrep 掃一遍再 commit——這能幫你抓到大部分 AI 常犯的安全錯誤。
十、結語:讓工具成為你的安全夥伴
回到蓋房子的比喻。
沒有人會說:「我蓋了二十年的房子,用眼睛看就夠了,不需要什麼驗屋師。」因為不管你多有經驗,總有看漏的地方。而且,當你同時管理好幾棟房子的施工進度時,你更需要自動化的檢查工具幫你盯住每一個環節。
SAST 也是一樣。它不是要取代你的專業判斷,而是幫你處理那些重複性高、模式化、容易被疏忽的安全檢查。讓工具做工具擅長的事,你才能把寶貴的注意力放在更重要的地方——像是商業邏輯的安全設計、架構層級的防禦策略、以及培養團隊的安全文化。
安全工具不是給開發者加壓力的枷鎖,而是讓你寫程式時更安心的夥伴。
當 IDE 裡的紅色波浪線從「煩人的干擾」變成「可靠的提醒」,你就知道——SAST 已經融入你的開發日常了。
下一篇,我們將進入安全驗證的第二個重要主題——DAST 動態測試實戰:用 OWASP ZAP 掃描網站。學完 SAST 看程式碼內部,接下來要學怎麼從外部攻擊自己的系統,找出只有在運行時才會出現的漏洞。