diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e769a08b..e9f862b8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,28 @@ jobs: - name: Run ruff format check 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: name: Backend Tests runs-on: ubuntu-latest @@ -103,6 +125,29 @@ jobs: working-directory: frontend 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: name: Frontend Type Check runs-on: ubuntu-latest diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 000000000..ae9f89b4a --- /dev/null +++ b/.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 \` + 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'); + }