feat(ci): fail on critical vulnerabilities #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: AI Security Audit | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| ai-security-audit: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check ANTHROPIC_API_KEY | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| if [ -z "$ANTHROPIC_API_KEY" ]; then | |
| echo "::error::ANTHROPIC_API_KEY is not configured. Please add it to your repository secrets (Settings > Secrets and variables > Actions > New repository secret). The AI security audit cannot run without this key." | |
| exit 1 | |
| fi | |
| - name: Get PR diff | |
| id: diff | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh pr diff ${{ github.event.pull_request.number }} > pr_diff.txt | |
| echo "diff_size=$(wc -c < pr_diff.txt | tr -d ' ')" >> "$GITHUB_OUTPUT" | |
| - name: Check diff size | |
| id: check | |
| run: | | |
| if [ "${{ steps.diff.outputs.diff_size }}" -eq 0 ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "No diff found, skipping audit." | |
| elif [ "${{ steps.diff.outputs.diff_size }}" -gt 200000 ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Diff too large (>200KB), skipping AI audit." | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Install Claude Code | |
| if: steps.check.outputs.skip != 'true' | |
| run: npm install -g @anthropic-ai/claude-code | |
| - name: Run AI security audit | |
| if: steps.check.outputs.skip != 'true' | |
| id: audit | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| PROMPT=$(cat <<'AUDIT_PROMPT' | |
| You are a senior security engineer performing a security audit on a pull request diff. | |
| Analyze the following code diff and provide a structured security audit report. | |
| Focus on these areas: | |
| 1. **Critical Vulnerabilities**: SQL injection, command injection, XSS, SSRF, deserialization flaws, path traversal | |
| 2. **Authentication & Authorization**: Broken auth, missing access controls, credential exposure | |
| 3. **Cryptography Issues**: Weak algorithms, hardcoded keys/secrets, improper random number generation | |
| 4. **Data Exposure**: Sensitive data leaks, PII exposure, excessive logging of secrets | |
| 5. **Dependency Risks**: Known vulnerable patterns, unsafe deserialization | |
| 6. **Blockchain-Specific**: Private key handling, transaction signing flaws, smart contract interaction risks, address validation | |
| 7. **Input Validation**: Missing or insufficient validation, type confusion, buffer issues | |
| 8. **Configuration & Infrastructure**: Insecure defaults, debug mode in production, permissive CORS | |
| Output format (use GitHub-flavored Markdown): | |
| ## AI Security Audit Report | |
| ### Summary | |
| [One-paragraph overall assessment with risk level: CRITICAL / HIGH / MEDIUM / LOW / CLEAN] | |
| ### Findings | |
| For each finding: | |
| #### [SEVERITY] Finding Title | |
| - **File**: `filename:line_number` | |
| - **Category**: [category from above] | |
| - **Description**: What the issue is | |
| - **Impact**: What could go wrong | |
| - **Recommendation**: How to fix it | |
| If no security issues are found, state that the code appears clean and list what was checked. | |
| ### Statistics | |
| - Files analyzed: X | |
| - Issues found: X (critical: X, high: X, medium: X, low: X) | |
| --- | |
| *This report was generated by AI security audit. Please verify findings manually.* | |
| Here is the diff to audit: | |
| AUDIT_PROMPT | |
| ) | |
| DIFF_CONTENT=$(cat pr_diff.txt) | |
| FULL_PROMPT="${PROMPT} | |
| \`\`\`diff | |
| ${DIFF_CONTENT} | |
| \`\`\`" | |
| # Run claude and capture output | |
| AUDIT_RESULT=$(echo "$FULL_PROMPT" | claude -p --output-format text 2>&1) || true | |
| # Save result to file (avoid shell escaping issues) | |
| echo "$AUDIT_RESULT" > audit_result.md | |
| - name: Post audit comment | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Build comment body | |
| { | |
| echo "<!-- ai-security-audit -->" | |
| echo "" | |
| cat audit_result.md | |
| } > comment_body.md | |
| PR_NUMBER=${{ github.event.pull_request.number }} | |
| # Delete previous audit comment if exists | |
| EXISTING_COMMENT_ID=$(gh api \ | |
| "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ | |
| --jq '.[] | select(.body | contains("<!-- ai-security-audit -->")) | .id' \ | |
| | head -1) | |
| if [ -n "$EXISTING_COMMENT_ID" ]; then | |
| gh api \ | |
| --method DELETE \ | |
| "repos/${{ github.repository }}/issues/comments/${EXISTING_COMMENT_ID}" \ | |
| || true | |
| fi | |
| # Post new comment | |
| gh pr comment "$PR_NUMBER" --body-file comment_body.md | |
| - name: Fail if critical issues found | |
| if: steps.check.outputs.skip != 'true' | |
| run: | | |
| if grep -qi '\[CRITICAL\]' audit_result.md; then | |
| CRITICAL_COUNT=$(grep -ci '\[CRITICAL\]' audit_result.md) | |
| echo "::error::AI security audit found ${CRITICAL_COUNT} CRITICAL issue(s). Please fix them before merging." | |
| exit 1 | |
| fi | |
| - name: Post skip comment | |
| if: steps.check.outputs.skip == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PR_NUMBER=${{ github.event.pull_request.number }} | |
| REASON="No diff found" | |
| if [ "${{ steps.diff.outputs.diff_size }}" -gt 200000 ]; then | |
| REASON="Diff too large (>200KB) for AI audit" | |
| fi | |
| BODY="<!-- ai-security-audit --> | |
| ## AI Security Audit Report | |
| **Skipped**: ${REASON}. | |
| Please perform a manual security review." | |
| # Delete previous audit comment if exists | |
| EXISTING_COMMENT_ID=$(gh api \ | |
| "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ | |
| --jq '.[] | select(.body | contains("<!-- ai-security-audit -->")) | .id' \ | |
| | head -1) | |
| if [ -n "$EXISTING_COMMENT_ID" ]; then | |
| gh api \ | |
| --method DELETE \ | |
| "repos/${{ github.repository }}/issues/comments/${EXISTING_COMMENT_ID}" \ | |
| || true | |
| fi | |
| gh pr comment "$PR_NUMBER" --body "$BODY" |