/
/
/
1# Dependency Security Check Workflow
2# Checks Python dependencies for security vulnerabilities and supply chain risks
3
4name: Dependency Security Check
5
6on:
7 pull_request_target:
8 paths:
9 - "requirements_all.txt"
10 - "**/manifest.json"
11 branches:
12 - stable
13 - dev
14
15permissions:
16 contents: read
17 pull-requests: write
18 issues: write # Needed to post PR comments
19
20jobs:
21 security-check:
22 runs-on: ubuntu-latest
23 steps:
24 - name: Check out code from GitHub
25 uses: actions/checkout@v6
26 with:
27 ref: ${{ github.event.pull_request.head.sha }}
28 fetch-depth: 0 # Need full history for diff
29
30 - name: Set up Python
31 uses: actions/[email protected]
32 with:
33 python-version: "3.12"
34
35 - name: Install security tools
36 run: |
37 pip install pip-audit
38
39 # Step 1: Run pip-audit for known vulnerabilities
40 - name: Run pip-audit on all requirements
41 id: pip_audit
42 continue-on-error: true
43 run: |
44 echo "## ð Vulnerability Scan Results" > audit_report.md
45 echo "" >> audit_report.md
46
47 if pip-audit -r requirements_all.txt --desc --format=markdown >> audit_report.md 2>&1; then
48 echo "status=pass" >> $GITHUB_OUTPUT
49 echo "â
No known vulnerabilities found" >> audit_report.md
50 else
51 echo "status=fail" >> $GITHUB_OUTPUT
52 echo "" >> audit_report.md
53 echo "â ï¸ **Vulnerabilities detected! Please review the findings above.**" >> audit_report.md
54 fi
55
56 cat audit_report.md
57
58 # Step 2: Detect new or changed dependencies
59 - name: Detect dependency changes
60 id: deps_check
61 run: |
62 # Get base branch (dev or stable)
63 BASE_BRANCH="${{ github.base_ref }}"
64
65 # Check for changes in requirements_all.txt
66 if git diff origin/$BASE_BRANCH...HEAD -- requirements_all.txt > /dev/null 2>&1; then
67 # Extract added lines (new or modified dependencies)
68 git diff origin/$BASE_BRANCH...HEAD -- requirements_all.txt | \
69 grep "^+" | grep -v "^+++" | sed 's/^+//' > new_deps_raw.txt || true
70
71 # Also check for version changes (lines that were modified)
72 git diff origin/$BASE_BRANCH...HEAD -- requirements_all.txt | \
73 grep "^-" | grep -v "^---" | sed 's/^-//' > old_deps_raw.txt || true
74
75 if [ -s new_deps_raw.txt ]; then
76 echo "has_changes=true" >> $GITHUB_OUTPUT
77 echo "## ð¦ Dependency Changes Detected" > deps_report.md
78 echo "" >> deps_report.md
79 echo "The following dependencies were added or modified:" >> deps_report.md
80 echo "" >> deps_report.md
81 echo '```diff' >> deps_report.md
82 git diff origin/$BASE_BRANCH...HEAD -- requirements_all.txt >> deps_report.md
83 echo '```' >> deps_report.md
84 echo "" >> deps_report.md
85
86 # Extract just package names for safety check
87 cat new_deps_raw.txt | grep -v "^#" | grep -v "^$" > new_deps.txt || true
88
89 if [ -s new_deps.txt ]; then
90 echo "New/modified packages to review:" >> deps_report.md
91 cat new_deps.txt | while read line; do
92 echo "- \`$line\`" >> deps_report.md
93 done
94 fi
95 else
96 echo "has_changes=false" >> $GITHUB_OUTPUT
97 echo "No dependency changes detected in requirements_all.txt" > deps_report.md
98 fi
99 else
100 echo "has_changes=false" >> $GITHUB_OUTPUT
101 echo "No dependency changes detected" > deps_report.md
102 fi
103
104 cat deps_report.md
105
106 # Step 3: Check manifest.json changes
107 - name: Check provider manifest changes
108 id: manifest_check
109 run: |
110 BASE_BRANCH="${{ github.base_ref }}"
111
112 # Find all changed manifest.json files
113 CHANGED_MANIFESTS=$(git diff --name-only origin/$BASE_BRANCH...HEAD | grep "manifest.json" || true)
114
115 if [ -n "$CHANGED_MANIFESTS" ]; then
116 echo "has_changes=true" >> $GITHUB_OUTPUT
117 echo "## ð Provider Manifest Changes" > manifest_report.md
118 echo "" >> manifest_report.md
119 echo "The following provider manifests were modified:" >> manifest_report.md
120 echo "" >> manifest_report.md
121
122 for manifest in $CHANGED_MANIFESTS; do
123 echo "### \`$manifest\`" >> manifest_report.md
124 echo "" >> manifest_report.md
125
126 # Check if there are requirements changes in the manifest
127 if git diff origin/$BASE_BRANCH...HEAD -- "$manifest" | grep -i "requirements" > /dev/null 2>&1; then
128 echo "Requirements section modified:" >> manifest_report.md
129 echo '```diff' >> manifest_report.md
130 git diff origin/$BASE_BRANCH...HEAD -- "$manifest" | grep -A 10 -B 2 "requirements" >> manifest_report.md || true
131 echo '```' >> manifest_report.md
132 else
133 echo "No requirements changes detected" >> manifest_report.md
134 fi
135 echo "" >> manifest_report.md
136 done
137 else
138 echo "has_changes=false" >> $GITHUB_OUTPUT
139 echo "No provider manifest changes detected" > manifest_report.md
140 fi
141
142 cat manifest_report.md
143
144 # Step 4: Run package safety check on new dependencies
145 - name: Check new package safety
146 id: safety_check
147 if: steps.deps_check.outputs.has_changes == 'true'
148 continue-on-error: true
149 run: |
150 echo "## ð¡ï¸ Supply Chain Security Check" > safety_report.md
151 echo "" >> safety_report.md
152
153 if [ -f new_deps.txt ] && [ -s new_deps.txt ]; then
154 # Run our custom safety check script
155 python scripts/check_package_safety.py new_deps.txt > safety_output.txt 2>&1
156 SAFETY_EXIT=$?
157
158 cat safety_output.txt >> safety_report.md
159 echo "" >> safety_report.md
160
161 if [ $SAFETY_EXIT -eq 2 ]; then
162 echo "status=high_risk" >> $GITHUB_OUTPUT
163 echo "" >> safety_report.md
164 echo "â ï¸ **HIGH RISK PACKAGES DETECTED**" >> safety_report.md
165 echo "Manual security review is **required** before merging this PR." >> safety_report.md
166 elif [ $SAFETY_EXIT -eq 1 ]; then
167 echo "status=medium_risk" >> $GITHUB_OUTPUT
168 echo "" >> safety_report.md
169 echo "â ï¸ **MEDIUM RISK PACKAGES DETECTED**" >> safety_report.md
170 echo "Please review the warnings above before merging." >> safety_report.md
171 else
172 echo "status=pass" >> $GITHUB_OUTPUT
173 fi
174 else
175 echo "No new dependencies to check" >> safety_report.md
176 echo "status=pass" >> $GITHUB_OUTPUT
177 fi
178
179 cat safety_report.md
180
181 # Step 5: Combine all reports and post as PR comment
182 - name: Create combined security report
183 id: report
184 run: |
185 echo "# ð Dependency Security Report" > security_report.md
186 echo "" >> security_report.md
187 echo "Automated security check for dependency changes in this PR." >> security_report.md
188 echo "" >> security_report.md
189 echo "---" >> security_report.md
190 echo "" >> security_report.md
191
192 # Add all report sections
193 cat audit_report.md >> security_report.md
194 echo "" >> security_report.md
195 echo "---" >> security_report.md
196 echo "" >> security_report.md
197
198 cat deps_report.md >> security_report.md
199 echo "" >> security_report.md
200
201 if [ -f manifest_report.md ]; then
202 echo "---" >> security_report.md
203 echo "" >> security_report.md
204 cat manifest_report.md >> security_report.md
205 echo "" >> security_report.md
206 fi
207
208 if [ -f safety_report.md ]; then
209 echo "---" >> security_report.md
210 echo "" >> security_report.md
211 cat safety_report.md >> security_report.md
212 echo "" >> security_report.md
213 fi
214
215 echo "---" >> security_report.md
216 echo "" >> security_report.md
217 echo "## ð Review Checklist" >> security_report.md
218 echo "" >> security_report.md
219
220 if [ "${{ steps.deps_check.outputs.has_changes }}" == "true" ] || [ "${{ steps.manifest_check.outputs.has_changes }}" == "true" ]; then
221 echo "Before merging this PR, please ensure:" >> security_report.md
222 echo "" >> security_report.md
223 echo "- [ ] All new dependencies are from trusted sources" >> security_report.md
224 echo "- [ ] Package names are spelled correctly (check for typosquatting)" >> security_report.md
225 echo "- [ ] Dependencies have active maintenance and community" >> security_report.md
226 echo "- [ ] No known vulnerabilities are present" >> security_report.md
227 echo "- [ ] Licenses are compatible with the project" >> security_report.md
228 echo "" >> security_report.md
229 echo "Once reviewed, a maintainer should add the **\`dependencies-reviewed\`** label to this PR." >> security_report.md
230 else
231 echo "â
No dependency changes detected in this PR." >> security_report.md
232 fi
233
234 cat security_report.md
235
236 # Add to GitHub job summary (always available, even for forks)
237 cat security_report.md >> $GITHUB_STEP_SUMMARY
238
239 # Step 6: Post comment to PR
240 - name: Post security report to PR
241 uses: actions/github-script@v7
242 with:
243 script: |
244 const fs = require('fs');
245 const report = fs.readFileSync('security_report.md', 'utf8');
246
247 // Find existing bot comment
248 const comments = await github.rest.issues.listComments({
249 owner: context.repo.owner,
250 repo: context.repo.repo,
251 issue_number: context.issue.number,
252 });
253
254 const botComment = comments.data.find(comment =>
255 comment.user.type === 'Bot' &&
256 comment.body.includes('ð Dependency Security Report')
257 );
258
259 if (botComment) {
260 // Update existing comment
261 await github.rest.issues.updateComment({
262 owner: context.repo.owner,
263 repo: context.repo.repo,
264 comment_id: botComment.id,
265 body: report
266 });
267 } else {
268 // Create new comment
269 await github.rest.issues.createComment({
270 owner: context.repo.owner,
271 repo: context.repo.repo,
272 issue_number: context.issue.number,
273 body: report
274 });
275 }
276
277 # Step 7: Check for approval label (if dependencies changed)
278 - name: Check for security review approval
279 if: |
280 (steps.deps_check.outputs.has_changes == 'true' ||
281 steps.manifest_check.outputs.has_changes == 'true')
282 uses: actions/github-script@v7
283 with:
284 script: |
285 const labels = context.payload.pull_request.labels.map(l => l.name);
286 const hasReviewLabel = labels.includes('dependencies-reviewed');
287 const isHighRisk = '${{ steps.safety_check.outputs.status }}' === 'high_risk';
288 const hasFailed = '${{ steps.pip_audit.outputs.status }}' === 'fail';
289
290 if (isHighRisk) {
291 core.setFailed('ð´ HIGH RISK dependencies detected! This PR requires thorough security review before merging.');
292 } else if (hasFailed) {
293 core.setFailed('ð´ Known vulnerabilities detected! Please address the security issues above.');
294 } else if (!hasReviewLabel) {
295 core.setFailed('â ï¸ Dependency changes detected. A maintainer must add the "dependencies-reviewed" label after security review.');
296 } else {
297 core.info('â
Security review approved via "dependencies-reviewed" label');
298 }
299
300 # Step 8: Fail the check if high-risk or vulnerabilities found
301 - name: Final security status
302 if: always()
303 run: |
304 if [ "${{ steps.pip_audit.outputs.status }}" == "fail" ]; then
305 echo "â Known vulnerabilities found!"
306 exit 1
307 fi
308
309 if [ "${{ steps.safety_check.outputs.status }}" == "high_risk" ]; then
310 echo "â High-risk packages detected!"
311 exit 1
312 fi
313
314 if [ "${{ steps.deps_check.outputs.has_changes }}" == "true" ] || [ "${{ steps.manifest_check.outputs.has_changes }}" == "true" ]; then
315 echo "â ï¸ Dependency changes require review"
316 # Don't fail here - the label check above will handle it
317 fi
318
319 echo "â
Security checks completed"
320