NRCL-64 Register trusted release candidate validation (#23) #42
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: Security Scan | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| dependency-audit: | |
| runs-on: ubuntu-latest | |
| name: Dependency Audit (Advisory) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.11" | |
| enable-cache: true | |
| - name: Export dependency graph for audit | |
| run: uv export --extra dev --format requirements-txt -o requirements-dev.txt | |
| - name: Run pip-audit | |
| id: pip_audit | |
| continue-on-error: true | |
| run: uvx pip-audit -r requirements-dev.txt -f json -o pip-audit.json | |
| - name: Summarize dependency findings | |
| if: always() | |
| env: | |
| PIP_AUDIT_OUTCOME: ${{ steps.pip_audit.outcome }} | |
| run: | | |
| uv run python - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| summary = Path("pip-audit-summary.md") | |
| report_path = Path("pip-audit.json") | |
| audit_outcome = os.getenv("PIP_AUDIT_OUTCOME", "unknown") | |
| if not report_path.exists(): | |
| summary.write_text( | |
| "## Dependency Audit (Advisory)\n\n" | |
| f"- pip-audit step outcome: **{audit_outcome}**\n" | |
| "- Report missing: pip-audit did not produce `pip-audit.json`.\n" | |
| ) | |
| print("pip-audit report missing") | |
| else: | |
| report = json.loads(report_path.read_text()) | |
| findings = [] | |
| for dep in report.get("dependencies", []): | |
| for vuln in dep.get("vulns", []): | |
| findings.append((dep["name"], dep["version"], vuln.get("id", "UNKNOWN"))) | |
| with summary.open("w", encoding="utf-8") as f: | |
| f.write("## Dependency Audit (Advisory)\n\n") | |
| f.write(f"- pip-audit step outcome: **{audit_outcome}**\n") | |
| f.write(f"- Vulnerabilities found: **{len(findings)}**\n\n") | |
| if findings: | |
| f.write("| Package | Version | Vulnerability |\n") | |
| f.write("|---|---|---|\n") | |
| for pkg, ver, vuln in findings[:25]: | |
| f.write(f"| {pkg} | {ver} | {vuln} |\n") | |
| else: | |
| f.write("No known vulnerabilities found.\n") | |
| PY | |
| cat pip-audit-summary.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload dependency report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: pip-audit-report | |
| path: | | |
| pip-audit.json | |
| pip-audit-summary.md | |
| retention-days: 14 | |
| static-security: | |
| runs-on: ubuntu-latest | |
| name: Bandit Scan (Advisory) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.11" | |
| enable-cache: true | |
| - name: Run Bandit | |
| id: bandit | |
| continue-on-error: true | |
| run: uvx --from bandit bandit -q -r neural -f json -o bandit.json | |
| - name: Summarize Bandit findings | |
| if: always() | |
| env: | |
| BANDIT_OUTCOME: ${{ steps.bandit.outcome }} | |
| run: | | |
| uv run python - <<'PY' | |
| import json | |
| import os | |
| from pathlib import Path | |
| report_path = Path("bandit.json") | |
| summary = Path("bandit-summary.md") | |
| bandit_outcome = os.getenv("BANDIT_OUTCOME", "unknown") | |
| if not report_path.exists(): | |
| summary.write_text( | |
| "## Static Security Scan (Bandit, Advisory)\n\n" | |
| f"- Bandit step outcome: **{bandit_outcome}**\n" | |
| "- Report missing: Bandit did not produce `bandit.json`.\n" | |
| ) | |
| print("Bandit report missing") | |
| else: | |
| report = json.loads(report_path.read_text()) | |
| results = report.get("results", []) | |
| with summary.open("w", encoding="utf-8") as f: | |
| f.write("## Static Security Scan (Bandit, Advisory)\n\n") | |
| f.write(f"- Bandit step outcome: **{bandit_outcome}**\n") | |
| f.write(f"- Findings: **{len(results)}**\n\n") | |
| if results: | |
| f.write("| Severity | File | Test ID | Message |\n") | |
| f.write("|---|---|---|---|\n") | |
| for finding in results[:25]: | |
| sev = finding.get("issue_severity", "UNKNOWN") | |
| path = finding.get("filename", "unknown") | |
| test_id = finding.get("test_id", "UNKNOWN") | |
| text = finding.get("issue_text", "").replace("|", "\\|") | |
| f.write(f"| {sev} | {path} | {test_id} | {text} |\n") | |
| else: | |
| f.write("No Bandit findings detected.\n") | |
| PY | |
| cat bandit-summary.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload Bandit report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: bandit-report | |
| path: | | |
| bandit.json | |
| bandit-summary.md | |
| retention-days: 14 |