From be0eabdab5a2d146af3fd30fcbb2842fa29068ba Mon Sep 17 00:00:00 2001 From: "sh1pt-actions-fleet[bot]" <287014002+sh1pt-actions-fleet[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 11:26:40 +0000 Subject: [PATCH] Add .github/workflows/vu1nz-scan.yml via sh1pt vu1nz-scan@1.0.0 --- .github/workflows/vu1nz-scan.yml | 214 +++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 .github/workflows/vu1nz-scan.yml diff --git a/.github/workflows/vu1nz-scan.yml b/.github/workflows/vu1nz-scan.yml new file mode 100644 index 0000000..0579790 --- /dev/null +++ b/.github/workflows/vu1nz-scan.yml @@ -0,0 +1,214 @@ +# Managed by sh1pt Actions Fleet +# pack: vu1nz-scan@1.0.0 +# install: sh1pt-actions-store +# hash: sha256:a5f27998f1a6ddd9e2ff263724a5d4eb5887a306210d9c00591d9a918a7136ad +name: vu1nz security scan + +on: + pull_request: + +permissions: + contents: read + pull-requests: write + +jobs: + review: + name: Review PR for security vulnerabilities + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install vu1nz + run: pip install --quiet git+https://github.com/profullstack/vu1nz-gh-actions.git + + - name: Load env file + env: + ENV_FILE: ${{ secrets.ENV_FILE }} + run: | + echo "$ENV_FILE" > "$RUNNER_TEMP/.env" + echo "Keys in ENV_FILE:" + grep -oP '^[A-Z_]+(?==)' "$RUNNER_TEMP/.env" || echo "(no keys found or different format)" + ANTHROPIC_API_KEY=$(grep -E '^ANTHROPIC_API_KEY=' "$RUNNER_TEMP/.env" | head -1 | sed 's/^ANTHROPIC_API_KEY=//') + if [ -n "$ANTHROPIC_API_KEY" ]; then + echo "::add-mask::$ANTHROPIC_API_KEY" + echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> "$GITHUB_ENV" + echo "ANTHROPIC_API_KEY found and exported" + else + echo "::warning::ANTHROPIC_API_KEY not found in ENV_FILE" + fi + + - name: Review PR + id: review + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NO_COLOR: "1" + TERM: dumb + run: | + vu1nz review-pr main \ + ${{ github.repository }} \ + ${{ github.event.pull_request.number }} \ + --token "$GITHUB_TOKEN" \ + --json \ + | tee "$RUNNER_TEMP/vu1nz-review-raw.txt" || true + + python3 -c " + import json, re, sys + raw = open('$RUNNER_TEMP/vu1nz-review-raw.txt').read() + raw = re.sub(r'\x1b\[[0-9;]*m', '', raw) + start = raw.find('{') + if start >= 0: + obj, _ = json.JSONDecoder(strict=False).raw_decode(raw, start) + json.dump(obj, sys.stdout) + else: + print('{}') + " > "$RUNNER_TEMP/vu1nz-review.json" + + - name: Build PR comment + id: comment + run: | + python3 << 'PYEOF' + import json, os, sys + + review_file = os.environ.get("RUNNER_TEMP", "") + "/vu1nz-review.json" + comment_file = os.environ.get("RUNNER_TEMP", "") + "/vu1nz-comment.md" + + try: + with open(review_file) as f: + data = json.loads(f.read(), strict=False) + except Exception as e: + print(f"::warning::Could not parse review results: {e}") + with open(comment_file, "w") as f: + f.write("## vu1nz Security Review\n\nCould not parse review results.\n") + sys.exit(0) + + findings = data.get("findings", []) + analysis = data.get("analysis", "") + pr = data.get("pr_number", "?") + total = len(findings) + + counts = {"critical": 0, "high": 0, "medium": 0, "low": 0} + for finding in findings: + sev = finding.get("severity", "").lower() + if sev in counts: + counts[sev] += 1 + + has_hc = counts["critical"] > 0 or counts["high"] > 0 + + lines = ["## vu1nz Security Review", ""] + lines.append(f"**{total}** finding(s) in PR #{pr}") + lines.append("") + + badge_parts = [] + for sev in ("critical", "high", "medium", "low"): + if counts[sev] > 0: + badge_parts.append(f"**{sev.upper()}**: {counts[sev]}") + if badge_parts: + lines.append(" | ".join(badge_parts)) + lines.append("") + + if has_hc: + lines.append("> **High or critical findings - review before merging.**") + lines.append("") + + if findings: + lines.append("### Findings") + lines.append("") + lines.append("| Severity | File | Issue | Suggestion |") + lines.append("|----------|------|-------|------------|") + for f in findings: + sev = f.get("severity", "?").upper() + file = f.get("file", "N/A") + issue = f.get("issue", "").replace("\n", " ")[:150] + suggestion = f.get("suggestion", "").replace("\n", " ")[:150] + lines.append(f"| {sev} | `{file}` | {issue} | {suggestion} |") + lines.append("") + else: + lines.append("No security issues found.") + lines.append("") + + if analysis: + lines.append("
Full AI Analysis") + lines.append("") + lines.append(analysis) + lines.append("") + lines.append("
") + + body = "\n".join(lines) + with open(comment_file, "w") as f: + f.write(body) + + with open(os.environ.get("GITHUB_OUTPUT", ""), "a") as out: + out.write(f"total={total}\n") + out.write(f"has_high_critical={'true' if has_hc else 'false'}\n") + + if has_hc: + print(f"::error::vu1nz found high/critical vulnerabilities in PR code") + sys.exit(1) + + print(f"::notice::vu1nz review: {total} finding(s), no high/critical issues") + PYEOF + + - name: Write report to job summary + if: always() + run: | + if [ -f "$RUNNER_TEMP/vu1nz-comment.md" ]; then + cat "$RUNNER_TEMP/vu1nz-comment.md" >> "$GITHUB_STEP_SUMMARY" + else + echo "## vu1nz Security Review" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Scan completed but could not read results." >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Comment on PR + if: always() && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const commentFile = `${process.env.RUNNER_TEMP}/vu1nz-comment.md`; + let body; + try { + body = fs.readFileSync(commentFile, 'utf8'); + } catch { + body = '## vu1nz Security Review\n\nScan completed but could not read results.'; + } + + try { + const { data: comments } = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + + const existing = comments.find(c => + c.user.type === 'Bot' && c.body.includes('vu1nz Security Review') + ); + + if (existing) { + await github.rest.issues.updateComment({ + comment_id: existing.id, + owner: context.repo.owner, + repo: context.repo.repo, + body: body, + }); + } else { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body, + }); + } + } catch (err) { + if (err.status === 403) { + core.warning(`Cannot post PR comment (read-only token): ${err.message}. Findings are in the job summary.`); + } else { + throw err; + } + }