CheckWP is an offline-first security scanner for WordPress plugins. It scans plugin directories and .zip archives for malware, backdoors, insecure code patterns, and common vulnerability classes such as remote code execution, SQL injection, cross-site scripting, CSRF, unsafe uploads, and privilege issues.
AI verification is optional. The scanner only enables AI support when --ai-key is provided.
- Scans plugin directories and plugin ZIP archives
- Validates WordPress plugin structure before scanning
- Detects PHP and JavaScript malware indicators
- Finds common security flaws in plugin code
- Generates HTML, JSON, Markdown, and PDF reports
- Provides a FastAPI service through
checkwp-api - Includes
mock-malicious-plugin/for local validation
The default scan is designed to be useful even without any external API. It combines:
- Signature-based detection
- Full-file regex matching
- Context-aware false-positive checks
- Composite malware heuristics for obfuscation and payload execution
This improves detection of backdoors and suspicious payloads even when AI is not used.
| Stage | What happens |
|---|---|
| Input validation | CheckWP accepts a plugin folder or ZIP archive and validates that it looks like a WordPress plugin |
| Safe extraction | ZIP archives are extracted with path traversal and symlink protections |
| File discovery | Files are filtered by extension, size, and excluded folders |
| Offline scan | Signatures and heuristics are applied across full file content |
| Optional AI verification | If --ai-key is present, findings can be reviewed by an OpenAI-compatible endpoint |
| Reporting | Results are written as HTML, JSON, Markdown, and PDF |
| Category | Examples |
|---|---|
| Remote code execution | eval(), assert(), create_function(), variable-function execution |
| Command injection | system(), exec(), passthru(), shell_exec(), popen() |
| SQL injection | Unsafe $wpdb queries and raw superglobals inside SQL |
| XSS | Unescaped output, innerHTML, document.write() |
| CSRF | Form and AJAX handlers without nonce verification |
| File issues | Dynamic include/require, unsafe file reads, unsafe uploads |
| Deserialization | unserialize() on user-controlled input |
| Authorization | Public REST routes, weak capability checks |
| Malware indicators | eval(base64_decode()), encoded payloads, exfiltration, hidden admin creation, skimmers |
| Obfuscation | chr() chains, String.fromCharCode(), long encoded blobs, entropy spikes |
pipx install checkwppip install checkwpbrew tap thaikolja/checkwp
brew install checkwpgit clone https://gitlab.com/thaikolja/checkwp.git
cd checkwp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"checkwp ./my-plugin
checkwp ./my-plugin.zip
checkwp ./my-plugin --deep -o report.html --no-open
checkwp ./mock-malicious-plugin --deep --no-opencheckwp <path> [options]
| Option | Description |
|---|---|
-o, --output PATH |
Output file path |
-f, --format {html,json} |
Report format |
--stdout |
Print report to stdout |
--no-open |
Do not auto-open HTML reports |
-s, --severity {critical,high,medium,low} |
Minimum severity to report |
--deep |
Enable deeper offline heuristics and entropy-based checks |
--quick |
Restrict output to high and critical findings |
-t, --threads N |
Number of worker threads |
--max-file-size KB |
Maximum file size to scan |
--context-lines N |
Context lines to include around findings |
--exclude NAME |
Exclude a directory name; repeatable |
--include-ext EXT |
Add extra file extensions; repeatable |
--php-only |
Scan PHP-like files only |
--js-only |
Scan JavaScript / TypeScript-like files only |
--no-banner |
Hide the startup banner |
--no-color |
Disable colored output |
-V, --version |
Show version |
| Option | Description |
|---|---|
--ai-key KEY |
Enables AI verification |
--ai-endpoint URL |
OpenAI-compatible base URL. If omitted, OpenAI is used by default |
--ai-model MODEL |
Model name for AI verification |
--ai-temperature FLOAT |
Sampling temperature |
Important:
--ai-endpointaccepts a full OpenAI-compatible URL only- No provider aliases are used
- If
--ai-endpointis omitted, CheckWP useshttps://api.openai.com/v1
OpenAI:
checkwp ./my-plugin \
--ai-key "sk-..." \
--ai-model "gpt-4o"DeepSeek with explicit OpenAI-compatible URL:
checkwp ./my-plugin \
--ai-key "sk-..." \
--ai-endpoint "https://api.deepseek.com/v1" \
--ai-model "deepseek-chat"Generic OpenAI-compatible endpoint:
checkwp ./my-plugin \
--ai-key "token" \
--ai-endpoint "https://example.com/v1" \
--ai-model "my-model"Start the API locally:
checkwp-apiDefault local address:
http://127.0.0.1:8000
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/ |
Service index |
GET |
/health |
Health check |
GET |
/version |
API version |
POST |
/scan |
Upload and scan a plugin ZIP |
GET |
/static/reports/{id}.html |
Access saved HTML reports |
| Provider | Base URL for --ai-endpoint or ai_endpoint |
|---|---|
| OpenAI | https://api.openai.com/v1 |
| DeepSeek | https://api.deepseek.com/v1 |
| OpenRouter | https://openrouter.ai/api/v1 |
| Groq | https://api.groq.com/openai/v1 |
| Together | https://api.together.xyz/v1 |
curl -X POST "http://127.0.0.1:8000/scan?format=json" \
-F "file=@./my-plugin.zip"curl -X POST "http://127.0.0.1:8000/scan?format=json&ai_key=sk-...&ai_endpoint=https://api.deepseek.com/v1&ai_model=deepseek-chat" \
-F "file=@./my-plugin.zip"| Format | Use case |
|---|---|
| HTML | Manual review, sharing, and browser-based triage |
| JSON | CI/CD, scripting, and automation |
| Markdown | Lightweight text reports |
| Portable report handoff |
When HTML output is generated, CheckWP also writes companion .md and .pdf files.
The repository includes mock-malicious-plugin/, a deliberately vulnerable sample plugin that contains:
- SQL injection
- XSS
- unsafe redirects
- unsafe uploads
- public REST routes
- hidden admin creation
- obfuscated PHP payloads
- suspicious JavaScript payloads
Run it locally to validate detection quality:
checkwp ./mock-malicious-plugin --deep --no-openpytest
ruff check .
mypy src/
python -m buildpython -m build
python -m twine check dist/*
python -m twine upload dist/*After publishing to PyPI:
pipx install checkwpUpdate homebrew/checkwp.rb with the SHA256 of the published PyPI source tarball.
Related files:
homebrew/HOMEBREW.md.github/workflows/release.yml
| Field | Value |
|---|---|
| Name | Kolja Nolte |
| Website | https://checkwp.org |
kolja.nolte@gmail.com |
|
| GitLab | https://gitlab.com/thaikolja/checkwp |
| GitHub mirror | https://github.com/thaikolja/checkwp |
MIT. See LICENSE.