Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .audit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ policy:

# Documented exceptions with business justification
# Format: CVE-XXXX-XXXXX or GHSA-xxxx-xxxx-xxxx
exceptions: {}
exceptions:
GHSA-gv7w-rqvm-qjhr:
package: esbuild
reason: "esbuild vulnerability affects Deno module only; SecuScan uses esbuild via Vite for bundling in Node.js context. Fix requires Vite 8.x breaking upgrade."
expires_at: "2026-08-31"

# Packages to exclude from audits (use sparingly!)
excluded_packages: []
Expand Down
13 changes: 11 additions & 2 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This file is a human-readable index of the plugins currently present in `plugins/*/metadata.json`.

Last synced: 2026-05-11
Last synced: 2026-06-09

## At a Glance

Expand Down Expand Up @@ -100,7 +100,7 @@ Only run scans against systems you own or are explicitly authorized to assess.
| WordPress Security Scan | `wpscan` | `vulnerability` | `intrusive` | `wpscan` | WordPress security scanner for plugin, theme, and core risk visibility. |
| XSS Exploiter | `xss_exploiter` | `exploit` | `exploit` | `python3` | Exploit XSS in real-life attacks to extract cookies and data. |
| Binary Signature Scan | `yara_scan` | `forensics` | `intrusive` | `yara` | Binary and file-system signature matching with YARA rules. |
| DAST Web Proxy (ZAP) | `zap_scanner` | `vulnerability` | `exploit` | `python3` | Dynamic proxy spidering and payload injection. |
| DAST Web Proxy (ZAP) | `zap_scanner` | `vulnerability` | `exploit` | `python3` | Dynamic application security testing via OWASP ZAP Docker container. |

### SQL Injection Plugin Guidance

Expand Down Expand Up @@ -341,3 +341,12 @@ Two additional lint checks help maintain high-quality plugin metadata:
Existing plugins can be brought into compliance incrementally — the help
text check is a non-blocking warning, and unknown categories cause a
clear error message identifying the problem.

## Runtime Dependencies

Some plugins require external runtimes beyond standard CLI binaries. The
following table documents these special requirements:

