Skip to content

Commit 1785d6a

Browse files
Add .github/workflows/vu1nz-scan.yml via sh1pt vu1nz-scan@1.0.0
1 parent 5c9ea82 commit 1785d6a

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

.github/workflows/vu1nz-scan.yml

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Managed by sh1pt Actions Fleet
2+
# pack: vu1nz-scan@1.0.0
3+
# install: sh1pt-actions-store
4+
# hash: sha256:a5f27998f1a6ddd9e2ff263724a5d4eb5887a306210d9c00591d9a918a7136ad
5+
name: vu1nz security scan
6+
7+
on:
8+
pull_request:
9+
10+
permissions:
11+
contents: read
12+
pull-requests: write
13+
14+
jobs:
15+
review:
16+
name: Review PR for security vulnerabilities
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 15
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.12"
26+
27+
- name: Install vu1nz
28+
run: pip install --quiet git+https://github.com/profullstack/vu1nz-gh-actions.git
29+
30+
- name: Load env file
31+
env:
32+
ENV_FILE: ${{ secrets.ENV_FILE }}
33+
run: |
34+
echo "$ENV_FILE" > "$RUNNER_TEMP/.env"
35+
echo "Keys in ENV_FILE:"
36+
grep -oP '^[A-Z_]+(?==)' "$RUNNER_TEMP/.env" || echo "(no keys found or different format)"
37+
ANTHROPIC_API_KEY=$(grep -E '^ANTHROPIC_API_KEY=' "$RUNNER_TEMP/.env" | head -1 | sed 's/^ANTHROPIC_API_KEY=//')
38+
if [ -n "$ANTHROPIC_API_KEY" ]; then
39+
echo "::add-mask::$ANTHROPIC_API_KEY"
40+
echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> "$GITHUB_ENV"
41+
echo "ANTHROPIC_API_KEY found and exported"
42+
else
43+
echo "::warning::ANTHROPIC_API_KEY not found in ENV_FILE"
44+
fi
45+
46+
- name: Review PR
47+
id: review
48+
env:
49+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50+
NO_COLOR: "1"
51+
TERM: dumb
52+
run: |
53+
vu1nz review-pr main \
54+
${{ github.repository }} \
55+
${{ github.event.pull_request.number }} \
56+
--token "$GITHUB_TOKEN" \
57+
--json \
58+
| tee "$RUNNER_TEMP/vu1nz-review-raw.txt" || true
59+
60+
python3 -c "
61+
import json, re, sys
62+
raw = open('$RUNNER_TEMP/vu1nz-review-raw.txt').read()
63+
raw = re.sub(r'\x1b\[[0-9;]*m', '', raw)
64+
start = raw.find('{')
65+
if start >= 0:
66+
obj, _ = json.JSONDecoder(strict=False).raw_decode(raw, start)
67+
json.dump(obj, sys.stdout)
68+
else:
69+
print('{}')
70+
" > "$RUNNER_TEMP/vu1nz-review.json"
71+
72+
- name: Build PR comment
73+
id: comment
74+
run: |
75+
python3 << 'PYEOF'
76+
import json, os, sys
77+
78+
review_file = os.environ.get("RUNNER_TEMP", "") + "/vu1nz-review.json"
79+
comment_file = os.environ.get("RUNNER_TEMP", "") + "/vu1nz-comment.md"
80+
81+
try:
82+
with open(review_file) as f:
83+
data = json.loads(f.read(), strict=False)
84+
except Exception as e:
85+
print(f"::warning::Could not parse review results: {e}")
86+
with open(comment_file, "w") as f:
87+
f.write("## vu1nz Security Review\n\nCould not parse review results.\n")
88+
sys.exit(0)
89+
90+
findings = data.get("findings", [])
91+
analysis = data.get("analysis", "")
92+
pr = data.get("pr_number", "?")
93+
total = len(findings)
94+
95+
counts = {"critical": 0, "high": 0, "medium": 0, "low": 0}
96+
for finding in findings:
97+
sev = finding.get("severity", "").lower()
98+
if sev in counts:
99+
counts[sev] += 1
100+
101+
has_hc = counts["critical"] > 0 or counts["high"] > 0
102+
103+
lines = ["## vu1nz Security Review", ""]
104+
lines.append(f"**{total}** finding(s) in PR #{pr}")
105+
lines.append("")
106+
107+
badge_parts = []
108+
for sev in ("critical", "high", "medium", "low"):
109+
if counts[sev] > 0:
110+
badge_parts.append(f"**{sev.upper()}**: {counts[sev]}")
111+
if badge_parts:
112+
lines.append(" | ".join(badge_parts))
113+
lines.append("")
114+
115+
if has_hc:
116+
lines.append("> **High or critical findings - review before merging.**")
117+
lines.append("")
118+
119+
if findings:
120+
lines.append("### Findings")
121+
lines.append("")
122+
lines.append("| Severity | File | Issue | Suggestion |")
123+
lines.append("|----------|------|-------|------------|")
124+
for f in findings:
125+
sev = f.get("severity", "?").upper()
126+
file = f.get("file", "N/A")
127+
issue = f.get("issue", "").replace("\n", " ")[:150]
128+
suggestion = f.get("suggestion", "").replace("\n", " ")[:150]
129+
lines.append(f"| {sev} | `{file}` | {issue} | {suggestion} |")
130+
lines.append("")
131+
else:
132+
lines.append("No security issues found.")
133+
lines.append("")
134+
135+
if analysis:
136+
lines.append("<details><summary>Full AI Analysis</summary>")
137+
lines.append("")
138+
lines.append(analysis)
139+
lines.append("")
140+
lines.append("</details>")
141+
142+
body = "\n".join(lines)
143+
with open(comment_file, "w") as f:
144+
f.write(body)
145+
146+
with open(os.environ.get("GITHUB_OUTPUT", ""), "a") as out:
147+
out.write(f"total={total}\n")
148+
out.write(f"has_high_critical={'true' if has_hc else 'false'}\n")
149+
150+
if has_hc:
151+
print(f"::error::vu1nz found high/critical vulnerabilities in PR code")
152+
sys.exit(1)
153+
154+
print(f"::notice::vu1nz review: {total} finding(s), no high/critical issues")
155+
PYEOF
156+
157+
- name: Write report to job summary
158+
if: always()
159+
run: |
160+
if [ -f "$RUNNER_TEMP/vu1nz-comment.md" ]; then
161+
cat "$RUNNER_TEMP/vu1nz-comment.md" >> "$GITHUB_STEP_SUMMARY"
162+
else
163+
echo "## vu1nz Security Review" >> "$GITHUB_STEP_SUMMARY"
164+
echo "" >> "$GITHUB_STEP_SUMMARY"
165+
echo "Scan completed but could not read results." >> "$GITHUB_STEP_SUMMARY"
166+
fi
167+
168+
- name: Comment on PR
169+
if: always() && github.event.pull_request.head.repo.full_name == github.repository
170+
uses: actions/github-script@v7
171+
with:
172+
script: |
173+
const fs = require('fs');
174+
const commentFile = `${process.env.RUNNER_TEMP}/vu1nz-comment.md`;
175+
let body;
176+
try {
177+
body = fs.readFileSync(commentFile, 'utf8');
178+
} catch {
179+
body = '## vu1nz Security Review\n\nScan completed but could not read results.';
180+
}
181+
182+
try {
183+
const { data: comments } = await github.rest.issues.listComments({
184+
issue_number: context.issue.number,
185+
owner: context.repo.owner,
186+
repo: context.repo.repo,
187+
});
188+
189+
const existing = comments.find(c =>
190+
c.user.type === 'Bot' && c.body.includes('vu1nz Security Review')
191+
);
192+
193+
if (existing) {
194+
await github.rest.issues.updateComment({
195+
comment_id: existing.id,
196+
owner: context.repo.owner,
197+
repo: context.repo.repo,
198+
body: body,
199+
});
200+
} else {
201+
await github.rest.issues.createComment({
202+
issue_number: context.issue.number,
203+
owner: context.repo.owner,
204+
repo: context.repo.repo,
205+
body: body,
206+
});
207+
}
208+
} catch (err) {
209+
if (err.status === 403) {
210+
core.warning(`Cannot post PR comment (read-only token): ${err.message}. Findings are in the job summary.`);
211+
} else {
212+
throw err;
213+
}
214+
}

0 commit comments

Comments
 (0)