CI/CD Integration
Run DAST on every push, every PR, or on a schedule. The scanner ships as a Docker image, returns non-zero on findings above a threshold, and emits SARIF for the GitHub Security tab.
Two ways to configure a CI scan
levo-dast.ymlโ commit the scan config to the repo. Only secrets stay in CI. Recommended.- CLI flags โ one-off invocations with all options on the command line.
Key conceptsโ
CI/CD flagsโ
--ci/--non-interactiveโ run without prompts.--fail-on <severity>โ exit 1 if any finding at or above the threshold (criticalยทhighยทmediumยทlow).--output sarifโ SARIF report for the GitHub Security tab.--output jsonโ machine-readable findings.
Exit codesโ
| Code | Meaning |
|---|---|
| 0 | Scan completed; no findings above --fail-on threshold. |
| 1 | Scan failed, or findings at/above --fail-on. |
| 130 | Scan interrupted. |
GitHub Actionsโ
Basic workflowโ
- With levo-dast.yml
- With CLI flags
levo-dast.yml checked into the repo:
version: "1"
target:
url: "${TARGET_URL}"
auth:
strategy: "none"
scan:
depth: "smart"
reporting:
output: "sarif"
output_file: "/work/out/results.sarif"
fail_on: "high"
.github/workflows/dast.yml:
name: DAST Security Test
on:
push:
branches: [main, develop]
schedule:
- cron: '0 2 * * *'
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run DAST
run: |
docker run --rm \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-v "$PWD:/work/out" \
-w /work \
-e TARGET_URL=${{ secrets.TARGET_URL }} \
-e LEVOAI_AUTH_KEY=${{ secrets.LEVOAI_AUTH_KEY }} \
-e LEVOAI_ORG_ID=${{ secrets.LEVOAI_ORG_ID }} \
ghcr.io/levoai/levoai-shadownet:latest scan
- name: Upload to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
name: DAST Security Test
on:
push:
branches: [main, develop]
schedule:
- cron: '0 2 * * *'
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Run DAST
run: |
pip install shadownet
playwright install chromium
shadownet scan ${{ secrets.TARGET_URL }} \
--ci \
--auth none \
--output sarif \
--output-file results.sarif \
--fail-on high
- name: Upload to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Authenticated scanโ
- With levo-dast.yml
- With CLI flags
levo-dast.yml:
version: "1"
target:
url: "${STAGING_URL}"
auth:
strategy: "form"
login_url: "${LOGIN_URL}"
username: "${SCAN_USERNAME}"
scan:
depth: "smart"
reporting:
output: "sarif"
output_file: "/work/out/results.sarif"
fail_on: "critical"
Workflow:
- name: Run authenticated DAST
run: |
docker run --rm \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-v "$PWD:/work/out" -w /work \
-e STAGING_URL=${{ secrets.STAGING_URL }} \
-e LOGIN_URL=${{ secrets.LOGIN_URL }} \
-e SCAN_USERNAME=${{ secrets.SCAN_USERNAME }} \
-e SCAN_PASSWORD=${{ secrets.SCAN_PASSWORD }} \
-e LEVOAI_AUTH_KEY=${{ secrets.LEVOAI_AUTH_KEY }} \
-e LEVOAI_ORG_ID=${{ secrets.LEVOAI_ORG_ID }} \
ghcr.io/levoai/levoai-shadownet:latest scan --password "$SCAN_PASSWORD"
- name: Run authenticated DAST
run: |
pip install shadownet
playwright install chromium
shadownet scan ${{ secrets.STAGING_URL }} \
--ci \
--auth form \
--login-url ${{ secrets.LOGIN_URL }} \
--username ${{ secrets.SCAN_USERNAME }} \
--password ${{ secrets.SCAN_PASSWORD }} \
--output sarif \
--output-file results.sarif \
--fail-on critical
Reporting findings to the Levo dashboardโ
Add these env vars to any job (both styles) to push findings to your Levo workspace:
env:
LEVOAI_AUTH_KEY: ${{ secrets.LEVOAI_AUTH_KEY }}
LEVOAI_ORG_ID: ${{ secrets.LEVOAI_ORG_ID }}
LEVOAI_ENV_ID: ${{ secrets.LEVOAI_ENV_ID }}
In YAML config, set reporting.send_issues: true. With flags, add --send-issues --env-id $LEVOAI_ENV_ID.
GitHub secret setupโ
| Secret | Value |
|---|---|
TARGET_URL | https://example.com |
STAGING_URL | https://staging.example.com |
LOGIN_URL | https://example.com/login |
SCAN_USERNAME | Service account username |
SCAN_PASSWORD | Service account password |
LEVOAI_AUTH_KEY | Levo auth key |
LEVOAI_ORG_ID | Organization ID |
LEVOAI_ENV_ID | Environment ID |
GitLab CIโ
dast:
image: docker:24
services: [docker:24-dind]
stage: test
script:
- |
docker run --rm \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-v "$PWD:/work/out" -w /work \
-e TARGET_URL -e SCAN_USERNAME -e SCAN_PASSWORD \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID -e LEVOAI_ENV_ID \
ghcr.io/levoai/levoai-shadownet:latest scan --password "$SCAN_PASSWORD"
artifacts:
when: always
paths: [results.sarif]
Best practicesโ
- Service accounts for auth โ don't scan with a developer's personal account.
- Scan staging, not production. If you must scan prod, follow Scanning production safely.
- Set
fail_ondeliberately.highblocks pipelines on high/critical;criticalonly blocks on critical. Pick the one your team will actually fix, not the one that looks strictest. - Schedule deep scans off-peak and keep PR scans fast (
depth: smart, lowmax_pages). - Version-control
levo-dast.ymlโ you'll thank yourself the next time someone tunes the scan.
Example: PR-fast, nightly-thoroughโ
Two YAML files in the repo, picked by environment:
# PR job
shadownet scan --config levo-dast.pr.yml
# Nightly job
shadownet scan --config levo-dast.nightly.yml
Next stepsโ
- CLI reference โ every flag and exit code.
- YAML schema reference โ every YAML field.
- Levo dashboard โ findings triage and reporting.
- Configuration โ environment variables and advanced scan settings.
Was this page helpful?