Browse Source

Add security scanning to CI pipeline
- Add pip-audit check to PR workflow (non-blocking warning)
- Add npm audit check to PR workflow (non-blocking warning)
- Create scheduled weekly security audit workflow that:
- Runs strict pip-audit and npm audit
- Creates/updates GitHub issues when vulnerabilities found
- Uploads audit results as artifacts
- Supports manual trigger via workflow_dispatch

maziggy 4 tháng trước cách đây
mục cha
commit
164d22f2bb

+ 45 - 0
.github/workflows/ci.yml

@@ -44,6 +44,28 @@ jobs:
       - name: Run ruff format check
       - name: Run ruff format check
         run: ruff format --check backend/
         run: ruff format --check backend/
 
 
+  backend-security:
+    name: Backend Security
+    runs-on: ubuntu-latest
+    if: github.event_name == 'push' || github.actor != github.repository_owner
+    continue-on-error: true
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements.txt
+          pip install pip-audit
+
+      - name: Run pip-audit
+        run: pip-audit --desc on
+
   backend-tests:
   backend-tests:
     name: Backend Tests
     name: Backend Tests
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
@@ -103,6 +125,29 @@ jobs:
         working-directory: frontend
         working-directory: frontend
         run: npm run lint
         run: npm run lint
 
 
+  frontend-security:
+    name: Frontend Security
+    runs-on: ubuntu-latest
+    if: github.event_name == 'push' || github.actor != github.repository_owner
+    continue-on-error: true
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'npm'
+          cache-dependency-path: frontend/package-lock.json
+
+      - name: Install dependencies
+        working-directory: frontend
+        run: npm ci
+
+      - name: Run npm audit
+        working-directory: frontend
+        run: npm audit --audit-level=moderate
+
   frontend-typecheck:
   frontend-typecheck:
     name: Frontend Type Check
     name: Frontend Type Check
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest

+ 213 - 0
.github/workflows/security.yml

@@ -0,0 +1,213 @@
+name: Security Audit
+
+on:
+  schedule:
+    # Run weekly on Monday at 6:00 UTC
+    - cron: '0 6 * * 1'
+  workflow_dispatch:
+    # Allow manual trigger
+
+env:
+  PYTHON_VERSION: '3.11'
+  NODE_VERSION: '20'
+
+jobs:
+  backend-audit:
+    name: Backend Security Audit
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements.txt
+          pip install pip-audit
+
+      - name: Run pip-audit
+        id: pip-audit
+        run: |
+          pip-audit --desc on --format json --output pip-audit-results.json || echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
+          pip-audit --desc on || true
+
+      - name: Upload audit results
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: pip-audit-results
+          path: pip-audit-results.json
+          retention-days: 30
+
+      - name: Create issue on vulnerability
+        if: steps.pip-audit.outputs.vulnerabilities_found == 'true'
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const results = JSON.parse(fs.readFileSync('pip-audit-results.json', 'utf8'));
+
+            // Build vulnerability table
+            let table = '| Package | Version | Vulnerability | Fix Version |\n';
+            table += '|---------|---------|---------------|-------------|\n';
+
+            for (const vuln of results.dependencies || []) {
+              for (const v of vuln.vulns || []) {
+                table += `| ${vuln.name} | ${vuln.version} | ${v.id} | ${v.fix_versions?.join(', ') || 'N/A'} |\n`;
+              }
+            }
+
+            const title = `Security Alert: ${results.dependencies?.reduce((acc, d) => acc + (d.vulns?.length || 0), 0) || 0} Python vulnerabilities found`;
+
+            // Check for existing open issue
+            const existingIssues = await github.rest.issues.listForRepo({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              state: 'open',
+              labels: 'security,automated'
+            });
+
+            const existingIssue = existingIssues.data.find(i => i.title.startsWith('Security Alert:') && i.title.includes('Python'));
+
+            const body = `## Automated Security Audit Results
+
+            The weekly security audit found vulnerabilities in Python dependencies.
+
+            ${table}
+
+            ### Recommended Actions
+
+            1. Review each vulnerability
+            2. Update affected packages: \`pip install --upgrade <package>\`
+            3. Run \`pip-audit\` locally to verify fixes
+            4. Close this issue when resolved
+
+            ---
+            *This issue was automatically created by the security audit workflow.*`;
+
+            if (existingIssue) {
+              await github.rest.issues.update({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: existingIssue.number,
+                body: body
+              });
+              console.log(`Updated existing issue #${existingIssue.number}`);
+            } else {
+              await github.rest.issues.create({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                title: title,
+                body: body,
+                labels: ['security', 'automated', 'dependencies']
+              });
+              console.log('Created new security issue');
+            }
+
+  frontend-audit:
+    name: Frontend Security Audit
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'npm'
+          cache-dependency-path: frontend/package-lock.json
+
+      - name: Install dependencies
+        working-directory: frontend
+        run: npm ci
+
+      - name: Run npm audit
+        id: npm-audit
+        working-directory: frontend
+        run: |
+          npm audit --json > npm-audit-results.json || echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
+          npm audit --audit-level=moderate || true
+
+      - name: Upload audit results
+        if: always()
+        uses: actions/upload-artifact@v4
+        with:
+          name: npm-audit-results
+          path: frontend/npm-audit-results.json
+          retention-days: 30
+
+      - name: Create issue on vulnerability
+        if: steps.npm-audit.outputs.vulnerabilities_found == 'true'
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const results = JSON.parse(fs.readFileSync('frontend/npm-audit-results.json', 'utf8'));
+
+            const vulns = results.vulnerabilities || {};
+            const vulnCount = Object.keys(vulns).length;
+
+            if (vulnCount === 0) {
+              console.log('No vulnerabilities to report');
+              return;
+            }
+
+            // Build vulnerability table
+            let table = '| Package | Severity | Via | Fix |\n';
+            table += '|---------|----------|-----|-----|\n';
+
+            for (const [name, info] of Object.entries(vulns)) {
+              const via = Array.isArray(info.via) ? info.via.map(v => typeof v === 'string' ? v : v.name).join(', ') : info.via;
+              table += `| ${name} | ${info.severity} | ${via} | ${info.fixAvailable ? 'Yes' : 'No'} |\n`;
+            }
+
+            const title = `Security Alert: ${vulnCount} npm vulnerabilities found`;
+
+            // Check for existing open issue
+            const existingIssues = await github.rest.issues.listForRepo({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              state: 'open',
+              labels: 'security,automated'
+            });
+
+            const existingIssue = existingIssues.data.find(i => i.title.startsWith('Security Alert:') && i.title.includes('npm'));
+
+            const body = `## Automated Security Audit Results
+
+            The weekly security audit found vulnerabilities in npm dependencies.
+
+            ${table}
+
+            ### Recommended Actions
+
+            1. Review each vulnerability: \`npm audit\`
+            2. Auto-fix if possible: \`npm audit fix\`
+            3. Manual fix for breaking changes: \`npm audit fix --force\` (review changes!)
+            4. Close this issue when resolved
+
+            ---
+            *This issue was automatically created by the security audit workflow.*`;
+
+            if (existingIssue) {
+              await github.rest.issues.update({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: existingIssue.number,
+                body: body
+              });
+              console.log(`Updated existing issue #${existingIssue.number}`);
+            } else {
+              await github.rest.issues.create({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                title: title,
+                body: body,
+                labels: ['security', 'automated', 'dependencies']
+              });
+              console.log('Created new security issue');
+            }