| Plugin | Dependency | Notes |
| --- | --- | --- |
| `zap_scanner` | Docker | Launches OWASP ZAP via `ghcr.io/zaproxy/zaproxy:stable`. Docker must be installed on the host and the current user must have permission to run containers. |
30 changes: 15 additions & 15 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 9 additions & 11 deletions plugins/zap_scanner/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,22 @@
"id": "zap_scanner",
"name": "DAST Web Proxy (ZAP)",
"version": "1.0.0",
"description": "Dynamic proxy spidering and payload injection.",
"long_description": "Dynamic proxy spidering and payload injection.",
"description": "Dynamic application security testing via OWASP ZAP Docker container.",
"long_description": "Runs OWASP ZAP in a Docker container to perform automated DAST scans against web applications. Supports full spidering, active scanning, and AJAX crawling for JavaScript-heavy apps. Generates HTML and JSON reports with categorized findings.\n\nRequires Docker to be installed on the host machine.",
"category": "vulnerability",
"author": {
"name": "SecuScan Contributors",
"email": "dev@secuscan.local"
},
"license": "MIT",
"icon": "🛠️",
"implementation_status": "integrated",
"supports_authenticated_crawling": true,
"supports_session_reuse": true,
"engine": {
"type": "cli",
"binary": "python3"
},
"command_template": [
"python3",
"-c",
"import sys;print('ZAP connector placeholder scan');print('target='+sys.argv[1]);print('mode=dast')",
"plugins/zap_scanner/run.py",
"{target}"
],
"fields": [
Expand All @@ -30,11 +26,12 @@
"label": "Target URL",
"type": "string",
"required": true,
"placeholder": "https://secuscan.in",
"placeholder": "https://example.com",
"validation": {
"pattern": "^https?://",
"message": "Must be a valid HTTP(S) URL"
}
},
"help": "The full URL (including scheme) of the web application to scan."
}
],
"presets": {
Expand All @@ -47,7 +44,7 @@
"safety": {
"level": "exploit",
"requires_consent": true,
"consent_message": "DAST payload injection should only run in authorized environments.",
"consent_message": "DAST payload injection should only run in authorized environments. Active scanning may disrupt target services.",
"rate_limit": {
"max_per_hour": 20,
"max_concurrent": 1
Expand All @@ -62,10 +59,11 @@
],
"dependencies": {
"binaries": [
"docker",
"python3"
],
"python_packages": [],
"system_packages": []
},
"checksum": "6546eca4549d40f188c84e9be46b7e992aa6a025d25a9e7ba1e2420387108819"
"checksum": "4b57127fff0b0ef3f667658a02673e8f47bd87ea0ee8296e9126c372f138b088"
}
49 changes: 29 additions & 20 deletions plugins/zap_scanner/parser.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
import json
from typing import Any, Dict, List

SEVERITY_MAP = {
"high": "high",
"critical": "critical",
"medium": "medium",
"low": "low",
"info": "info",
}


def parse(output: str) -> Dict[str, Any]:
lines = [line.strip() for line in output.splitlines() if line.strip()]
findings: List[Dict[str, Any]] = []
try:
data = json.loads(output)
except (json.JSONDecodeError, ValueError):
return {"findings": [], "count": 0, "items": []}

for line in lines[:300]:
severity = "info"
low_line = line.lower()
if any(keyword in low_line for keyword in ["open", "found", "vuln", "warning", "detected", "exposed"]):
severity = "low"
if any(keyword in low_line for keyword in ["critical", "exploit", "injection", "compromised"]):
severity = "high"
raw_findings: List[Dict[str, Any]] = data.get("findings", data.get("items", []))
findings: List[Dict[str, Any]] = []

findings.append(
{
"title": "Recon/Scan Observation",
"category": "Security Scan",
"severity": severity,
"description": line,
"remediation": "Review scan output and validate findings before remediation planning.",
"metadata": {"raw": line},
}
)
for item in raw_findings:
if not isinstance(item, dict):
continue
severity_raw = item.get("severity", "info") or "info"
severity = SEVERITY_MAP.get(severity_raw.lower(), "info")
findings.append({
"title": item.get("title", "Security Observation"),
"category": "DAST",
"severity": severity,
"description": item.get("description", ""),
"remediation": item.get("remediation", "Review scan output and validate findings before remediation."),
"metadata": item.get("metadata", {}),
})

return {
"findings": findings,
"count": len(findings),
"items": lines[:300],
"items": raw_findings,
}
103 changes: 103 additions & 0 deletions plugins/zap_scanner/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import json
import sys
import tempfile
import subprocess
import os
import shutil


def main():
if len(sys.argv) < 2:
print(json.dumps({"error": "No target URL provided", "findings": [], "count": 0}))
sys.exit(1)

target = sys.argv[1]
work_dir = tempfile.mkdtemp(prefix="zap_scan_")
json_path = os.path.join(work_dir, "output.json")

try:
cmd = [
"docker", "run", "--rm",
"-v", f"{work_dir}:/zap/wrk:rw",
"-t", "ghcr.io/zaproxy/zaproxy:stable",
"zap-full-scan.py",
"-t", target,
"-j", "-J", "/zap/wrk/output.json",
]

result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=600,
)

if result.returncode != 0:
err_msg = (result.stderr or "").strip() or f"ZAP process exited with code {result.returncode}"
print(json.dumps({
"error": f"ZAP scan failed: {err_msg}",
"findings": [],
"count": 0,
"stderr": result.stderr[:2000],
}))
sys.exit(1)

findings = []
if os.path.exists(json_path):
with open(json_path) as f:
zap_output = json.load(f)
if isinstance(zap_output, list):
findings = zap_output
elif isinstance(zap_output, dict):
site = zap_output.get("site", [])
if isinstance(site, list):
for s in site:
alerts = s.get("alerts", [])
for alert in alerts:
findings.append({
"title": alert.get("name", "Unknown"),
"severity": alert.get("riskdesc", "info").split(" ")[0].lower(),
"description": alert.get("desc", ""),
"remediation": alert.get("solution", ""),
"metadata": {
"url": alert.get("url", ""),
"param": alert.get("param", ""),
"cweid": alert.get("cweid", ""),
},
})

output = {
"findings": findings,
"count": len(findings),
"items": findings,
"stderr": result.stderr[:2000],
}
print(json.dumps(output))

except subprocess.TimeoutExpired:
print(json.dumps({
"error": "ZAP scan timed out after 10 minutes",
"findings": [],
"count": 0,
}))
sys.exit(1)
except FileNotFoundError:
print(json.dumps({
"error": "Docker not found. Ensure Docker is installed and in PATH.",
"findings": [],
"count": 0,
}))
sys.exit(1)
except Exception as e:
print(json.dumps({
"error": str(e),
"findings": [],
"count": 0,
}))
sys.exit(1)
finally:
shutil.rmtree(work_dir, ignore_errors=True)


if __name__ == "__main__":
main()
Loading
Loading