這是一個展示完整安全 CI/CD Pipeline 的專案,包含多層次的安全掃描和程式碼品質檢測工具。
本專案整合了以下安全掃描工具:
-
SAST (靜態應用程式安全測試) - Semgrep
- 原始碼安全漏洞掃描
- OWASP Top 10 檢測
- Secrets 洩漏檢測
-
SCA (軟體組成分析) - OWASP Dependency-Check
- 依賴套件漏洞檢測
- CVE 漏洞資料庫比對
-
容器映像掃描 - Trivy
- Docker 映像安全掃描
- 作業系統漏洞檢測
-
IaC 安全檢查 - Checkov
- 基礎設施即程式碼安全檢查
- Terraform、Kubernetes 設定檢查
-
Secret 掃描 - Gitleaks
- Git 歷史記錄 secret 掃描
- API 金鑰、密碼洩漏檢測
-
程式碼品質掃描 - SonarCloud
- 程式碼品質分析
- 技術債務追蹤
- 安全漏洞檢測
- 程式碼覆蓋率分析
- Docker 和 Docker Compose
- GitHub 帳號
- SonarCloud 帳號(用於程式碼品質掃描)
- 克隆專案
git clone https://github.com/your-username/202510_lab1.git
cd 202510_lab1- 使用 Docker Compose 啟動
docker-compose up -d- 開啟瀏覽器訪問
http://localhost:8080
# 建置映像
docker build -t lab1:local .
# 執行容器
docker run -d -p 8080:80 lab1:local
# 檢視日誌
docker logs <container-id>- 前往 SonarCloud
- 使用 GitHub 帳號登入
- 點擊右上角的 "+" → "Analyze new project"
- 選擇你的 GitHub 組織
- 選擇此專案 (202510_lab1)
- 點擊 "Set Up"
ERROR You are running CI analysis while Automatic Analysis is enabled.
Please consider disabling one or the other.
操作步驟:
- 進入你的 SonarCloud 專案頁面
- 點擊左側選單 "Administration" → "Analysis Method"
- 找到 "SonarCloud Automatic Analysis" 區塊
- 關閉(Toggle off)"Automatic Analysis" 開關
- 確認顯示為 "Disabled"
- 設定會自動儲存
💡 提示:自動分析和 CI 分析不能同時啟用。由於我們使用 GitHub Actions 進行 CI 掃描,因此必須關閉自動分析。
- 在 SonarCloud 中,點擊右上角的頭像
- 選擇 "My Account" → "Security"
- 在 "Generate Tokens" 區域輸入 Token 名稱(例如:
lab1-github-actions) - 點擊 "Generate"
- 立即複製並保存此 Token(之後將無法再次查看)
- 前往你的 GitHub 專案頁面
- 點擊 "Settings" → "Secrets and variables" → "Actions"
- 點擊 "New repository secret"
- 新增以下 secret:
- Name:
SONAR_TOKEN - Secret: 貼上剛才複製的 SonarCloud Token
- Name:
- 點擊 "Add secret"
SonarCloud 專案金鑰格式為:組織名稱_專案名稱
例如:
- 組織:
my-organization - 專案:
202510_lab1 - 專案金鑰:
my-organization_202510_lab1
專案金鑰會在 CI/CD Pipeline 中自動設定為:
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}推送程式碼到 GitHub 後:
- 前往 "Actions" 標籤查看 Pipeline 執行狀態
- 確認 "SonarCloud 程式碼品質掃描" job 成功執行
- 前往 SonarCloud 查看分析報告:
https://sonarcloud.io/dashboard?id=你的組織名稱_202510_lab1
預設的品質門檻包括:
- ✅ 新程式碼覆蓋率 ≥ 80%
- ✅ 新程式碼重複度 ≤ 3%
- ✅ 可維護性評級 ≥ A
- ✅ 可靠性評級 ≥ A
- ✅ 安全性評級 ≥ A
你可以在 SonarCloud 專案設定中自訂這些門檻。
GitHub Actions Workflow 會在以下情況觸發:
- 推送到
main或develop分支 - 對
main分支發起 Pull Request - 手動觸發(workflow_dispatch)
- SAST 掃描 - Semgrep 原始碼安全檢測
- SCA 掃描 - 依賴套件漏洞檢測
- 容器掃描 - Docker 映像安全檢測
- IaC 掃描 - 基礎設施程式碼安全檢查
- Secret 掃描 - 敏感資訊洩漏檢測
- 程式碼品質掃描 - SonarCloud 分析
- 安全總結 - 彙整所有掃描結果
- 建置映像 - 建置並推送 Docker 映像到 GHCR
- 建置網站 - 準備靜態網站內容
- 部署 - 部署到 GitHub Pages
前往專案的 "Security" 標籤 → "Code scanning alerts" 查看:
- SAST 掃描結果
- SCA 漏洞報告
- 容器安全問題
- IaC 設定問題
前往 SonarCloud 查看詳細的程式碼品質報告:
https://sonarcloud.io/dashboard?id=你的組織名稱_202510_lab1
包含:
- 程式碼異味(Code Smells)
- 技術債務
- 安全漏洞
- 測試覆蓋率
- 重複程式碼分析
前往 "Actions" 標籤查看每個 job 的詳細執行日誌。
.github/workflows/secure-pipeline.yml- CI/CD Pipeline 設定sonar-project.properties- SonarCloud 掃描設定Dockerfile- Docker 映像建置設定docker-compose.yml- 本地開發環境設定nginx.conf- Nginx 網頁伺服器設定
202510_lab1/
├── .github/
│ └── workflows/
│ └── secure-pipeline.yml # CI/CD Pipeline 設定
├── app/
│ ├── index.html # 主要網頁
│ ├── script.js # JavaScript 邏輯
│ └── style.css # 樣式表
├── Dockerfile # Docker 映像定義
├── docker-compose.yml # Docker Compose 設定
├── nginx.conf # Nginx 設定
├── sonar-project.properties # SonarCloud 設定
└── README.md # 專案說明文件
本專案展示了以下安全最佳實踐:
- 多層次安全掃描:從原始碼到容器映像的全方位檢測
- 持續整合:每次 commit 都會觸發安全檢查
- 自動化部署:只有通過所有檢查的程式碼才會部署
- 程式碼品質管理:使用 SonarCloud 持續追蹤程式碼品質
- 容器化部署:使用 Docker 確保環境一致性
- Secret 管理:使用 GitHub Secrets 安全儲存敏感資訊
歡迎提交 Issue 或 Pull Request!
Pull Request 流程:
- Fork 本專案
- 建立你的功能分支 (
git checkout -b feature/AmazingFeature) - 提交你的變更 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 開啟 Pull Request
- 等待 CI/CD Pipeline 執行完成
- 確保所有安全掃描和品質檢查通過
本專案採用 MIT 授權條款。
如有任何問題,歡迎開 Issue 討論。
本專案已修復 SonarCloud 檢測到的安全漏洞:
修復前:
const riskyRegex = new RegExp('(a+)+$'); // 危險:災難性回溯修復後:
const safeRegex = /^a+$/; // 安全:線性時間複雜度說明: 原本的正則表達式 (a+)+$ 會造成災難性回溯,攻擊者可以構造特殊輸入導致程式掛起。修復後使用線性時間複雜度的正則表達式。
修復前:
const API_KEY = "xxxxx"; // 危險:硬編碼
const DATABASE_URL = "mongodb://admin:passxxxxx@localhost:27017/game";修復後:
function getApiConfig() {
return {
apiKey: process.env.API_KEY || '',
databaseUrl: process.env.DATABASE_URL || 'mongodb://localhost:27017/game'
};
}說明: 移除了硬編碮的敏感資訊,改用環境變數管理。已建立 .env.example 檔案作為範本。
-
環境變數管理
- 複製
.env.example為.env - 在
.env中設定實際的敏感資訊 .env檔案已加入.gitignore,不會被提交
- 複製
-
輸入驗證
- 使用安全的正則表達式
- 進行型別檢查和邊界檢查
- 避免災難性回溯攻擊
-
偽隨機數生成器安全
- 區分遊戲邏輯和安全敏感操作的隨機需求
- 提供密碼學安全的隨機數選項
- 詳細註解說明使用場景
-
程式碼品質改善
- 降低函數的認知複雜度
- 重構複雜邏輯為更小的專責函數
- 提高程式碼可讀性和可維護性
-
敏感資訊保護
- 不在程式碼中硬編碼密碼、金鑰
- 使用環境變數或安全的配置管理
- 在日誌中避免輸出敏感資訊
修復前:
// 認知複雜度 17,超過允許的 15
function minimax(board, depth, isMaximizing) {
const result = checkWinner();
if (result !== null) {
if (result === 'O') return 10 - depth;
if (result === 'X') return depth - 10;
return 0;
}
if (isMaximizing) {
let bestScore = -Infinity;
for (let i = 0; i < 9; i++) {
if (board[i] === '') {
// 複雜的內嵌邏輯...
}
}
} else {
// 更多複雜邏輯...
}
}修復後:
// 將複雜邏輯拆分成專責函數,降低認知複雜度
function calculateGameEndScore(result, depth) {
if (result === 'O') return 10 - depth;
if (result === 'X') return depth - 10;
return 0;
}
function calculateMaximizingScore(board, depth) {
// 處理最大化玩家邏輯
}
function calculateMinimizingScore(board, depth) {
// 處理最小化玩家邏輯
}
function minimax(board, depth, isMaximizing) {
const result = checkWinner();
if (result !== null) {
return calculateGameEndScore(result, depth);
}
return isMaximizing ?
calculateMaximizingScore(board, depth) :
calculateMinimizingScore(board, depth);
}說明: 將原本認知複雜度為 17 的函數重構為多個專責函數,每個函數只負責一個特定任務。主函數使用簡潔的三元運算符,大幅提升程式碼可讀性和可維護性。
修復前:
return availableMoves[Math.floor(Math.random() * availableMoves.length)]; // ❌ 使用偽隨機數
if (Math.random() < 0.5) { // ❌ 不安全的隨機選擇修復後:
// 完全移除 Math.random(),使用密碼學安全的隨機數生成器
function getSecureRandomInt(max) {
if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
// 瀏覽器環境:Web Crypto API
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
return Math.floor((array[0] / (0xFFFFFFFF + 1)) * max);
} else if (typeof require !== 'undefined') {
// Node.js 環境:crypto 模組
const crypto = require('crypto');
const randomBytes = crypto.randomBytes(4);
const randomValue = randomBytes.readUInt32BE(0);
return Math.floor((randomValue / (0xFFFFFFFF + 1)) * max);
} else {
return 0; // 安全的預設值
}
}
function getSecureRandomFloat() {
// 生成 0-1 之間的密碼學安全隨機浮點數
// 替代所有 Math.random() 使用
}說明: 完全移除了所有 Math.random() 的使用,改用密碼學安全的隨機數生成器。支援瀏覽器和 Node.js 環境,當無可用的安全隨機數生成器時提供安全的預設值。