{"id":51280909,"url":"https://github.com/dimasma0305/wp-taint-scan","last_synced_at":"2026-06-30T01:32:19.096Z","repository":{"id":362891119,"uuid":"1261079984","full_name":"dimasma0305/wp-taint-scan","owner":"dimasma0305","description":"Go static taint-analysis engine that finds vulnerabilities in WordPress plugins — WordPress-aware (capability tiers, nonce≠authz, REST/AJAX entrypoints). Detects SQLi, XSS, IDOR, privesc, RCE. Built on php-parser-go.","archived":false,"fork":false,"pushed_at":"2026-06-06T13:03:39.000Z","size":4304,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T13:05:51.156Z","etag":null,"topics":["appsec","bug-bounty","golang","php","sast","security","security-tools","static-analysis","taint-analysis","vulnerability-scanner","wordpress","wordpress-plugin"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dimasma0305.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T07:56:42.000Z","updated_at":"2026-06-06T13:03:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dimasma0305/wp-taint-scan","commit_stats":null,"previous_names":["dimasma0305/wp-taint-scan"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dimasma0305/wp-taint-scan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimasma0305%2Fwp-taint-scan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimasma0305%2Fwp-taint-scan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimasma0305%2Fwp-taint-scan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimasma0305%2Fwp-taint-scan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dimasma0305","download_url":"https://codeload.github.com/dimasma0305/wp-taint-scan/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimasma0305%2Fwp-taint-scan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34949234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appsec","bug-bounty","golang","php","sast","security","security-tools","static-analysis","taint-analysis","vulnerability-scanner","wordpress","wordpress-plugin"],"created_at":"2026-06-30T01:32:18.461Z","updated_at":"2026-06-30T01:32:19.068Z","avatar_url":"https://github.com/dimasma0305.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"docs/banner.png\" alt=\"wp-taint-scan\" width=\"780\"\u003e\n\n\u003cp\u003e\u003cb\u003eFind real vulnerabilities in WordPress plugins.\u003c/b\u003e Search the plugin directory, scan any version — or every version — in parallel, and diff findings across releases. A native Go taint-analysis engine that understands the \u003ci\u003eWordPress security model\u003c/i\u003e, not just generic source→sink flow.\u003c/p\u003e\n\n[![Go](https://img.shields.io/badge/Go-1.25+-00ADD8?logo=go\u0026logoColor=white)](https://go.dev)\n[![CI](https://github.com/dimasma0305/wp-taint-scan/actions/workflows/ci.yml/badge.svg)](https://github.com/dimasma0305/wp-taint-scan/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-7c5cff.svg)](https://github.com/dimasma0305/wp-taint-scan/pulls)\n![Dependencies](https://img.shields.io/badge/dependencies-stdlib%20only-22c55e.svg)\n\n[Quick start](#install--build) · [Web UI](#web-ui) · [How it works](#why-its-different) · [HTTP API](#http-api)\n\n\u003csub\u003e⭐ If this is useful, a star helps others find it.\u003c/sub\u003e\n\n\u003c/div\u003e\n\n---\n\n## Screenshots\n\n|  |  |\n|:--:|:--:|\n| **Search the WordPress.org directory** | **Pick a version, or scan them all** |\n| [![Discover](docs/screenshots/01-discover.png)](docs/screenshots/01-discover.png) | [![Versions](docs/screenshots/02-versions.png)](docs/screenshots/02-versions.png) |\n| **Parallel scan dashboard (live)** | **Findings + source→sink dataflow trace** |\n| [![Jobs](docs/screenshots/03-jobs.png)](docs/screenshots/03-jobs.png) | [![Findings](docs/screenshots/04-findings.png)](docs/screenshots/04-findings.png) |\n\n**Version diff** — see exactly which findings were *introduced* or *fixed* between two releases:\n\n[![Diff](docs/screenshots/05-diff.png)](docs/screenshots/05-diff.png)\n\n```\nplugin source ──▶ php-parser-go ──▶ call graph + taint propagation ──▶ findings\n                                     (WordPress-aware authorization context)\n```\n\n\u003e The analysis engine ships as a CLI (`taint-scan`, installed as `phparser`) and a web app (`taint-web`). No PHP runtime, no Semgrep, no external services — just Go.\n\n## Why it's different\n\nGeneric taint scanners drown in false positives on WordPress code because they don't model how WordPress actually gates access. This engine encodes the real rules that decide whether a finding is exploitable:\n\n- **A nonce is not authorization.** A valid nonce only proves the request came from the site's UI; it does not prove the user is allowed to perform the action. Nonce-without-capability is *the* dominant WordPress bounty pattern, and the engine treats it as such.\n- **`is_admin()` is not an auth gate.** It checks whether the URL is under `/wp-admin/` — and `admin-ajax.php` always returns `true`. It is never treated as a capability check.\n- **Capability tiers matter.** `current_user_can('read')` (subscriber/customer/student) is *authenticated*, not *privileged*. Missing-authorization bugs reachable by low-privilege users are surfaced and ranked above admin-only self-XSS.\n- **Numeric sanitizers stop injection, not IDOR.** `intval()`/`absint()` make a value injection-safe but leave it attacker-controlled for resource selection (delete/action/disclosure) — so those flows stay tainted.\n\n## Vulnerability classes detected\n\n- **SQL injection** — `$wpdb` raw queries and non-`$wpdb` drivers (`mysqli_*`, PDO, `pg_query`, `sqlsrv_query`, `multi_query`), with `prepare()` parameterization and identifier-injection modeling.\n- **Reflected \u0026 stored XSS** — unescaped request input reaching output, with a broad escaper/sanitizer model (`esc_html`/`esc_attr`/`esc_url`/`esc_js`, `json_encode`, attribute helpers, numeric casts) and format-aware `printf`/`vprintf`/`wp_die` sinks.\n- **Path traversal / arbitrary file read, write, delete, include** — file-system sinks with path-safety modeling and zip-slip (`ZipArchive::extractTo`, `unzip_file`).\n- **Missing-authorization / IDOR** — sensitive actions, file delete/upload, and record-read-to-output reachable without a capability check.\n- **Privilege escalation** — tainted writes to `wp_capabilities`/user-meta, `grant_super_admin`, role/capability mutation.\n- **Open redirect \u0026 header injection** — request-controlled redirect targets and response headers.\n- **Dynamic-dispatch RCE** — request-controlled callable/method/class names (`$fn()`, `$o-\u003e$m()`, `Class::$m()`, `new $c()`).\n- **REST/AJAX exposure surfaces** — endpoints with public or missing permission callbacks reaching sensitive sinks.\n\nAnalysis runs as independent **sink-op batches** (`delete`, `read`, `open`, `include`, `write`, `output`, `sql`, `action`, `call`) so each detector has its own relevance/scoring and one detector can't perturb another's results.\n\n## Install\n\nPick whichever is easiest — no Go toolchain needed for the first three.\n\n**1. One-line installer** (Linux / macOS, downloads the prebuilt binary):\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/dimasma0305/wp-taint-scan/main/scripts/install.sh | sh\n```\n\n**2. Download a prebuilt binary** for your OS/arch from the\n[**Releases**](https://github.com/dimasma0305/wp-taint-scan/releases/latest) page\n(Linux/macOS `.tar.gz`, Windows `.zip` — each contains `taint-web` + `taint-scan`), unpack, and run.\n\n**3. Docker:**\n\n```bash\ndocker run --rm -p 8080:8080 ghcr.io/dimasma0305/wp-taint-scan\n# then open http://localhost:8080\n```\n\n**4. With Go** (1.25+):\n\n```bash\ngo install github.com/dimasma0305/wp-taint-scan/cmd/taint-web@latest   # web UI\ngo install github.com/dimasma0305/wp-taint-scan/cmd/taint-scan@latest  # CLI scanner\n```\n\n**5. From source:**\n\n```bash\ngit clone https://github.com/dimasma0305/wp-taint-scan\ncd wp-taint-scan\ngo build -o bin/taint-web ./cmd/taint-web\n```\n\nThen start the web UI and open \u003chttp://localhost:8080\u003e:\n\n```bash\ntaint-web            # (or ./bin/taint-web from a source build)\n```\n\n## Usage\n\n```bash\n# Scan a plugin directory\n./bin/phparser -target /path/to/plugins/some-plugin -output-dir /tmp/scan\n\n# Key flags\n#   -target         plugin or source directory to scan\n#   -output-dir     where to write results (defaults to a timestamped tmp dir)\n#   -mem-limit-mb   soft heap ceiling (default 6144); aborts in-flight analysis under pressure\n#   -phparser-workers / -max-passes   parallelism \u0026 fixpoint tuning\n```\n\nEach scan writes:\n\n| File | Contents |\n|---|---|\n| `taint-results.json` | Machine-readable findings (rule id, message, source→sink trace, access tier). |\n| `human-summary.md` | Findings ranked by exploitability (unauth/low-priv first). |\n| `README.md` | Run metadata. |\n\n## Web UI\n\n`taint-web` is a self-contained web app for discovering and scanning plugins straight from the WordPress.org directory — search by name, browse every released version, and scan one version or a whole batch **in parallel**.\n\n```bash\ngo build -o bin/taint-web ./cmd/taint-web\n./bin/taint-web                 # http://localhost:8080\n# flags: -addr -cache-dir -concurrency -mem-limit-mb -hard-cap-mb -timeout\n```\n\nWhat it gives you:\n\n- **Search** the WordPress.org plugin directory by name (installs, rating, description).\n- **Version picker** — every released version, newest-first; scan the latest, a multi-selected subset, or **all versions** as one batch.\n- **Parallel scanning** — a bounded worker pool runs N versions at once; live progress via Server-Sent Events.\n- **Findings viewer** — grouped by severity (derived from the WordPress access tier: `unauthenticated`→critical, low-priv→high, nonce-only→medium, capability-checked→low), each with the source→sink dataflow trace and code snippets. Filter by severity, export per scan as JSON or Markdown.\n- **Version diff** — compare two scanned versions to see which findings were **introduced** or **fixed** (great for pinpointing the version that added or patched a bug).\n- **Result + download caching** — re-scanning a version is instant; downloaded zips are reused.\n\n### Isolation \u0026 safety\n\nEvery scan runs in a **separate child process** (`taint-web -scan-worker …`) — the same binary re-invokes itself — with a soft heap ceiling (`-mem-limit-mb`), a hard RSS watchdog (`-hard-cap-mb`, kills + marks the job *skipped*), and a wall-clock `-timeout`. A pathological mega-plugin therefore can never OOM or crash the server; only its own worker dies. Downloads are size-capped, zip extraction is **zip-slip-safe** with uncompressed-size/file-count limits, and all plugin/version inputs are strictly validated before they touch a URL or a filesystem path. The server only ever talks to the hardcoded `api.wordpress.org` / `downloads.wordpress.org` hosts (no SSRF), and the UI renders all untrusted plugin data as text (no HTML injection from malicious plugin code in snippets).\n\n### HTTP API\n\n| Endpoint | Purpose |\n|---|---|\n| `GET /api/search?q=` | search plugins by name |\n| `GET /api/plugin?slug=` | plugin info + sorted versions + which are already scanned |\n| `POST /api/scan` | `{slug, name, versions[], mode:\"selected\\|latest\\|all\", force}` → enqueue jobs |\n| `GET /api/jobs[?batch=]` | list jobs (optionally one batch) |\n| `GET /api/job?id=` | one job with findings |\n| `GET /api/job/export?id=\u0026format=json\\|md` | download a report |\n| `POST /api/cancel?id=` | cancel a queued/running job |\n| `GET /api/diff?slug=\u0026a=\u0026b=` | finding delta between two scanned versions |\n| `GET /api/stats` | aggregate counters |\n| `GET /api/events` | Server-Sent Events stream of job updates |\n\n## Mass-scanning safely (memory)\n\nThe engine's peak held memory is small (~0.2–0.7 GB on normal plugins), but a handful of\nmega-plugins (WooCommerce/Elementor-scale) can trigger a transient interprocedural-instantiation\nallocation burst that outpaces the garbage collector. **Go cannot abort an allocation\nmid-flight**, so `-mem-limit-mb` reduces — but cannot *guarantee* — no OOM.\n\nFor scanning a whole plugin tree, use the watchdog as the outer loop:\n\n```bash\n./scan-capped.sh /path/to/plugin /tmp/out 10   # hard 10 GB RSS cap\n# exit 0 = scanned, 75 = skipped (exceeded cap, host protected), other = phparser's exit\n```\n\n`scan-capped.sh` runs the scan under a hard RSS ceiling and kills + skips a pathological\nplugin instead of crashing the host. See [`docs/EFFECTIVENESS_AUDIT_2026-06.md`](docs/EFFECTIVENESS_AUDIT_2026-06.md)\nand [`docs/OPTIMIZATION_PLAN.md`](docs/OPTIMIZATION_PLAN.md) for the full memory analysis.\n\n## Validation\n\nThe engine is validated against a **72-case real-CVE corpus** (57 with locally reproduced\nplugin fixtures). The comparison harness lives in `test/semgrep_bundle_corpus/`:\n\n```bash\n# Fetch the vulnerable-plugin fixtures (large; not committed)\npython3 test/semgrep_bundle_corpus/download_corpus_plugins.py\n\n# Run the engine against the corpus and diff expected vs. found\ngo run ./cmd/corpus-compare\n```\n\nThe committed `corpus.json` is the manifest of CVEs and expected findings; the plugin\nfixtures (~685 MB) are fetched on demand. Unit/regression tests:\n\n```bash\ngo test ./...\n```\n\n## Repository layout\n\n```\ncmd/\n  taint-scan/       main vulnerability scanner CLI  (binary: phparser)\n  corpus-compare/   corpus validation harness\n  lower-bundle/     PHP bundle lowering (legacy Semgrep migration path)\n  semgrep-target/   native Semgrep target/orchestration wrapper\n  semgrep-bundle/\ninternal/\n  taintscan/        the taint-analysis engine (sources, sinks, sanitizers,\n                    WordPress context, call graph, propagation)  — ~70k LOC\n  corpuscompare/    corpus diff logic\n  lowerbundle/      bundle lowering\nscan-capped.sh      RSS watchdog for safe mass-scanning\ntest/               CVE corpus manifest + fetch/report scripts\ndocs/               effectiveness audits, optimization plans, roadmap\n```\n\n## Dependency\n\nBuilt on **[`php-parser-go`](https://github.com/dimasma0305/php-parser-go)** (a native Go port of nikic/PHP-Parser) for PHP parsing/AST — pulled in as a versioned module, no other third-party dependencies. To hack on both repos at once, side-by-side:\n\n```bash\ngit clone https://github.com/dimasma0305/php-parser-go\ngit clone https://github.com/dimasma0305/wp-taint-scan\ncd wp-taint-scan \u0026\u0026 go work init . ../php-parser-go\n```\n\n## Contributing\n\nIssues and PRs welcome. `go build ./...`, `go test ./...`, and `go vet` should pass; CI runs build + vet + race tests on every PR. No plugin-specific or CVE-specific logic in the engine — detectors must be generic.\n\n## License\n\n[MIT](LICENSE) © Dimas Maulana ([dimasma0305](https://github.com/dimasma0305)). The bundled `php-parser-go` derives from [nikic/PHP-Parser](https://github.com/nikic/PHP-Parser) (BSD-3-Clause).\n\n\u003e Intended for **authorized** security testing, CTF, and defensive research — scan plugins you own or are permitted to test.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimasma0305%2Fwp-taint-scan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdimasma0305%2Fwp-taint-scan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimasma0305%2Fwp-taint-scan/lists"}