如何將靜態程式碼分析整合到 CI/CD 管道中?

如何將靜態程式碼分析整合到 CI/CD 管道中?

內部網路 2026 年 6 月 11 日

每一行最終部署到生產環境的程式碼,都是由某個人提交的,而提交者當時都認為它是正確的。靜態程式碼分析是一個自動化系統,它透過分析程式碼結構、追蹤資料流,並將其與已知的漏洞、缺陷和品質違規模式進行比較,來驗證這種判斷是否正確。問題不在於是否執行靜態程式碼分析,而是在開發流程的哪個環節運行它,使用哪個工具,設定哪些閾值,以及如何確保回饋足夠迅速,以便開發人員能夠真正採取行動,而不是置之不理。

靜態分析作為例行公事和作為真正的品質門控之間的區別在於配置。一個運作並產生報告卻無人問津的掃描器只是徒有其表。而一個真正的品質門控系統,當引入新的嚴重漏洞時會終止構建,當代碼覆蓋率低於閾值時會阻止合併,並在拉取請求審查界面中提供精確的文件和行反饋,這才是真正能在決策過程中實時調整行為的系統。

靜態分析應在流程的哪個階段運行

靜態分析應該在多個節點運行,每個節點服務於不同的目的。只在一個節點運行靜態分析會造成盲點;在所有節點都運行則會導致流程緩慢,開發人員需要繞過這些流程。

管道階段運行什麼延遲預算目的
預提交(本地)僅快速 linter(ESLint、Clippy、rustfmt)在5秒下在顯而易見的問題影響程式碼庫之前就先加以解決。
拉取請求/合併請求完整程式碼檢查 + SAST + 增量式 SonarQube 掃描3分鐘以內布羅德合併新的關鍵問題
主分支構建全面分析,包括覆蓋率、重複數據和技術債。10分鐘以內追蹤品質趨勢,更新儀錶板。
每晚安排深度掃描、依賴審計、合規性檢查沒有預算發現建設進度緩慢問題和供應鏈風險

最有價值的階段是拉取請求推送至主分支後執行的分析會在合併決定做出後執行。而拉取請求後運行的分析,會發布包含精確文件和行上下文的內聯註釋,此時開發者仍在程式碼中,修復成本最低。

每次保存都運行繁重的預提交分析會嚴重影響開發者體驗。 5 秒內完成的快速程式碼檢查工具才適合在預先提交中使用。其他所有分析都應該在持續整合(CI)中執行。

GitHub Actions:完整的工作配置

GitHub Actions 是使用最廣泛的持續整合平台。以下工作流程執行一個分層品質管線:格式檢查、程式碼檢查、建置、SAST 安全掃描和 SonarCloud 品質閘控。

雅姆

# .github/workflows/quality.yml
name: Code Quality

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  # ── Layer 1: Fast checks (under 60 seconds) ─────────────────────────────
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - run: npm ci

      - name: Lint (fail on warnings)
        run: npx eslint src/ --max-warnings 0

      - name: Format check
        run: npx prettier --check src/

      - name: TypeScript type check
        run: npx tsc --noEmit

  # ── Layer 2: Security SAST (Semgrep) ────────────────────────────────────
  sast:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Semgrep SAST scan
        uses: semgrep/semgrep-action@v1
        with:
          config: p/javascript p/nodejs p/owasp-top-ten
        env:
          SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}

  # ── Layer 3: SonarCloud quality gate ────────────────────────────────────
  sonar:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # full history required for blame data

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - run: npm ci

      - name: Run tests with coverage
        run: npm test -- --coverage --coverageReporters=lcov

      - name: SonarCloud scan
        uses: SonarSource/sonarcloud-github-action@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

性能

# sonar-project.properties
sonar.projectKey=my-org_my-project
sonar.organization=my-org
sonar.sources=src
sonar.tests=tests
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.coverage.exclusions=**/*.test.js,**/*.spec.js

