Skip to main content

CI/CD Integration

Run DAST on every push, every PR, or on a schedule. The scanner ships as a Docker image and returns a non-zero exit code when findings cross the --fail-on threshold โ€” perfect for blocking a pipeline.

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 json โ€” machine-readable findings on stdout.

Exit codesโ€‹

CodeMeaning
0Scan completed; no findings above --fail-on threshold.
1Scan failed, or findings at/above --fail-on.
130Scan interrupted.

GitHub Actionsโ€‹

Basic workflowโ€‹

levo-dast.yml checked into the repo:

version: "1"
name: "acme-webapp"
target:
url: "${TARGET_URL}"
auth:
strategy: "none"
scan:
depth: "smart"

.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 --shm-size=1g \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
-e TARGET_URL=${{ secrets.TARGET_URL }} \
-e LEVOAI_AUTH_KEY=${{ secrets.LEVOAI_AUTH_KEY }} \
-e LEVOAI_ORG_ID=${{ secrets.LEVOAI_ORG_ID }} \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml --ci --fail-on high

Authenticated scanโ€‹

levo-dast.yml:

version: "1"
name: "acme-webapp-staging"
target:
url: "${STAGING_URL}"
auth:
strategy: "form"
login_url: "${LOGIN_URL}"
username: "${SCAN_USERNAME}"
scan:
depth: "smart"

Workflow:

- name: Run authenticated DAST
run: |
docker run --rm --shm-size=1g \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
-e STAGING_URL=${{ secrets.STAGING_URL }} \
-e LOGIN_URL=${{ secrets.LOGIN_URL }} \
-e SCAN_USERNAME=${{ secrets.SCAN_USERNAME }} \
-e LEVOAI_AUTH_KEY=${{ secrets.LEVOAI_AUTH_KEY }} \
-e LEVOAI_ORG_ID=${{ secrets.LEVOAI_ORG_ID }} \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml \
--password "${{ secrets.SCAN_PASSWORD }}" \
--ci --fail-on critical

GitHub secret setupโ€‹

SecretValue
TARGET_URLhttps://example.com
STAGING_URLhttps://staging.example.com
LOGIN_URLhttps://example.com/login
SCAN_USERNAMEService account username
SCAN_PASSWORDService account password
LEVOAI_AUTH_KEYLevo auth key
LEVOAI_ORG_IDOrganization ID

GitLab CIโ€‹

dast:
image: docker:24
services: [docker:24-dind]
stage: test
script:
- |
docker run --rm --shm-size=1g \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
-e TARGET_URL -e SCAN_USERNAME \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml \
--password "$SCAN_PASSWORD" \
--ci --fail-on high

Best practicesโ€‹

  1. Service accounts for auth โ€” don't scan with a developer's personal account.
  2. Scan staging, not production. If you must scan prod, follow Scanning production safely.
  3. Set fail_on deliberately. high blocks pipelines on high/critical; critical only blocks on critical. Pick the one your team will actually fix, not the one that looks strictest.
  4. Schedule deep scans off-peak and keep PR scans fast (depth: smart, low max_pages).
  5. 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โ€‹

Was this page helpful?