Install
Three ways: pip, the Windows .exe from the
GitHub release
(wpsecscan.exe and wpsecscan-gui.exe, about 22 MB each), or source.
See the download page.
# pip (Python 3.10+)
pip install wpsecscan
# from source (all optional extras)
pip install -e ".[all]"
# Docker: build locally, there is no published image
docker build -t wpsecscan . && docker run --rm wpsecscan https://example.com First scan
The target is a positional argument; there is no scan verb.
# Walk every check with a synthetic site
wpsecscan --demo
# Pull the latest CVE feed
wpsecscan db update
# Scan your site (passive checks only by default)
wpsecscan https://your-site.com
# Save the report
wpsecscan https://your-site.com --out ./reports --format html
# Several report formats at once
wpsecscan https://your-site.com --out ./reports --format json,html,sarif The CVE feed is aggregated nightly from up to 8 public sources (NVD, MITRE, GHSA, OSV, Wordfence, WPVulnerability, Patchstack, CIRCL); per-source counts are published in the feed header.
Aggressive mode
31 checks send active payloads (SQLi probes, XSS reflections,
SSRF, etc.). They're off by default. Opt in with --aggressive
and confirm you are allowed to test the target with --owned
(see consent below).
wpsecscan https://your-site.com --aggressive --owned
# Also write read-only proof artifacts for confirmed findings
wpsecscan https://your-site.com --aggressive --owned --prove --prove runs one of 5 proof helpers
for each confirmed aggressive finding. Each collects minimal read-only evidence (see
docs/proof.md)
and writes it under the scan's output directory as
proof-CHECK_ID.txt. It works on a single target and
requires --aggressive.
Consent for aggressive checks
Aggressive checks run only against targets you have marked as owned: pass --owned,
set WPSECSCAN_OWNED_TARGETS, or record the host with wpsecscan owned add.
Otherwise the scan runs passive and records why.
wpsecscan owned add your-site.com
wpsecscan owned list
wpsecscan owned remove your-site.com Report formats
9 values for --format (repeat the flag or comma-separate them):
- json · machine-readable
- html · interactive report (--single-page-html for a print-friendly layout)
- csv · spreadsheet-friendly
- md · Markdown report
- xlsx · Excel workbook
- sarif · for code-scanning UIs
- burp · Burp Suite scope XML
- docx · Word-compatible (.rtf fallback)
- exec-pdf · one-page executive summary PDF
14 more formats are produced from a saved JSON report with
wpsecscan emit <format> report.json:
attestation-letter · cab · ce-plus · cef · e8 · fedramp · gdpr-dpia · hipaa-map · intoto · leef · risk-csv · risk-json · spdx-sbom · stakeholder-bundle
exec-pdf uses the optional [pdf] extra (reportlab) when installed and falls
back to an HTML print-to-PDF page otherwise.
Subcommand families
Three umbrella subcommands work from saved scan reports: emit (14 export
formats), push (16 destinations) and ai
(7 LLM helpers). In total the CLI has 66 subcommands;
run wpsecscan --help for the list.
# export formats (SBOM, in-toto, CEF, LEEF, HIPAA, FedRAMP, CE+, E8, ...)
wpsecscan emit spdx-sbom report.json --out sbom.json
wpsecscan emit cef report.json --out splunk-events.cef
wpsecscan emit hipaa-map report.json
wpsecscan emit --help # list all formats
# push destinations (provider settings come from environment variables)
wpsecscan push gitlab-ci report.json
wpsecscan push sentry report.json
wpsecscan push defectdojo report.json
wpsecscan push chat report.json # Mattermost / RocketChat / Telegram
wpsecscan push --help # env vars per provider
# LLM helpers (OpenAI, Anthropic or Ollama)
wpsecscan ai injection-check response.txt
wpsecscan ai control-map report.json --framework hipaa
wpsecscan ai waf-rule finding.json --target-waf cloudflare
wpsecscan ai drift https://example.com 42 push providers: gitlab-ci · circleci · azure-devops · buildkite · shortcut · plane · wiz · chat · hosting · automation · osv-enrich · exploitdb-xref · nuclei · sentry · datadog · defectdojo
Utilities
wpsecscan init # interactive first-run wizard
wpsecscan doctor # audit optional deps + tokens
wpsecscan check disable wpgraphql # toggle a check off
wpsecscan check enable wpgraphql # turn it back on
wpsecscan check list --json # machine-readable inventory
wpsecscan benchmark https://example.com # per-check timing table
wpsecscan export-config # show effective merged config
wpsecscan compare-pypi-version # is your install behind PyPI? Daemon mode
Runs scans on cron-style schedules from a YAML config (needs the [yaml] extra).
A target with aggressive: true only runs aggressive checks when it also has
owned: true (or consent resolves through WPSECSCAN_OWNED_TARGETS or
wpsecscan owned add); otherwise it is downgraded to passive.
wpsecscan --daemon daemon.yml # daemon.yml
targets:
- url: https://example.com
schedule: "0 3 * * *" # cron: daily at 3am
aggressive: false
fail_on: high
- url: https://shop.example.com
schedule: "0 */6 * * *" # every 6 hours
aggressive: true
owned: true # you own / are authorised to test this host
fail_on: critical
out_dir: /var/log/wpsecscan
webhook_url: https://hooks.slack.com/... API
A small HTTP API server can trigger scans and fetch past reports. It refuses to start without a bearer token.
export WPSECSCAN_API_TOKEN=change-me
wpsecscan --api-server 127.0.0.1:8765 POST /scan accepts "owned": true in the JSON body as
ownership consent for "aggressive": true. The response carries
consent: granted, downgraded (aggressive was requested but no consent
resolved, so the scan runs passive) or n/a (not aggressive). When
WPSECSCAN_API_ALLOWED_TARGETS is set (comma-separated hosts) and excludes the target host, the
server returns 403 before a scan starts. Details:
docs/api-reference.md.
Scheduled scans
Add sites to your list, install the scheduled job (Task Scheduler on Windows), and optionally configure a digest of new findings.
wpsecscan sites add https://example.com --weekly
wpsecscan sites list
wpsecscan schedule install --time 03:00
wpsecscan schedule status
wpsecscan schedule pause
wpsecscan schedule resume
wpsecscan schedule uninstall
wpsecscan digest configure --to [email protected] --smtp smtp.example.com:587
wpsecscan digest test
wpsecscan digest send Companion plugin
For server-side checks (MFA audit, DB triggers, wp-cron jobs, webhook
inventory), install the companion plugin. It is source-only: the plugin
folder is
wp-plugin/
in the repo. Generate a shared secret in the plugin settings, then
store it with the site entry or pass it for a single scan:
wpsecscan sites add https://example.com --companion-token sk_xxx
wpsecscan https://example.com --companion-token sk_xxx AI options (opt-in)
AI-assisted triage toggles behind an opt-in panel. All off by default.
They need an LLM key (OpenAI / Anthropic) or a local Ollama URL. The separate
wpsecscan ai subcommand has 7 helpers (see
subcommand families).
export OPENAI_API_KEY=sk-...
wpsecscan ai-options list
wpsecscan ai-options set severity_auto_tuner true
wpsecscan ai-options set exec_brief_audience cto
# Or via GUI: Tools → Advanced AI options... Usage analytics (opt-in, local-first)
Off by default. Local-only by default (events never leave your machine unless you also set an upload URL). Per-event field allowlist prevents PII leaks.
wpsecscan analytics enable
wpsecscan analytics show 50 # inspect last 50 events
wpsecscan analytics export out.jsonl
wpsecscan analytics forget # delete everything locally Full doc: docs/analytics.md.
Multi-site use
Self-hostable, one codebase. For more than one site, the scanner provides:
- a
sitesregistry (wpsecscan sites add / list); - batch scanning from a file of URLs;
- the
--daemonscheduler; - a token-protected
--api-server, with an optional target allowlist (WPSECSCAN_API_ALLOWED_TARGETS); - an HMAC-chained audit log, which records credential-vault and marketplace actions only.
There is no separate enterprise mode: no single sign-on, role-based access control or two-person approval is wired into the scanner. Details: docs/api-reference.md.
Integrations
The Python, JavaScript and Go SDKs are source-only, in
sdks/;
no packages are published. The Ansible role is in
ansible/.
The Kubernetes operator is a design scaffold (k8s/), not released.
Per-check reference
The per-check reference pages live in the repository for now, in
docs/checks/
on GitHub.
Windows Defender false-positive
Defender may flag wpsecscan.exe on first run. It's a known
false-positive because the binary contains string-pattern signatures
for webshells (so we can detect them in your site). The .exe is
Sigstore-signed; verify the signature to confirm authenticity.
Workaround: add an exclusion via Tools → Add Windows Defender exclusion... in the GUI, or run the PowerShell helper:
Add-MpPreference -ExclusionPath "C:\Tools\wpsecscan.exe" Writing a check
Drop a .py module in ~/.wpsecscan/checks/
or contribute one upstream. The scaffolder generates a skeleton:
python scripts/new-check.py --id my_check
python scripts/lint-checks.py # enforce hygiene Full tutorial: docs/writing-a-check.md.