1+ name : Semgrep Code Scan with Claude Verification
2+ on :
3+ push :
4+ branches : [ main ]
5+ pull_request :
6+ branches : [ main ]
7+ workflow_dispatch :
8+
9+ jobs :
10+ semgrep :
11+ name : Run Semgrep with Claude Verification
12+ runs-on : ubuntu-latest
13+ steps :
14+ - name : Checkout repository
15+ uses : actions/checkout@v4
16+
17+ - name : Set up Python
18+ uses : actions/setup-python@v5
19+ with :
20+ python-version : 3.x
21+
22+ - name : Install Semgrep
23+ run : pip install semgrep
24+
25+ - name : Run Semgrep scan (non-blocking)
26+ run : semgrep ci --config auto || true
27+
28+ - name : Save Semgrep reports
29+ if : always()
30+ run : |
31+ semgrep --config auto --json > semgrep-report.json
32+ semgrep --config auto --text > semgrep-report.txt
33+
34+ - name : Install Claude CLI
35+ if : always()
36+ run : |
37+ curl -fsSL https://cli.anthropic.com/install.sh | sh
38+ echo "$HOME/.local/bin" >> $GITHUB_PATH
39+
40+ - name : Verify Semgrep findings with Claude
41+ if : always()
42+ env :
43+ ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
44+ run : |
45+ cat > claude_prompt.txt <<'EOF'
46+ You are a security code reviewer. I have a Semgrep scan report that I need you to verify.
47+
48+ Your task:
49+ 1. Read the semgrep-report.json file which contains security findings
50+ 2. For EACH finding in the report:
51+ - Access the specific file mentioned in the finding
52+ - Navigate to the exact line number mentioned
53+ - Read the surrounding code context
54+ - Determine if the finding is a TRUE POSITIVE or FALSE POSITIVE
55+ - If TRUE POSITIVE: Explain why it's a real security issue and provide a specific fix
56+ - If FALSE POSITIVE: Explain why it's not actually a security issue
57+
58+ 3. Create a detailed markdown report (claude-verification-report.md) with this structure:
59+
60+ # Semgrep Findings Verification Report
61+
62+ ## Summary
63+ - Total findings: X
64+ - True positives: Y
65+ - False positives: Z
66+
67+ ## Detailed Analysis
68+
69+ ### Finding 1: [Rule ID]
70+ - **File**: path/to/file.py
71+ - **Line**: 123
72+ - **Semgrep Message**: [original message]
73+ - **Verdict**: TRUE POSITIVE / FALSE POSITIVE
74+ - **Code Context**:
75+ ```
76+ [relevant code snippet]
77+ ```
78+ - **Analysis**: [Your detailed explanation]
79+ - **Recommended Fix** (if applicable):
80+ ```
81+ [suggested code fix]
82+ ```
83+
84+ [Repeat for each finding]
85+
86+ Please analyze all findings thoroughly by examining the actual code.
87+ EOF
88+
89+ claude "$(cat claude_prompt.txt)"
90+
91+ - name : Check if Claude report exists
92+ if : always()
93+ id : check_report
94+ run : |
95+ if [ -f "claude-verification-report.md" ]; then
96+ echo "report_exists=true" >> $GITHUB_OUTPUT
97+ else
98+ echo "report_exists=false" >> $GITHUB_OUTPUT
99+ echo "Claude verification report not found"
100+ fi
101+
102+ - name : Display Claude verification summary
103+ if : always() && steps.check_report.outputs.report_exists == 'true'
104+ run : |
105+ echo "=== Claude Verification Summary ==="
106+ head -n 20 claude-verification-report.md
107+ echo ""
108+ echo "Full report available in artifacts"
109+
110+ - name : Upload all reports
111+ if : always()
112+ uses : actions/upload-artifact@v4
113+ with :
114+ name : security-scan-reports
115+ path : |
116+ semgrep-report.json
117+ semgrep-report.txt
118+ claude-verification-report.md
119+
120+ - name : Comment on PR (optional)
121+ if : always() && github.event_name == 'pull_request' && steps.check_report.outputs.report_exists == 'true'
122+ uses : actions/github-script@v7
123+ with :
124+ script : |
125+ const fs = require('fs');
126+ let comment = '## Security Scan Results\n\n';
127+
128+ try {
129+ const claudeReport = fs.readFileSync('claude-verification-report.md', 'utf8');
130+ const summary = claudeReport.split('## Detailed Analysis')[0];
131+ comment += summary;
132+ comment += '\n\n Full verification report available in workflow artifacts.';
133+ } catch (error) {
134+ comment += 'Claude verification report could not be read.';
135+ }
136+
137+ github.rest.issues.createComment({
138+ issue_number: context.issue.number,
139+ owner: context.repo.owner,
140+ repo: context.repo.repo,
141+ body: comment
142+ });
0 commit comments