Skip to main content

Examples & recipes

Copy-paste starting points for common scan scenarios. Pair each with the matching invocation; secrets stay in env vars.

1. Minimum viable config

Public site, passive-only.

version: "1"
name: "marketing-site"

target:
url: "https://marketing.example.com"

auth:
strategy: "none"

scan:
enable_passive: true
enable_active: false
shadownet scan

2. Form-authenticated web app

Commit-friendly config for a typical login-protected app.

version: "1"
name: "acme-webapp"

target:
url: "https://app.example.com"
ignore_third_party: true

crawl:
type: "hybrid"
max_depth: 4
max_pages: 150

auth:
strategy: "form"
login_url: "https://app.example.com/login"
username: "${SCAN_USERNAME}"

scan:
depth: "smart"
enable_active: true
# Log in once — caches your selected env, no per-scan env-id needed
shadownet login -k "$LEVOAI_AUTH_KEY" -o "$LEVOAI_ORG_ID" --env-name <env_name>

# Then run the scan
shadownet scan --password "$SCAN_PASSWORD"

3. API-only scan (Bearer token)

No crawler UI nonsense — hit the API, fuzz it.

version: "1"
name: "acme-api"

target:
url: "https://api.example.com"

crawl:
type: "standard"
max_pages: 500

auth:
strategy: "token"
headers:
- "X-Scan-Source: shadownet"

scan:
enable_active: true
enable_graphql: true
http_methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]
inject_locations: ["query", "body", "header", "path"]
shadownet scan --token "$SCAN_TOKEN"

4. Browser-session (session transplant)

Reuse a logged-in session captured by the Levo browser extension — bypasses login flows that the scanner can't reasonably script (MFA, SSO, biometric, captchas). The YAML is non-secret; the actual session lives in a separate session.json file that you mount alongside.

version: "1"
name: "acme-webapp"

target:
url: "https://app.example.com"

auth:
strategy: "session_transplant"
# Optional: reference the JSON in YAML instead of passing
# --auth-session-file. Path resolved relative to CWD when the
# scan runs.
# session_file: "session.json"

scan:
enable_active: true
# Local CLI
shadownet scan --config levo-dast.yml \
--auth-session-file ./session.json
# Docker — mount both files
docker run --rm --shm-size=1g \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-v "$PWD/session.json:/work/session.json:ro" \
-w /work \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml \
--auth-session-file /work/session.json
Where session.json comes from

Install the Levo browser extension, log into the target app, click Export session. The JSON is a credential — treat it like a password (commit it nowhere, mount read-only, rotate when the session expires).

5. CI-friendly (GitHub Actions)

Opinionated config for PR-gating scans — fast, smart depth, fails the job when findings hit high or above (the --fail-on high flag on the docker invocation below).

# levo-dast.yml (checked into the app repo)
version: "1"
name: "acme-webapp"

target:
url: "${TARGET_URL}"

crawl:
type: "standard"
max_depth: 2
max_pages: 50

auth:
strategy: "form"
login_url: "${LOGIN_URL}"
username: "${SCAN_USERNAME}"

scan:
depth: "smart"
enable_passive: true
enable_active: true
# .github/workflows/dast.yml
- name: DAST scan
run: |
docker run --rm --shm-size=1g \
-v "$PWD/levo-dast.yml:/work/levo-dast.yml:ro" \
-w /work \
-e TARGET_URL -e LOGIN_URL -e SCAN_USERNAME -e SCAN_PASSWORD \
-e LEVOAI_AUTH_KEY -e LEVOAI_ORG_ID \
levoai/levoai-shadownet:stable \
scan --config /work/levo-dast.yml --password "$SCAN_PASSWORD" --fail-on high

6. Thorough weekly release scan

Release-candidate scan that takes longer but digs deeper. Turns on every active-test category and cranks attack_strength to insane.

version: "1"
name: "acme-webapp"

target:
url: "https://staging.example.com"

crawl:
type: "hybrid"
max_depth: 6
max_pages: 500
max_global_clicks: 2000
timeout: 1800

scan:
depth: "thorough"
attack_strength: "insane"
enable_ai: true
max_payloads: 200
timeout: 7200
test_timeout: 60

active_testing_categories:
xss: true
sqli: true
path_traversal: true
ssrf: true
xxe: true
command_injection: true
open_redirect: true
csrf: true
idor: true

cve:
js: true
dom: true

7. Environment interpolation patterns

# Required variable — scan fails if SCAN_USERNAME is unset.
auth:
username: "${SCAN_USERNAME}"

# Optional with fallback inside a string.
target:
url: "${TARGET_URL:-https://app.example.com}"

# Interpolation in a header list.
auth:
headers:
- "X-Build-Sha: ${GITHUB_SHA}"
- "X-Run-Id: ${GITHUB_RUN_ID}"

8. What not to do

These will be rejected
auth:
password: "hunter2" # rejected — use $SCAN_PASSWORD
token: "eyJhbGciOi..." # rejected — use $SCAN_TOKEN
local_storage_b64: "eyJ..." # rejected — use $SCAN_LOCAL_STORAGE_B64

# No top-level `levo:` block — identity comes from CLI flags / env vars.
# --org-id / $LEVOAI_ORG_ID, --env-id / $LEVOAI_ENV_ID, --app-id / $LEVOAI_APP_ID

ai:
api_key: "sk-ant-..." # rejected — use $ANTHROPIC_API_KEY

Unknown top-level keys (app:, vulnerabilities:, timeouts:) are also rejected — the loader runs with extra = "forbid".

The loader prints the offending YAML path and the env var / flag to use instead.

Next

Was this page helpful?