此工作流程中的關鍵配置決策:

  • --max-warnings 0 在 ESLint 中:任何警告都會導致建置失敗。允許顯示警告的團隊會不斷累積警告,直到無人理會為止。
  • fetch-depth: 0 結帳時:SonarCloud 需要完整的 git 歷史記錄才能將責任歸咎於引入的問題並正確計算新程式碼指標。
  • Semgrep 在 lint 之後與 Sonar 並行運行:安全性和品質掃描是獨立的關注點;並行運行它們可以減少整個流程時間。
  • 測試運行 lcov 覆蓋率輸出:SonarCloud 讀取此輸出以顯示每個檔案的覆蓋率,並在品質閘中強制執行覆蓋率閾值。

GitLab CI/CD:帶有質量門的質量流水線

雅姆

# .gitlab-ci.yml
stages:
  - lint
  - test
  - analyze
  - security

variables:
  SONAR_HOST_URL: "https://sonarcloud.io"

lint:
  stage: lint
  image: node:20
  cache:
    paths: [node_modules/]
  script:
    - npm ci
    - npx eslint src/ --max-warnings 0
    - npx prettier --check src/
    - npx tsc --noEmit
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == "main"

test:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm test -- --coverage --coverageReporters=lcov cobertura
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

sonarcloud:
  stage: analyze
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"
  cache:
    key: "${CI_JOB_NAME}"
    paths: [.sonar/cache]
  script:
    - sonar-scanner
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == "main"

semgrep:
  stage: security
  image: returntocorp/semgrep:latest
  script:
    - semgrep scan --config=p/owasp-top-ten --config=p/javascript
      --sarif --output=semgrep.sarif src/
  artifacts:
    reports:
      sast: semgrep.sarif
  rules:
    - if: $CI_MERGE_REQUEST_IID
    - if: $CI_COMMIT_BRANCH == "main"

artifacts: reports: sast: 此模組將 Semgrep 的 SARIF 輸出直接整合到 GitLab 的安全儀表板中,其中發現的結果會顯示在合併請求安全性報告中,而不是作為原始 CI 日誌輸出。

Jenkins:使用 SonarQube 建立聲明式管線

Groovy的

