CyberDuty Scanner
Complete command reference for local Docker-based pentesting with OWASP ZAP and Nuclei. Scans run entirely on your machine; pushing findings to a hosted portal is optional. Nuclei downloads its templates on first run.
Installation
Install from the public repository. Clone, install dependencies, build the dashboard UI, and link the cyber command globally.
# Clone the repository git clone https://github.com/pv-cloud/cyberduty-scanner.git cd cyberduty-scanner # Install dependencies and build the dashboard UI npm install npm run build # Link the CLI globally (exposes the `cyber` command) npm link -w @cyber/cli # Verify cyber --version
cyber pentest. Requires Node.js ≥ 20.
npm run build — it compiles the local dashboard UI. Without it, cyber dashboard serves a blank page (the CLI will warn you).
Quick start
From zero to a local security report. Pick one engine per scan with --engine (default zap); run it twice to use both.
# 1. Start your app (if it isn't running already) docker compose up -d # 2. Run a ZAP scan — use the container name and the port inside the container cyber pentest my-app --port 3000 # 3. (optional) Run a Nuclei template scan too — a separate scan entry cyber pentest my-app --port 3000 --engine nuclei # 4. Open the findings dashboard cyber dashboard → Cyber dashboard running at http://localhost:8787
Each run is stored as its own scan. The dashboard lists every scan and shows findings grouped by severity with evidence, affected URLs, and a Claude-ready remediation brief.
cyber pentest
The primary command. Resolves the target container, starts the selected scan engines, groups findings, and publishes a report to the local dashboard.
cyber pentest <container-ref> [options]
The positional <container-ref> (or -c, --container) selects the target container. These are the core scan flags; authentication flags are covered under Authenticated scans.
| Flag | Description |
|---|---|
| -c, --container <ref> | Target container Container name, short ID, or full ID — alternative to the positional argument. |
| -p, --port <n> | Target port HTTP port the app listens on inside the container's Docker network. default: 3000 |
| -s, --scheme <s> |
URL scheme
http or https.
default: http
|
| --path <path> | Target path Starting URL path for the scan. default: / |
| -e, --engine <e> |
Scan engine
zap or nuclei. One engine per run — run twice to use both; each is stored as a separate scan.
default: zap
|
| -m, --mode <m> |
Scan depth
For ZAP: baseline (passive + spider) or full (active). For Nuclei it maps to severities (baseline = medium,high,critical; full = all).
default: baseline
|
| --severity <list> |
Nuclei severities
Nuclei only. Comma-separated from info,low,medium,high,critical; overrides the --mode mapping.
|
| --network <name> | Docker network Use a specific Docker network instead of auto-detecting the container's. |
Example — targeted run
# Full ZAP active scan on a specific network cyber pentest api-server \ --port 8080 \ --engine zap \ --mode full \ --network app-net # Nuclei, critical + high templates only cyber pentest api-server \ --port 8080 \ --engine nuclei \ --severity critical,high
--mode full performs active probing that may modify application state. Use only against containers running locally for testing purposes, and only on systems you own or have explicit authorization to test.
cyber scans
List every scan stored locally, newest first — one line per scan with its ID, status, mode, engine, target, and severity counts. Use the ID with cyber export or cyber push.
cyber scans m6rQCt7sBIcs completed baseline juice-shop http://juice-shop:3000/ high=0 medium=10 W9GGoiwKJ-V5 completed baseline parento-portal-nginx-1 http://parento-portal-nginx-1:80/ high=0 medium=0
cyber export
Export a scan's findings as a Claude-ready remediation brief in Markdown — distinct issues grouped from raw findings, with affected endpoints, evidence, and a fix task. Hand the file to a Claude Code session in the target app's repo to drive the fixes.
cyber export [scanId] [options]
| Flag | Description |
|---|---|
| [scanId] | Scan to export Positional. Omit to export the most recent completed scan. |
| -m, --min-risk <risk> |
Minimum severity
One of High, Medium, Low, Informational, Unknown.
default: High
|
| -o, --out <file> |
Output file
File path, or - for stdout.
default: <report-dir>/remediation.md
|
| --format <fmt> |
Output format
Currently markdown.
default: markdown
|
# Latest completed scan, High findings, to the default file cyber export # A specific scan, include Medium+, write to a file cyber export m6rQCt7sBIcs --min-risk Medium -o remediation.md # Pipe straight to stdout cyber export m6rQCt7sBIcs -o -
cyber dashboard
Starts the local dashboard API + UI and opens it in your browser. It reads the local scan database, so every scan you've run (ZAP and Nuclei) appears here.
cyber dashboard Cyber dashboard running at http://localhost:8787
| Flag | Description |
|---|---|
| -p, --port <port> | Dashboard/API port default: 8787 |
| --no-open | Don't open the browser Start the server without launching a browser tab. |
Each scan's detail view shows findings by severity, request/response evidence, affected URLs, and a copy-ready "Remediation brief (for Claude)".
npm run build in the repo, then restart cyber dashboard. If the UI isn't built, the command prints a warning and won't auto-open a blank page.
cyber push & login
Optional. Upload a scan's findings to a hosted CyberDuty portal for team-wide triage and AI-assisted fixes. Scanning is fully local; this is the only step that sends data off your machine.
cyber login / logout
cyber login opens a browser OAuth flow and stores a CLI token in ~/.cyber-scanner/credentials (chmod 600). cyber logout clears it.
| Flag | Description |
|---|---|
| --url <url> |
Portal base URL
Overrides $CYBER_CLOUD_URL or the saved URL. (login & push)
|
| --org <slug> |
Destination org
push only. Required if you belong to more than one organization.
|
| --all |
Push every scan
push only. Upload all completed scans instead of a single one.
|
# Sign in (stores a CLI token) cyber login --url https://portal.example.com # Push the latest completed scan (or a specific scanId) cyber push cyber push m6rQCt7sBIcs --org acme # CI / headless: use env vars instead of `cyber login` CYBER_CLOUD_URL=https://portal.example.com \ CYBER_CLOUD_TOKEN=cyk_… \ cyber push --all
OWASP ZAP
CyberDuty runs OWASP ZAP as a Docker container internally. You don't need to install ZAP separately. Two scan modes are available:
Baseline (passive)
Spiders the application and runs passive checks only. No requests are modified. Safe to run against any environment. Covers: missing security headers, insecure cookies, information disclosure, and common misconfigurations.
cyber pentest my-app --port 3000 --engine zap --mode baseline
Full (active)
Actively probes the application for vulnerabilities including SQL injection, XSS, path traversal, and authentication weaknesses. Sends crafted requests that may alter data. Use only against a dedicated test instance.
cyber pentest my-app --port 3000 --engine zap --mode full
Nuclei
Nuclei runs template-based checks against the target. Templates cover a wide range of vulnerability classes:
Severity levels
Select severities with --severity (overrides the --mode mapping, where baseline = medium,high,critical and full = all).
# Nuclei, default severities (medium,high,critical from --mode baseline) cyber pentest my-app --port 3000 --engine nuclei # Nuclei, critical + high only cyber pentest my-app --port 3000 \ --engine nuclei \ --severity critical,high
~/.cyber-scanner/nuclei-templates, so subsequent runs are fast. The first run needs internet access. Nuclei critical findings are recorded as High (the model has no separate Critical tier).
Authenticated scans
Both engines can scan behind a login using the same flags. ZAP drives its built-in form-login engine (form auth + cookie session + anti-CSRF, forced-user mode); Nuclei performs a headless form login in a helper container and replays the session cookie on every request. Framework presets fill in the login path and field names — override any of them individually.
| Flag | Description |
|---|---|
| --framework <preset> |
Auth preset
laravel · django · rails · express — pre-fills login path, username/password field names, and CSRF token.
|
| --auth-user <username> | Username / email for the login form. |
| --auth-pass <password> | Password for the login form. |
| --login-url <url> | Login POST URL. Defaults to the target + the preset's login path. |
| --user-field / --pass-field <name> | Form field names for username and password (override the preset). |
| --csrf-field <name> | Anti-CSRF token field name. |
| --logged-in-regex / --logged-out-regex <regex> | Session indicators — response regexes marking an authenticated / logged-out session. |
| --auth-config <file> | JSON auth file — supply any of the above as JSON instead of flags (see Configuration). |
| --rewrite-host <url> | ZAP only. Rewrite a base URL to the scan target in responses (e.g. an app APP_URL unreachable from the scan network). Repeatable; ignored by Nuclei. |
Each preset fills in that framework's login path, username/password field names, and CSRF token — so you usually only need --framework, --auth-user, and --auth-pass. The same flags work with --engine nuclei.
Laravel
# Preset fills: email / password fields · _token CSRF · /login cyber pentest app-nginx --port 80 --network app-net --mode full \ --framework laravel --auth-user admin@example.com --auth-pass secret \ --rewrite-host http://localhost:8001
Django
# Preset fills: username / password fields · csrfmiddlewaretoken · /accounts/login/ cyber pentest web --port 8000 --network app-net --mode full \ --framework django --auth-user admin --auth-pass secret
Rails
# Preset fills: user[email] / user[password] fields · authenticity_token · /users/sign_in (Devise) cyber pentest rails-app --port 3000 --network app-net --mode full \ --framework rails --auth-user admin@example.com --auth-pass secret
Express
# Preset fills: username / password fields · _csrf · /login (shown with the Nuclei engine) cyber pentest node-app --port 3000 \ --engine nuclei --severity high,critical \ --framework express --auth-user admin --auth-pass secret
node:20-alpine container on the target's Docker network (the first authenticated scan pulls that image).
Container references
The <container-ref> argument accepts three forms. CyberDuty resolves whichever you provide.
| Form | Example | Notes |
|---|---|---|
| Container name | my-app |
The NAME column from docker ps. Easiest to use in scripts. |
| Short ID | 8c91a4d72fb1 |
First 12 characters of the full container ID. |
| Full ID | 8c91a4d72fb1a… |
Full 64-character container ID from docker inspect. |
# Find your container name docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}" NAMES IMAGE STATUS my-app node:20-alpine Up 4 minutes postgres-db postgres:15 Up 4 minutes # Pentest by name cyber pentest my-app --port 3000
Output & storage
Everything is stored under ~/.cyber-scanner on your machine.
| Output | Description |
|---|---|
| Console summary | Real-time engine output and a severity summary on completion, with the scan ID. |
| Local dashboard | Interactive report at http://localhost:8787 via cyber dashboard. Lists all scans and shows findings + the remediation brief. |
| Remediation brief | Claude-ready Markdown via cyber export — grouped issues with a fix task per issue. |
~/.cyber-scanner/cyber.db |
SQLite database holding scan metadata and findings for every run. |
~/.cyber-scanner/reports/<scanId>/ |
Per-scan raw reports: zap-report.html + zap-report.json (ZAP) or nuclei-report.jsonl (Nuclei), plus remediation.md. |
Configuration
CyberDuty is configured with CLI flags plus a few environment variables and an optional auth file — there is no project config file. Flags always take precedence.
Environment variables
| Variable | Description |
|---|---|
| CYBER_CLOUD_URL | Default hosted-portal base URL for cyber login / cyber push (overridden by --url). |
| CYBER_CLOUD_TOKEN | CLI token for headless cyber push (CI), instead of running cyber login. |
Saved login state lives in ~/.cyber-scanner/credentials (portal URL + token, chmod 600).
Auth config file (--auth-config)
Supply authentication settings as JSON instead of flags. Any field can also be set with its matching --auth-* flag.
{
"username": "admin@example.com",
"password": "secret",
"loginUrl": "http://app-nginx:80/login",
"userField": "email",
"passField": "password",
"csrfTokens": ["_token"],
"loggedInRegex": "Log ?out"
}
cyber pentest app-nginx --port 80 --auth-config ./auth.json
CI integration
Run CyberDuty as a step in your CI pipeline to surface security issues before they merge or deploy. cyber pentest exits non-zero if the scan fails to complete; it does not currently gate on finding severity, so export the report as an artifact for review.
GitHub Actions
name: Security scan
on: [push, pull_request]
jobs:
pentest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start application
run: docker compose up -d
- name: Install CyberDuty
run: |
git clone https://github.com/pv-cloud/cyberduty-scanner.git
cd cyberduty-scanner
npm ci && npm run build
npm link -w @cyber/cli
- name: Run pentest
run: cyber pentest my-app --port 3000
- name: Export remediation brief
if: always()
run: cyber export --min-risk Medium -o ./remediation.md
- name: Upload scan artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: cyberduty-report
path: remediation.md
GitLab CI
security-scan:
stage: test
services:
- docker:dind
script:
- docker compose up -d
- git clone https://github.com/pv-cloud/cyberduty-scanner.git && cd cyberduty-scanner
- npm ci && npm run build && npm link -w @cyber/cli
- cyber pentest my-app --port 3000
- cyber export --min-risk Medium -o remediation.md
artifacts:
paths:
- remediation.md
when: always
Troubleshooting
Container not found
Error: container "my-app" not found
Verify the container is running: docker ps. The name must match exactly. Check that Docker is running and you have permission to access the Docker socket.
Dashboard is blank
The dashboard UI must be built. Run npm run build in the repo, then restart cyber dashboard. If the UI isn't built, the command prints a warning and won't open a browser tab.
Dashboard port in use
Start it on another port: cyber dashboard -p 8899, or stop the process occupying port 8787.
Nuclei: "no templates provided"
Nuclei downloads its templates on the first scan into ~/.cyber-scanner/nuclei-templates. Ensure the machine has internet access for that first run; later runs reuse the cache.
ZAP scan fails to start
ZAP runs in its own Docker container. Ensure Docker has enough memory allocated (at least 2 GB). On macOS, check Docker Desktop resource settings.