DAST in GitHub Actions: A Practical Tutorial with Vuln0x
A complete walkthrough of running DAST scans in GitHub Actions using Vuln0x: API tokens, workflow YAML, blocking PRs on critical findings, posting results to GitHub Code Scanning via SARIF, and Slack alerts. Production-ready in under an hour.

DAST in GitHub Actions: A Practical Tutorial with Vuln0x
Most teams agree DAST should run in CI. Most teams also do not actually run DAST in CI, because integrating a real scanner into a pipeline used to be a multi-week project. With the right tool, it should take an afternoon.
This tutorial walks through setting up a complete DAST pipeline in GitHub Actions using Vuln0x. By the end you will have:
- A scan that runs on every PR against a deployed preview environment.
- Critical findings that block the PR from merging.
- Results posted to GitHub Code Scanning as SARIF, so they show up in the Security tab.
- Slack alerts for high-severity findings.
Prerequisites
You need three things before starting:
- A Vuln0x account with at least one project. Sign up at vuln0x.com — the free tier with 20 credits is enough to test this tutorial end to end.
- A deployment with a stable, scannable URL. PR preview environments (Vercel, Netlify, Cloudflare Pages) work great. A staging environment also works.
- GitHub repository write access to add Actions and secrets.
Step 1 — Create a Vuln0x API token
In the Vuln0x dashboard:
- Go to Settings → API Reference.
- Create a new API token. Name it something like
github-actions-prod. - Copy the token. You will not be able to see it again.
- Settings → Secrets and variables → Actions → New repository secret.
- Name:
VULN0X_API_TOKEN. Value: the token you just copied. - While you are here, add
VULN0X_PROJECT_ID(the project ID from the dashboard) as another secret.
Step 2 — The minimum viable workflow
Create a file at .github/workflows/dast.yml:
name: DAST Scan
on:
pull_request:
branches: [main]
jobs:
dast:
runs-on: ubuntu-latest
steps:
- name: Wait for preview deployment
run: |
# Replace with your deploy provider's check or a sleep
sleep 60
- name: Trigger Vuln0x scan
id: scan
env:
VULN0X_API_TOKEN: ${{ secrets.VULN0X_API_TOKEN }}
VULN0X_PROJECT_ID: ${{ secrets.VULN0X_PROJECT_ID }}
TARGET_URL: https://pr-${{ github.event.pull_request.number }}.preview.example.com
run: |
curl -X POST https://api.vuln0x.com/v1/scans \
-H "Authorization: Bearer $VULN0X_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"project_id\":\"$VULN0X_PROJECT_ID\",\"target\":\"$TARGET_URL\",\"profile\":\"standard\"}" \
-o scan.json
echo "scan_id=$(jq -r .id scan.json)" >> $GITHUB_OUTPUT
- name: Wait for scan to complete
env:
VULN0X_API_TOKEN: ${{ secrets.VULN0X_API_TOKEN }}
SCAN_ID: ${{ steps.scan.outputs.scan_id }}
run: |
for i in $(seq 1 60); do
STATUS=$(curl -s -H "Authorization: Bearer $VULN0X_API_TOKEN" \
https://api.vuln0x.com/v1/scans/$SCAN_ID | jq -r .status)
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
break
fi
sleep 30
done
- name: Fail on critical findings
env:
VULN0X_API_TOKEN: ${{ secrets.VULN0X_API_TOKEN }}
SCAN_ID: ${{ steps.scan.outputs.scan_id }}
run: |
CRITICAL=$(curl -s -H "Authorization: Bearer $VULN0X_API_TOKEN" \
https://api.vuln0x.com/v1/scans/$SCAN_ID/findings?severity=critical | jq '. | length')
echo "Critical findings: $CRITICAL"
if [ "$CRITICAL" -gt 0 ]; then
echo "::error::Critical vulnerabilities detected. PR cannot merge."
exit 1
fi
That is the spine of the pipeline. It triggers a scan, waits for it to finish, and fails the job if any critical findings exist.
Note on the API: the exact endpoint paths and parameters in this tutorial follow the current Vuln0x REST API conventions. Always check the live API reference at vuln0x.com/dashboard/api for the most up-to-date schema.
Step 3 — Post results to GitHub Code Scanning (SARIF)
GitHub's Security tab can natively render any SARIF file. Vuln0x outputs SARIF, so this integration is essentially free:
- name: Download SARIF report
env:
VULN0X_API_TOKEN: ${{ secrets.VULN0X_API_TOKEN }}
SCAN_ID: ${{ steps.scan.outputs.scan_id }}
run: |
curl -s -H "Authorization: Bearer $VULN0X_API_TOKEN" \
"https://api.vuln0x.com/v1/scans/$SCAN_ID/report?format=sarif" \
-o vuln0x.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: vuln0x.sarif
category: vuln0x-dast
After this runs, every PR shows DAST findings inline in the GitHub UI, with code-scanning-style annotations. The Security tab gets a permanent record. Reviewers can comment on findings the same way they comment on code.
This is the single highest-leverage piece of the integration. Every additional scan over time builds a real security audit trail directly inside GitHub.
Step 4 — Slack alerts for high-severity findings
For high (not just critical) findings, we want a Slack ping rather than a hard failure. Add another step:
- name: Slack alert on high findings
if: always()
env:
VULN0X_API_TOKEN: ${{ secrets.VULN0X_API_TOKEN }}
SCAN_ID: ${{ steps.scan.outputs.scan_id }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: |
HIGH=$(curl -s -H "Authorization: Bearer $VULN0X_API_TOKEN" \
https://api.vuln0x.com/v1/scans/$SCAN_ID/findings?severity=high | jq '. | length')
if [ "$HIGH" -gt 0 ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\":warning: $HIGH high-severity findings on PR #${{ github.event.pull_request.number }}\"}" \
$SLACK_WEBHOOK
fi
Add SLACK_WEBHOOK as a repository secret pointing to a channel-specific Slack incoming webhook.
Step 5 — Sentinel pentest on main branch merges
DAST is fast (minutes). Sentinel — the autonomous AI pentest agent — runs longer because it does multi-phase exploitation, privilege escalation, and lateral movement. We do not want that on every PR. We do want it on every merge to main:
on:
push:
branches: [main]
jobs:
sentinel:
runs-on: ubuntu-latest
steps:
- name: Trigger Sentinel pentest
env:
VULN0X_API_TOKEN: ${{ secrets.VULN0X_API_TOKEN }}
run: |
curl -X POST https://api.vuln0x.com/v1/sentinel/runs \
-H "Authorization: Bearer $VULN0X_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"project_id\":\"${{ secrets.VULN0X_PROJECT_ID }}\",\"target\":\"https://staging.example.com\"}"
You can (and should) wire the Sentinel run to also produce a SARIF file and Slack notifications, with the same patterns as the DAST job.
Step 6 — Optimize for credits
The free tier gives you 20 credits, and team plans use a similar credit model. A few tips to keep your scan budget reasonable:
- Run DAST only on PRs that touch app code — use
paths:in the workflow to skip docs/typo PRs. - Use the
quickprofile for PR scans and thestandardordeepprofile only for main-branch merges. - Run Sentinel weekly, not on every merge, if your branch is high-traffic.
- Block on critical only, not on high — high findings ping Slack but do not fail builds, so you maintain a fast PR loop.
Step 7 — Avoid the classic CI/CD security mistakes
This integration is only as good as the discipline around it. The classic mistakes:
- Treating the scanner as the audit. It is not. It is one signal. Logic flaws, design issues, and authorization mistakes still need human (or Sentinel-agent) review.
- Suppressing all noise. If everyone gets in the habit of suppressing findings, the pipeline becomes theater. Triage findings; do not blanket-mute them.
- Pointing the scanner at production. Always scan staging or PR previews. A loud DAST run hammers your app — that is the point. Production should never see those payloads.
- Storing the API token in plain text. Use
secrets, notenvblocks committed to the repo.
Putting it all together
The full workflow file ends up around 80 lines and gives you:
- Per-PR DAST scans with SARIF output to GitHub Security.
- Critical findings that block PR merges.
- High findings that ping Slack but do not block.
- Weekly or on-merge Sentinel pentests.
- A real audit trail you can show to a SOC 2 or ISO 27001 auditor.
Try it on your own repo
Sign up at vuln0x.com, grab your API token, copy the YAML above, and ship the workflow on a real PR today. The 20 free credits cover several full pipeline runs — enough to confirm the integration works end-to-end before you commit to a plan.
If you hit any rough edges in the API or the GitHub Action wiring, tell us. This is exactly the kind of integration we want to make boring and reliable for everyone.
Frequently Asked Questions
Why run DAST in CI/CD instead of weekly?
Because most security regressions are introduced by code changes, and most code changes ship through PRs. Catching a critical vulnerability before merge is dramatically cheaper than catching it after deploy. Weekly scans are still useful for production drift and dependency updates, but PR-time scanning is where most of the value lives.
Will scanning every PR slow down my deployment pipeline?
Quick-profile DAST scans typically finish in a few minutes for small to medium apps. You can run them in parallel with your other CI checks so they do not extend total wall time. For larger apps or deeper Sentinel pentests, run those on merge-to-main rather than on every PR.
Can I use this with GitLab CI or other platforms?
Yes. The same REST API calls work from any CI system. Vuln0x has documented integrations for GitHub Actions and GitLab CI specifically, plus a generic REST API and webhooks for everything else. If you are on CircleCI, Buildkite, Jenkins, or anything else that can run a curl command, the same workflow translates directly.
What happens if Vuln0x flags a false positive in a PR?
Use the dashboard to mark a finding as a false positive or accepted risk, with a reason. The next scan will respect that decision and the PR will not block on it. Avoid blanket suppressions — track each false positive with a justification so the audit trail stays meaningful.