5353 with :
5454 python-version : " 3.12"
5555
56- - name : Install dependencies
57- run : pip install pyyaml flask requests pytest ruff bandit
56+ - name : Install dependencies (pinned)
57+ run : pip install -r requirements-ci.txt
5858
5959 - name : Lint (syntax check)
6060 run : |
@@ -77,10 +77,35 @@ jobs:
7777
7878 - name : Bandit security scan
7979 run : |
80- bandit -r services/ -ll --skip B101,B404,B603 -f txt || {
81- echo "::warning::Bandit found potential security issues (see above)"
82- true
83- }
80+ # Fail on high-severity + high-confidence findings.
81+ # Medium/low findings are reported as warnings.
82+ bandit -r services/ -ll --skip B101,B404,B603 -f json -o /tmp/bandit.json || true
83+ python3 -c "
84+ import json, sys
85+ with open('/tmp/bandit.json') as f:
86+ data = json.load(f)
87+ high = [r for r in data.get('results', [])
88+ if r['issue_severity'] == 'HIGH' and r['issue_confidence'] == 'HIGH']
89+ for r in data.get('results', []):
90+ sev = r['issue_severity']
91+ msg = f\"{r['filename']}:{r['line_number']}: [{sev}] {r['issue_text']}\"
92+ if sev == 'HIGH':
93+ print(f'::error ::{msg}')
94+ else:
95+ print(f'::warning ::{msg}')
96+ if high:
97+ print(f'FAIL: {len(high)} high-severity/high-confidence finding(s)')
98+ sys.exit(1)
99+ print('OK: no high-severity/high-confidence findings')
100+ "
101+
102+ - name : Mypy type check (security-sensitive services)
103+ run : |
104+ mypy --ignore-missing-imports \
105+ services/common/ \
106+ services/agent/agent/ \
107+ services/quarantine/quarantine/ \
108+ services/ui/ui/
84109
85110 - name : Test (unit + integration)
86111 env :
@@ -233,7 +258,7 @@ jobs:
233258 go-version : " 1.23"
234259
235260 - name : Install Python dependencies
236- run : pip install pyyaml flask requests pytest
261+ run : pip install -r requirements-ci.txt
237262
238263 - name : Run adversarial Python tests
239264 run : python -m pytest tests/test_adversarial.py -v --tb=short
@@ -267,7 +292,7 @@ jobs:
267292 python-version : " 3.12"
268293
269294 - name : Install Python dependencies
270- run : pip install pyyaml flask requests pytest
295+ run : pip install -r requirements-ci.txt
271296
272297 - name : Check test counts for drift
273298 run : bash .github/scripts/check-test-counts.sh
@@ -291,26 +316,77 @@ jobs:
291316 - name : Install govulncheck
292317 run : go install golang.org/x/vuln/cmd/govulncheck@latest
293318
294- - name : Go vulnerability scan
319+ - name : Go vulnerability scan (enforced)
295320 run : |
296321 echo "=== Go Dependency Vulnerability Scan ==="
297322 VULN_ERRORS=0
298323 for svc in airlock registry tool-firewall gpu-integrity-watch mcp-firewall \
299324 policy-engine runtime-attestor integrity-monitor incident-recorder; do
300325 echo "--- ${svc} ---"
301326 cd "services/${svc}"
302- govulncheck ./... || VULN_ERRORS=$((VULN_ERRORS + 1))
327+ if ! govulncheck ./... 2>&1; then
328+ VULN_ERRORS=$((VULN_ERRORS + 1))
329+ echo "::error::${svc}: govulncheck found vulnerabilities"
330+ fi
303331 cd ../..
304332 done
305- if [ $VULN_ERRORS -gt 0 ]; then
306- echo "WARNING: $VULN_ERRORS service(s) have known vulnerabilities"
333+ # Check waivers
334+ WAIVED=$(python3 -c "
335+ import json, datetime
336+ with open('.github/vuln-waivers.json') as f:
337+ data = json.load(f)
338+ today = datetime.date.today().isoformat()
339+ active = [w for w in data.get('go', []) if w.get('expires', '') >= today]
340+ print(len(active))
341+ ")
342+ EFFECTIVE=$((VULN_ERRORS - WAIVED))
343+ if [ "$EFFECTIVE" -gt 0 ]; then
344+ echo "FAIL: $VULN_ERRORS service(s) have vulnerabilities ($WAIVED waived, $EFFECTIVE unwaived)"
345+ echo "To waive a reviewed finding, add it to .github/vuln-waivers.json"
346+ exit 1
307347 fi
348+ echo "OK: Go vulnerability scan passed ($WAIVED waiver(s) active)"
308349
309- - name : Python dependency audit
350+ - name : Python dependency audit (enforced)
310351 run : |
311- pip install pip-audit pyyaml flask requests
352+ pip install -r requirements-ci.txt
312353 echo "=== Python Dependency Audit ==="
313- pip-audit --strict --desc || echo "WARNING: Python dependencies have known vulnerabilities"
354+ # Run pip-audit, capture output
355+ pip-audit --strict --desc -f json -o /tmp/pip-audit.json 2>/dev/null || true
356+ python3 -c "
357+ import json, sys, datetime
358+ # Load audit results
359+ try:
360+ with open('/tmp/pip-audit.json') as f:
361+ data = json.load(f)
362+ except (FileNotFoundError, json.JSONDecodeError):
363+ print('OK: pip-audit produced no findings')
364+ sys.exit(0)
365+ vulns = data if isinstance(data, list) else data.get('dependencies', [])
366+ findings = [d for d in vulns if d.get('vulns')]
367+ if not findings:
368+ print('OK: no Python dependency vulnerabilities')
369+ sys.exit(0)
370+ # Load waivers
371+ with open('.github/vuln-waivers.json') as f:
372+ waivers = json.load(f)
373+ today = datetime.date.today().isoformat()
374+ waived_ids = {w['id'] for w in waivers.get('python', []) if w.get('expires', '') >= today}
375+ unwaived = 0
376+ for dep in findings:
377+ for v in dep.get('vulns', []):
378+ vid = v.get('id', '')
379+ if vid in waived_ids:
380+ print(f'WAIVED: {dep[\"name\"]} {vid}')
381+ else:
382+ print(f'::error::{dep[\"name\"]}: {vid} — {v.get(\"description\", \"\")}')
383+ unwaived += 1
384+ if unwaived > 0:
385+ print(f'FAIL: {unwaived} unwaived Python vulnerability finding(s)')
386+ print('To waive a reviewed finding, add it to .github/vuln-waivers.json')
387+ sys.exit(1)
388+ print(f'OK: all Python findings waived ({len(waived_ids)} waiver(s) active)')
389+ "
314390
315391 docs-validation :
316392 name : Documentation Validation
0 commit comments