[安全測試] 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 工具會一次全部標出來:

  1. 硬編碼密碼(<code>DB_PASSWORD</code> 直接寫在程式碼裡)
  2. SQL Injection(字串拼接組合 SQL 查詢)
  3. 錯誤資訊洩露(把資料庫錯誤訊息直接回傳給使用者)
  4. 過度資料暴露(回傳整個 <code>results</code> 物件,可能包含敏感欄位)

這就是 SAST 的價值——它不會累、不會忘記、不會因為趕 deadline 就放水


三、工具選擇:SonarQube vs. Semgrep

市面上 SAST 工具百百種,但對於台灣的中小型開發團隊來說,有兩個工具特別值得認識:SonarQubeSemgrep。它們各有特色,適合不同的使用情境。

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 看程式碼內部,接下來要學怎麼從外部攻擊自己的系統,找出只有在運行時才會出現的漏洞。


延伸閱讀