// Jenkinsfile
pipeline {
    agent any

    tools {
        nodejs "NodeJS-20"
    }

    environment {
        SONAR_TOKEN = credentials('sonar-token')
    }

    stages {
        stage('Lint') {
            steps {
                sh 'npm ci'
                sh 'npx eslint src/ --max-warnings 0'
                sh 'npx tsc --noEmit'
            }
        }

        stage('Test') {
            steps {
                sh 'npm test -- --coverage --coverageReporters=lcov'
            }
            post {
                always {
                    publishHTML([
                        allowMissing: false,
                        reportDir: 'coverage/lcov-report',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }

        stage('SonarQube Analysis') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh '''
                        npx sonar-scanner \
                          -Dsonar.projectKey=my-project \
                          -Dsonar.sources=src \
                          -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
                    '''
                }
            }
        }

        stage('Quality Gate') {
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
    }

    post {
        failure {
            emailext(
                subject: "Quality Gate FAILED: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "Build failed quality gate. Review: ${env.BUILD_URL}",
                to: "${env.CHANGE_AUTHOR_EMAIL}"
            )
        }
    }
}

waitForQualityGate abortPipeline: true 這是關鍵的一行程式碼。它會輪詢 SonarQube 直到分析完成,如果未滿足品質閘要求,則建置失敗。如果沒有這行程式碼,管道會在分析仍在運行時完成,品質門要求的結果將永遠不會生效。

配置團隊真正遵守的品質關卡

如果一個品質閘因為閾值設定過高而導致每次提交都失敗,那麼它就會被停用。如果一個品質門因為閾值設定過低而從不失敗,那麼它就毫無價值。我們的目標是設定一個與專案實際風險狀況相符的品質門。

建議的 SonarQube 新品質門初始配置:

公制新舊程度門檻
新漏洞比...更棒0
新漏洞比...更棒0
新的安全熱點區域已審核小於
新程式碼覆蓋率小於
新增重複行比...更棒3%
新的程式碼異味比...更棒10

將這些閾值應用於 限新代碼 (SonarQube 中的「新程式碼」階段)。不要將它們應用於遺留專案的全部程式碼庫,否則由於五年累積的技術債務導致每次建置都失敗,團隊最終會徹底停用該門控。新代碼方法可以在單獨解決現有債務的同時,止損。

棘輪閾值隨時間變化。 先設定保守的閾值,然後隨著團隊每季清理積壓工作,逐步收緊閾值。例如,如果某個關卡的完成度達到 75%,那麼在基準水準顯著高於 80% 時,就可以將完成度提高到 80%。循序漸進地收緊閾值比一下子追求完美更永續。

處理大型程式碼庫中的靜態分析效能

團隊在持續整合 (CI) 中停用或繞過靜態分析的最常見原因是它會降低管線的速度。每次向特性分支推送程式碼都進行 15 分鐘的品質掃描會嚴重影響開發人員的工作流程。以下幾種策略可以加快分析速度:

增量分析(僅限新程式碼)。 SonarQube 和大多數企業級靜態分析工具都支援僅分析拉取請求中已更改的文件,而不是整個程式碼庫。配置 sonar.pullrequest.base, sonar.pullrequest.branch以及 sonar.pullrequest.key 用於啟用 PR 特定增量掃描的參數。合併到主分支時仍會執行完整分析;PR 掃描在大多數程式碼庫上耗時不到 2 分鐘。

緩存。 許多靜態分析運行中最耗時的部分是下載工具及其規則定義。請在運行之間快取掃描器二進位檔案、規則資料庫以及任何已解析的依賴項:

雅姆

# GitHub Actions: cache SonarCloud scanner
- name: Cache SonarCloud packages
  uses: actions/cache@v4
  with:
    path: ~/.sonar/cache
    key: ${{ runner.os }}-sonar
    restore-keys: ${{ runner.os }}-sonar

平行結構。 代碼檢查、安全掃描和品質門分析是相互獨立的任務。應並行運行這些任務,而不是順序運行。管線總耗時取決於最慢的作業,而不是所有作業耗時的總和。

選擇性激活工具。 並非所有工具都需要在每個分支上運作。完整的安全掃描和合規性檢查可以在拉取請求和主分支上運行,而合併前的分支只需執行快速程式碼檢查工具。在持續整合 (CI) 配置中使用分支過濾器可以套用不同的分析設定檔。

安全優先管道中的靜態分析:SAST 和依賴掃描

以安全為中心的管道在標準品質分析的基礎上增加了兩層:針對應用程式程式碼漏洞的 SAST(靜態應用程式安全測試)和針對第三方程式庫中已知 CVE 的依賴項掃描。

SAST工具 Semgrep、CodeQL、Snyk Code 和 Checkmarx 等工具分析不受信任的資料如何流經應用程式程式碼並最終到達敏感操作。它們能夠發現標準程式碼品質檢查工具無法偵測到的 SQL 注入、XSS 攻擊、命令注入、不安全的反序列化以及硬編碼憑證等問題。

雅姆

# GitHub Actions: CodeQL analysis for deep vulnerability detection
- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    languages: javascript-typescript

- name: Perform CodeQL Analysis
  uses: github/codeql-action/analyze@v3
  with:
    category: "/language:javascript-typescript"

依賴關係掃描 檢查 package.json, pom.xml, requirements.txt以及針對漏洞資料庫的等效清單檔案:

雅姆

# Dependency audit in the pipeline
- name: Dependency audit
  run: |
    npm audit --audit-level=high
    # Fail if high or critical vulnerabilities are found

此分層安全模型在每個 PR 上都運行快速的基於模式的靜態應用安全測試 (Semgrep),以便開發者能夠立即提供回饋;同時,它還定期運行深度語義靜態應用安全測試 (CodeQL),以確保全面的安全覆蓋。由於每天都會發布新的 CVE,因此每次建置都會執行依賴項掃描。

常見的整合錯誤以及如何避免它們

僅對主分支進行分析。 靜態分析的價值在於合併之前就發現問題,而不是合併之後。僅在合併後運行的分析只能作為報告,而不能起到把關作用。

設定品質門禁,發出警告而不是判定失敗。 不會阻塞管線的警告通知,開發者往往會選擇忽略。不會失敗的關卡無法建構品質文化。

不將測試文件排除在覆蓋率閾值之外。 測試程式碼測試其他測試程式碼會導致覆蓋率數值虛高。為了獲得準確的生產代碼覆蓋率測量結果,請將測試目錄從覆蓋率計算中排除。

忽略遺留程式碼庫的分析積壓問題。 首次在百萬行遺留程式碼庫上啟用 SonarQube 就發現 40,000 萬個問題,令人沮喪且適得其反。應採用新程式碼基準方法:定義一個「新程式碼」起始日期,僅對在此日期之後編寫的程式碼設定審核機制,並將現有問題積壓視為一個獨立的技術債務削減計劃。

未將結果寫入拉取請求評論。 單獨發佈到開發者必須存取的儀表板上的分析結果將被忽略。在拉取請求審查介面中,以內聯評論形式發佈在引入問題的特定程式碼行上的分析結果將被處理。 sonar.pullrequest.* 設定 GitHub、GitLab 或 Bitbucket 整合的參數,以便將發現結果顯示在程式碼審查發生的地方。

SMART TS XL 與 CI/CD 流水線集成

CI/CD 管線中的大多數靜態分析工具一次只能辨識一種語言。例如,ESLint 可以識別 JavaScript,SonarQube 可以識別 Java、Python、JavaScript 和 C#。但它們都無法辨識 COBOL、JCL、RPG,也無法辨識連接大型主機批次程式和 Java 微服務(後者會接收批次程式的輸出)的跨語言依賴關係,而 Java 微服務又會連接到顯示輸出的 JavaScript 前端。

SMART TS XL 它作為跨語言靜態分析層整合到 CI/CD 管線中,可同時涵蓋企業環境中的所有語言。當 Java 服務被修改時, SMART TS XL“ 影響分析 在部署變更之前,它會追蹤依賴關係圖,以確定哪些 COBOL 程式、資料庫模式和下游服務會受到該變更的影響。當 COBOL 副本被修改時,它會識別整個應用程式組合中包含該副本且需要驗證的每個程式。

這種跨語言依賴感知能力是其關鍵所在。 SMART TS XL的 CI/CD 整合與向 SonarQube 添加另一種語言有所不同。它不僅僅是分析已更改文件中的程式碼,更重要的是理解該更改將影響系統中的哪些其他部分,這屬於架構分析的範疇,它能夠在更改實施之前確定其真正的影響範圍。

對於在傳統和現代技術棧之間切換的企業開發團隊而言, SMART TS XL“ DevOps 集成 此功能將結構分析嵌入到管線審查工作流程中,提供了特定語言工具無法實現的架構可見性。結果是,品質門不僅在語言邊界內,而且在整個系統中強制執行標準,確保任何元件的變更都不會導致依賴它的元件出現意外故障。

靜態分析作為永久管道公民

那些真正做好靜態分析的團隊,對待靜態分析的方式與對待測試的方式相同:不是將其視為一個需要通過的階段,而是將其視為融入每次程式碼提交的持續實踐。每個拉取請求都會執行分析。品質門控結果與程式碼審查結果同步呈現。隨著程式碼庫的改進,閾值也會隨之提高。隨著團隊安全性和品質成熟度的提升,也會增加新的分析類別。

本文中的管線配置範本僅供參考。具體的工具、閾值和門控條件應根據專案的語言堆疊、風險狀況和團隊成熟度進行調整。不變的是基本原則:不進行門控合併的分析無法改變行為,而程式碼品質計畫的目標正是改變行為。