Skip to content

Commit 23f6e7f

Browse files
committed
feat(security): add OWASP ZAP automated security scan workflow
1 parent 3e2dd95 commit 23f6e7f

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: OWASP ZAP Security Scan
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
target_url:
7+
description: Target URL to scan (overrides STAGING_URL)
8+
required: false
9+
type: string
10+
push:
11+
branches:
12+
- main
13+
pull_request:
14+
branches:
15+
- main
16+
17+
jobs:
18+
zap-scan:
19+
name: OWASP ZAP Passive Scan
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v4
25+
26+
- name: Determine target URL
27+
id: target
28+
run: |
29+
if [ -n "${{ github.event.inputs.target_url }}" ]; then
30+
echo "url=${{ github.event.inputs.target_url }}" >> "$GITHUB_OUTPUT"
31+
elif [ -n "${{ vars.STAGING_URL }}" ]; then
32+
echo "url=${{ vars.STAGING_URL }}" >> "$GITHUB_OUTPUT"
33+
elif [ -n "${{ secrets.STAGING_URL }}" ]; then
34+
echo "url=${{ secrets.STAGING_URL }}" >> "$GITHUB_OUTPUT"
35+
else
36+
echo "url=http://localhost:3000" >> "$GITHUB_OUTPUT"
37+
fi
38+
39+
- name: Start local application stack
40+
if: steps.target.outputs.url == 'http://localhost:3000'
41+
run: docker compose up --build -d
42+
43+
- name: Wait for application readiness
44+
if: steps.target.outputs.url == 'http://localhost:3000'
45+
run: |
46+
for i in $(seq 1 12); do
47+
if curl -sf http://localhost:3000 > /dev/null 2>&1 && \
48+
curl -sf http://localhost:3001/api/health > /dev/null 2>&1; then
49+
echo "Application stack is ready"
50+
exit 0
51+
fi
52+
echo "Waiting for application stack... attempt $i"
53+
sleep 10
54+
done
55+
echo "Application stack failed to start"
56+
exit 1
57+
58+
- name: Run OWASP ZAP Baseline Scan
59+
uses: zaproxy/action-baseline@v0
60+
with:
61+
target: ${{ steps.target.outputs.url }}
62+
allow_failure: false
63+
64+
- name: Upload ZAP Report
65+
if: always()
66+
uses: actions/upload-artifact@v4
67+
with:
68+
name: zap-report
69+
path: report*
70+
retention-days: 30
71+
72+
- name: Comment PR with scan summary
73+
if: always() && github.event_name == 'pull_request'
74+
uses: actions/github-script@v7
75+
with:
76+
script: |
77+
const fs = require('fs');
78+
let body = '## OWASP ZAP Security Scan\n\n';
79+
try {
80+
const report = fs.readFileSync('report.md', 'utf8');
81+
body += 'Scan completed against **${{ steps.target.outputs.url }}**.\n\n';
82+
const lines = report.split('\n').slice(0, 30).join('\n');
83+
body += '```\n' + lines + '\n```\n\n';
84+
body += 'Full report available in the [ZAP Report artifact](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).';
85+
} catch {
86+
body += 'ZAP scan completed. Download the report artifact for details.';
87+
}
88+
github.rest.issues.createComment({
89+
issue_number: context.issue.number,
90+
owner: context.repo.owner,
91+
repo: context.repo.repo,
92+
body: body
93+
});

0 commit comments

Comments
 (0)