name: Component Security Validation on: pull_request: paths: - 'cli-tool/components/**/*.md' - 'cli-tool/src/validation/**' - '.github/workflows/component-security-validation.yml' push: branches: - main paths: - 'cli-tool/components/**/*.md' permissions: contents: read pull-requests: write jobs: security-audit: name: Security Audit runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 # Full history for git metadata - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: '18' cache: 'npm' cache-dependency-path: cli-tool/package-lock.json - name: Install dependencies run: | cd cli-tool npm ci --ignore-scripts - name: Run Security Audit id: audit run: | cd cli-tool npm run security-audit:ci continue-on-error: true - name: Generate JSON Report if: always() run: | cd cli-tool npm run security-audit:json - name: Upload Security Report if: always() uses: actions/upload-artifact@v7 with: name: security-audit-report path: cli-tool/security-report.json retention-days: 30 - name: Comment PR with Results if: github.event_name == 'pull_request' && always() uses: actions/github-script@v9 with: script: | const fs = require('fs'); const reportPath = 'cli-tool/security-report.json'; if (!fs.existsSync(reportPath)) { console.log('No security report found'); return; } const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); const passed = report.summary.passed; const failed = report.summary.failed; const warnings = report.summary.warnings; const total = report.summary.total; const status = failed === 0 ? '✅ PASSED' : '❌ FAILED'; const emoji = failed === 0 ? '🎉' : '⚠️'; let comment = `## ${emoji} Security Audit Report\n\n`; comment += `**Status**: ${status}\n\n`; // Summary table comment += `| Metric | Count |\n`; comment += `|--------|-------|\n`; comment += `| Total Components | ${total} |\n`; comment += `| ✅ Passed | ${passed} |\n`; comment += `| ❌ Failed | ${failed} |\n`; comment += `| ⚠️ Warnings | ${warnings} |\n\n`; if (failed > 0) { comment += `### ❌ Failed Components (Top 5)\n\n`; comment += `| Component | Errors | Warnings | Score |\n`; comment += `|-----------|--------|----------|-------|\n`; const failedComponents = report.components .filter(c => !c.overall.valid) .sort((a, b) => b.overall.errorCount - a.overall.errorCount) .slice(0, 5); for (const component of failedComponents) { const name = component.component.path.split('/').pop().replace('.md', ''); comment += `| \`${name}\` | ${component.overall.errorCount} | ${component.overall.warningCount} | ${component.overall.score}/100 |\n`; } if (report.components.filter(c => !c.overall.valid).length > 5) { const remaining = report.components.filter(c => !c.overall.valid).length - 5; comment += `\n*...and ${remaining} more failed component(s)*\n`; } } comment += `\n---\n`; comment += `📊 **[View Full Report](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})** for detailed error messages and all components`; // Post comment github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: comment });