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