A static-analysis tool that flags dangerous PHP patterns — RCE, command/SQL injection,
LFI/RFI, insecure deserialization, hardcoded secrets, and best-practice issues — in a
single file, a whole folder, or an entire .zip project. Every finding shows the
exact file, line, and column, the offending code highlighted, and how to fix it.
It runs two ways from the same detection engine (scanner-core.js):
- Browser only — open
index.html. Everything (including ZIP extraction) happens locally in your browser. No server, no upload, works offline. - With the backend — run
npm startand open the printed URL. Scanning happens on a small Node server so the UI stays responsive on large projects. The page detects the backend automatically and shows a Server / Local badge you can toggle.
- Quick start
- What it detects
- How to use — step by step
- Try it now with the sample files
- Local vs Server mode
- Backend API
- Testing
- Configuration
- Troubleshooting
- Limitations & disclaimer
- Project layout
Just open the file in any modern browser:
double-click index.html
Keep index.html and scanner-core.js in the same folder — the page loads the engine
from that file. The badge in the top-right will read Local.
Requires Node 18+ (no packages to install — the server uses only built-in modules):
cd php-scanner
npm start
# PHP Danger Scanner · core v2.0.0 · 73 rules
# Running at http://127.0.0.1:3000Open http://127.0.0.1:3000. The badge will read Server.
12 categories, 73 rules, graded into four severities:
| Severity | Meaning | Example patterns |
|---|---|---|
| 🔴 Critical | Almost certainly exploitable / a backdoor | eval($_POST[...]), system(...), dynamic include, unserialize($_COOKIE), AWS keys, private keys |
| 🟠 High | Serious risk, needs review | shell_exec, deprecated mysql_*, arbitrary file write/delete, weak md5() password hashing, hardcoded credentials |
| 🟡 Medium | Risky depending on context | permissive chmod 0777, open redirect, strcmp() auth, error_reporting(0), phpinfo() |
| 🔵 Low | Best-practice / code-quality smells | var_dump() left in, $_REQUEST, loose ==, short open tags, TODO/FIXME, goto |
Categories: Remote Code Execution, Command Injection, SQL Injection, XSS, File Inclusion, Dangerous File Ops, Deserialization, Upload, Auth/Session, Hardcoded Secrets, Best Practices, Misc Dangerous.
The scanner is comment-aware (string-safe): dangerous-looking code inside //, #,
or /* ... */ comments is ignored, which greatly reduces false positives.
The left panel gives you four ways to load code. Use whichever fits:
- Whole
.zip— drag a.ziponto the drop zone (or click Files / .zip). Every PHP-like file inside is extracted and scanned automatically. Best for a whole project. - Whole folder — click Whole Folder and pick a directory; the full tree is loaded.
- Individual files — select one or more
.php,.phtml,.inc,.module, etc. - Paste code — paste a snippet directly into the “Or Paste Code Directly” box.
Scannable extensions: .php .phtml .inc .module .php5 .php7 .phps .php3 .php4 .ctp .tpl.
Loaded files appear in a list with sizes and a Clear all button.
- Scan Categories — toggle which vulnerability classes you care about. (You can also change these after a scan — see live filtering.)
- Scan Options
- Ignore comments (on) — skip matches inside comments.
- Skip vendor / node_modules (on) — ignore
vendor/,node_modules/,.git/, etc. - Scan on server (shown only when the backend is running) — run the scan on the server instead of in the browser.
Click Start Security Scan. A progress bar shows files scanned / percentage. Large projects stay responsive (the browser scan yields on a time budget; the server scan runs off the UI thread entirely).
The results panel is organised top-to-bottom:
- Verdict banner — a one-line takeaway (red if any critical/high issues exist).
- Severity stats — counts of Critical / High / Medium / Low.
- Most dangerous files — files ranked by a weighted risk score. Click a file to jump to and filter its findings. This is the fastest way to "find the dangerous file".
- Findings list — grouped by file. Each card shows the title, line & column, and a
severity tag. Click a card to expand:
- a plain-language description of the risk,
- the code context with the exact match highlighted,
- a Remediation box telling you how to fix it,
- the category.
- Severity filter — show only Critical / High / Medium / Low (or All).
- Search box — type to filter findings by title, file path, or matched code.
- Click a file in the overview to see only that file’s findings (Clear file filter to reset).
- Live category toggles — turning a Scan Category off/on in the left panel updates the results instantly, with no re-scan.
- Expand / Collapse — open or close all finding cards at once.
Click Export Report (top-right) and choose a format:
| Format | Use it for |
|---|---|
| HTML | A shareable, self-contained report you can open in any browser or print to PDF. |
| JSON | Feeding results into other tools / CI. |
| CSV | Opening in Excel / Google Sheets. |
| TXT | A quick plain-text summary. |
Re-scan hint: options that change what is detected (Ignore comments, Skip vendor, editing pasted code, adding/removing files) can’t update live. When you change one after a scan, the button turns into “Re-scan to apply changes.” Category toggles, search, and severity filters always update instantly.
Two ready-made fixtures are included so you can confirm everything works:
test.php— deliberately vulnerable, covering every category. It also contains a docblock witheval(...)/system(...)inside a comment to prove comment-awareness.test2.php— a mix of safe code (a prepared PDO statement,password_hash,htmlspecialchars) and real problems, to show it flags issues without over-flagging good code.
How to test: open the app, click Files / .zip (or drag the files in), select
test.php and test2.php, then Start Security Scan.
Expected results (with default options):
| File | Findings | Critical | High | Medium | Low |
|---|---|---|---|---|---|
test.php |
42 | 15 | 10 | 9 | 8 |
test2.php |
15 | 1 | 4 | 2 | 8 |
You should also see that the eval/system inside the comment block in test.php
are not reported, and that test2.php’s prepared PDO query is not flagged as SQL
injection. (Exact numbers may shift slightly as rules evolve.)
You can also verify from the command line without the UI:
node -e "const C=require('./scanner-core.js'),fs=require('fs');\
for(const f of ['test.php','test2.php']){\
const {findings}=C.scanAll([{name:f,content:fs.readFileSync(f,'utf8')}],{skipComments:true});\
console.log(f, findings.length, 'findings');}"| Local (badge: 💻) | Server (badge: 🖥️) | |
|---|---|---|
| How | Open index.html directly |
npm start, open the URL |
| Where scanning runs | In your browser | On the Node backend |
| Network | None — fully offline | localhost only |
| Best for | Quick checks, no setup | Large projects / many files |
The page auto-detects the backend on load. If the server is running you’ll get a Scan on server toggle (on by default); if it isn’t reachable, the app silently uses Local mode. If a server scan ever fails mid-run, it automatically falls back to a local scan.
All responses are JSON. Base URL defaults to http://127.0.0.1:3000.
| Method & path | Body | Returns |
|---|---|---|
GET /api/health |
— | { ok, version, categories, rules } |
POST /api/scan |
{ "files": [{ "name", "content" }], "options": { "skipComments" } } |
scan result |
POST /api/scan/zip |
raw .zip bytes (Content-Type: application/zip) |
scan result |
A scan result. Each finding nests its rule (severity, category, title,
description, remediation) and carries the surrounding context lines so a report
can show the code:
{
"ok": true,
"engine": "server",
"filesScanned": 2,
"filesAffected": 2,
"totalFindings": 57,
"capped": false,
"findings": [
{
"file": "test.php",
"line": 16,
"col": 1,
"match": "eval(",
"matchLine": 2,
"matchIndex": 0,
"matchLen": 5,
"context": [
{ "lineNum": 14, "text": "// ---- Remote Code Execution ----" },
{ "lineNum": 16, "text": "eval($_POST['code']);" }
],
"rule": {
"severity": "critical",
"category": "rce",
"title": "eval() — Direct Code Execution",
"description": "eval() executes arbitrary PHP code…",
"remediation": "Never use eval()…"
}
}
]
}Read a finding's severity as finding.rule.severity and its location as
finding.file / finding.line / finding.col.
Examples:
# Health
curl -s http://127.0.0.1:3000/api/health
# Scan a zipped project and print the finding count
curl -s -X POST http://127.0.0.1:3000/api/scan/zip \
-H 'Content-Type: application/zip' --data-binary @project.zip | jq .totalFindings
# Scan a single file's contents as JSON
curl -s -X POST http://127.0.0.1:3000/api/scan \
-H 'Content-Type: application/json' \
-d "{\"files\":[{\"name\":\"a.php\",\"content\":\"<?php eval(\$_POST[1]);\"}]}" | jq .totalFindingsnpm testRuns 31 checks across three areas: the shared rule engine (rule integrity, detection,
comment handling, sorting), the Node ZIP extractor, and a live end-to-end pass that
boots the server and exercises /api/health, /api/scan, /api/scan/zip, static hosting,
and error handling.
Environment variables for the server:
| Variable | Default | Purpose |
|---|---|---|
PORT |
3000 |
Port to listen on |
HOST |
127.0.0.1 |
Interface to bind |
PORT=8080 HOST=0.0.0.0 npm startBuilt-in safety limits: request body cap 120 MB, per-file cap 3 MB, and a scan cap of 20,000 findings for pathological inputs.
- Badge stuck on “Local” while the server is running. Make sure you opened the app via
the server URL (
http://127.0.0.1:3000) and not by double-clicking the file — a page opened fromfile://cannot reach the backend, so it stays Local by design. - “Could not load scanner-core.js.”
scanner-core.jsmust sit next toindex.html. - ZIP won’t open / “ZIP64 not supported.” Very large or encrypted archives aren’t supported; re-zip without encryption, or upload the folder instead.
- A big file was skipped. Individual files over 3 MB are skipped by design.
- Port already in use. Start with a different port:
PORT=8080 npm start.
This is a pattern-based scanner: fast and broad, but it does not perform full
data-flow analysis, so it can produce false positives and miss context-specific bugs.
Treat findings as leads, not proof, and always pair it with manual review. The bundled
test.php / test2.php contain intentionally dangerous code for demonstration only —
never deploy them.
| File | Purpose |
|---|---|
index.html |
Frontend UI (loads the shared engine). |
scanner-core.js |
Shared detection rules + scan engine (UMD: browser and Node). |
server.js |
Zero-dependency Node HTTP server: static hosting + scan API. |
lib/unzip.js |
Server-side ZIP extraction (built-in zlib). |
test/run-tests.mjs |
Test suite (core + unzip + live server). |
test.php, test2.php |
Sample vulnerable files for a quick demo. |
The browser and the server share one rule set in scanner-core.js, so results are
identical no matter where a scan runs.
MIT — see LICENSE.