Skip to content
Open
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
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ The format is based on Keep a Changelog and this project adheres to Semantic Ver
### Added

* Initial changelog created following the Keep a Changelog format.
* Added backend validator checks in `plugin_validator.py` to enforce that plugin IDs match `^[a-z][a-z0-9_]*$` and correspond to their folder name.
* Added SQL migration script `007_standardize_plugin_ids.sql` to update historical data referencing renamed or duplicate plugin IDs across tasks, findings, crawl runs, asset services, audit logs, presets, and active plugins tables.

### Changed

* None.
* Standardized all plugin IDs to `snake_case` (e.g., `domain_finder`, `google_dorking`, `people_email_discovery`, `port_scanner`, `subdomain_finder`, `url_fuzzer`, `virtual_host_finder`, `website_recon`).
* Updated unit and integration tests to reference the normalized plugin IDs.

### Deprecated

* None.

### Removed

* None.
* Removed the duplicate `waf-detection` plugin (merged references and migrated legacy data to the `waf_detector` plugin).

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ Please match the conventions already used in the repo instead of introducing a n
- Use type hints where they improve clarity
- Keep validation close to request and model boundaries
- Prefer small functions over large, multi-purpose blocks
- Plugins:
- Plugin IDs must use `snake_case` (only lowercase letters, numbers, and underscores). Do not use hyphens.
- The `"id"` field in the plugin's `metadata.json` file must exactly match its directory name (e.g. `plugins/domain_finder/metadata.json` must have `"id": "domain_finder"`).
- Frontend:
- Use TypeScript and functional React components
- Keep component logic readable and avoid unnecessary abstraction
Expand Down
24 changes: 13 additions & 11 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ New contributors should refer to these resources:

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-10

## At a Glance

- Total plugins: 59
- Safe plugins: 26
- Total plugins: 60
- Safe plugins: 27
- Intrusive plugins: 25
- Exploit plugins: 8
- Source of truth: each plugin's `metadata.json`
Expand All @@ -33,7 +33,7 @@ Only run scans against systems you own or are explicitly authorized to assess.

| Category | Count |
| --- | ---: |
| `recon` | 17 |
| `recon` | 18 |
| `vulnerability` | 12 |
| `robots` | 5 |
| `web` | 5 |
Expand All @@ -60,10 +60,10 @@ Only run scans against systems you own or are explicitly authorized to assess.
| Directory Discovery | `dir_discovery` | `web` | `intrusive` | `ffuf` | Discover hidden directories and files on web servers. |
| DNS Reconnaissance | `dns_enum` | `recon` | `safe` | `dnsrecon` | Enumerate DNS records and configurations. |
| dnsx | `dnsx` | `recon` | `safe` | `dnsx` | DNS resolution and wildcard-aware validation at scale. |
| Domain Finder | `domain-finder` | `recon` | `safe` | `amass` | Discover additional domain names of target organization. |
| Domain Finder | `domain_finder` | `recon` | `safe` | `amass` | Discover additional domain names of target organization. |
| Drupal Security Scan | `droopescan` | `vulnerability` | `intrusive` | `droopescan` | Drupal-focused CMS scanner for version and surface enumeration. |
| Payload Fuzzer | `fuzzer` | `robots` | `exploit` | `python3` | Autonomously fuzz target fields with massive dictionaries. |
| Google Hacking | `google-dorking` | `recon` | `safe` | `python3` | Find publicly indexed information about target. |
| Google Hacking | `google_dorking` | `recon` | `safe` | `python3` | Find publicly indexed information about target. |
| Password Recovery Audit | `hashcat` | `expert` | `exploit` | `hashcat` | Password recovery and hash audit workflow. |
| HTTP Inspector | `http_inspector` | `web` | `safe` | `curl` | Inspect HTTP/HTTPS endpoints for headers, cookies, and TLS configuration. |
| HTTP Request Logger | `http_request_logger` | `exploit` | `intrusive` | `httpx` | Handle incoming HTTP requests and record data. |
Expand All @@ -79,8 +79,8 @@ Only run scans against systems you own or are explicitly authorized to assess.
| Network Scanning | `nmap` | `network` | `safe` | `nmap` | Network discovery and port scanning tool. |
| Template Vulnerability Scan | `nuclei` | `web` | `intrusive` | `nuclei` | Fast and customizable vulnerability scanner. |
| Password Auditor | `password_auditor` | `vulnerability` | `intrusive` | `python3` | Discover weak credentials in network services and web apps. |
| People Hunter | `people-email-discovery` | `recon` | `safe` | `theHarvester` | Discover email addresses and social media profiles. |
| Port Scanner | `port-scanner` | `recon` | `intrusive` | `nmap` | Detect open ports and fingerprint services. |
| People Hunter | `people_email_discovery` | `recon` | `safe` | `theHarvester` | Discover email addresses and social media profiles. |
| Port Scanner | `port_scanner` | `recon` | `intrusive` | `nmap` | Detect open ports and fingerprint services. |
| Advanced Network Recon | `scapy_recon` | `network` | `safe` | `python3` | Advanced network probing using Scapy. |
| Secret Scanner | `secret_scanner` | `code` | `safe` | `gitleaks` | Scan directories for hardcoded secrets. |
| Semgrep Scanner | `semgrep_scanner` | `code` | `safe` | `semgrep` | Multi-language static code analysis using Semgrep. |
Expand All @@ -92,18 +92,20 @@ Only run scans against systems you own or are explicitly authorized to assess.
| SQLi Exploiter | `sqli_exploiter` | `exploit` | `exploit` | `sqlmap` | Exploitation-focused workflow for data extraction from confirmed SQL injection findings. |
| SQL Injection Testing | `sqlmap` | `web` | `exploit` | `sqlmap` | Detects SQL injection vulnerabilities and supports controlled database enumeration. |
| SSH Runner | `ssh_runner` | `execution` | `intrusive` | `ssh` | Remote command execution via SSH. |
| Subdomain Finder | `subdomain_finder` | `recon` | `safe` | `subfinder` | Discover subdomains of a domain. |
| Subdomain Scanner | `subdomain_discovery` | `recon` | `safe` | `subfinder` | Enumerate subdomains using passive sources. |
| Subdomain Discovery (Configurable) | `subdomain_discovery` | `recon` | `safe` | `subfinder` | Comprehensive configurable subdomain enumeration via passive sources. Thread count and source coverage tunable via presets. |
| Subdomain Takeover | `subdomain_takeover` | `exploit` | `intrusive` | `subfinder` | Discover dangling DNS entries pointing to external services. |
| Subfinder (Quick) | `subfinder` | `recon` | `safe` | `subfinder` | Quick passive subdomain enumeration with minimal configuration — just provide a root domain. |
| theHarvester | `theharvester` | `recon` | `safe` | `theHarvester` | OSINT collection for emails, domains, and hosts. |
| TLS Security Analysis | `tls_inspector` | `security` | `safe` | `openssl` | Examine TLS/SSL certificates and cipher configurations. |
| Uncover | `uncover` | `recon` | `safe` | `uncover` | Discover internet-exposed assets from external search sources. |
| URL Fuzzer | `url-fuzzer-2` | `recon` | `intrusive` | `ffuf` | Discover hidden files and directories. |
| URL Fuzzer | `url_fuzzer` | `recon` | `intrusive` | `ffuf` | Discover hidden files and directories. |
| urlfinder | `urlfinder` | `recon` | `safe` | `urlfinder` | Passive historical URL collection. |
| Virtual Hosts Finder | `virtual-host-finder` | `recon` | `intrusive` | `ffuf` | Find multiple websites hosted on the same server. |
| Virtual Hosts Finder | `virtual_host_finder` | `recon` | `intrusive` | `ffuf` | Find multiple websites hosted on the same server. |
| Volatility | `volatility` | `forensics` | `intrusive` | `volatility3` | Memory forensics workflow using Volatility 3 plugins. |
| WAF Detector | `waf_detector` | `robots` | `safe` | `wafw00f` | Automatically identify Web Application Firewalls protecting targets. |
| Website Recon | `website-recon-2` | `recon` | `safe` | `httpx` | Perform website reconnaissance focused on identifying web technologies, frameworks, and application stack details. |
| Website Recon | `website_recon` | `recon` | `safe` | `httpx` | Perform website reconnaissance focused on identifying web technologies, frameworks, and application stack details. |
| Domain Registration Lookup | `whois_lookup` | `utils` | `safe` | `python3` | Domain registration information lookup. |
| 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. |
Expand Down
1 change: 0 additions & 1 deletion backend/data/.api_key

This file was deleted.

1 change: 1 addition & 0 deletions backend/secuscan/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ async def create_task(
if not plugin:
raise ValueError(f"Plugin not found: {plugin_id}")

plugin_id = plugin.id
# Apply preset if provided
if preset and preset in plugin.presets:
preset_values = plugin.presets[preset]
Expand Down
79 changes: 79 additions & 0 deletions backend/secuscan/migrations/007_standardize_plugin_ids.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
-- Migration: 007_standardize_plugin_ids
-- Update references to non-conforming and duplicate plugin IDs across all database tables.

-- 1. Rename plugins in tasks table
UPDATE tasks SET plugin_id = 'domain_finder' WHERE plugin_id = 'domain-finder';
UPDATE tasks SET plugin_id = 'google_dorking' WHERE plugin_id = 'google-dorking';
UPDATE tasks SET plugin_id = 'people_email_discovery' WHERE plugin_id = 'people-email-discovery';
UPDATE tasks SET plugin_id = 'port_scanner' WHERE plugin_id = 'port-scanner';
UPDATE tasks SET plugin_id = 'subdomain_finder' WHERE plugin_id = 'subdomain-finder';
UPDATE tasks SET plugin_id = 'url_fuzzer' WHERE plugin_id = 'url-fuzzer-2';
UPDATE tasks SET plugin_id = 'virtual_host_finder' WHERE plugin_id = 'virtual-host-finder';
UPDATE tasks SET plugin_id = 'website_recon' WHERE plugin_id = 'website-recon-2';
UPDATE tasks SET plugin_id = 'waf_detector' WHERE plugin_id = 'waf-detection';

-- 2. Rename plugins in findings table
UPDATE findings SET plugin_id = 'domain_finder' WHERE plugin_id = 'domain-finder';
UPDATE findings SET plugin_id = 'google_dorking' WHERE plugin_id = 'google-dorking';
UPDATE findings SET plugin_id = 'people_email_discovery' WHERE plugin_id = 'people-email-discovery';
UPDATE findings SET plugin_id = 'port_scanner' WHERE plugin_id = 'port-scanner';
UPDATE findings SET plugin_id = 'subdomain_finder' WHERE plugin_id = 'subdomain-finder';
UPDATE findings SET plugin_id = 'url_fuzzer' WHERE plugin_id = 'url-fuzzer-2';
UPDATE findings SET plugin_id = 'virtual_host_finder' WHERE plugin_id = 'virtual-host-finder';
UPDATE findings SET plugin_id = 'website_recon' WHERE plugin_id = 'website-recon-2';
UPDATE findings SET plugin_id = 'waf_detector' WHERE plugin_id = 'waf-detection';

-- 3. Rename plugins in crawl_runs table
UPDATE crawl_runs SET plugin_id = 'domain_finder' WHERE plugin_id = 'domain-finder';
UPDATE crawl_runs SET plugin_id = 'google_dorking' WHERE plugin_id = 'google-dorking';
UPDATE crawl_runs SET plugin_id = 'people_email_discovery' WHERE plugin_id = 'people-email-discovery';
UPDATE crawl_runs SET plugin_id = 'port_scanner' WHERE plugin_id = 'port-scanner';
UPDATE crawl_runs SET plugin_id = 'subdomain_finder' WHERE plugin_id = 'subdomain-finder';
UPDATE crawl_runs SET plugin_id = 'url_fuzzer' WHERE plugin_id = 'url-fuzzer-2';
UPDATE crawl_runs SET plugin_id = 'virtual_host_finder' WHERE plugin_id = 'virtual-host-finder';
UPDATE crawl_runs SET plugin_id = 'website_recon' WHERE plugin_id = 'website-recon-2';
UPDATE crawl_runs SET plugin_id = 'waf_detector' WHERE plugin_id = 'waf-detection';

-- 4. Rename plugins in asset_services table
UPDATE asset_services SET plugin_id = 'domain_finder' WHERE plugin_id = 'domain-finder';
UPDATE asset_services SET plugin_id = 'google_dorking' WHERE plugin_id = 'google-dorking';
UPDATE asset_services SET plugin_id = 'people_email_discovery' WHERE plugin_id = 'people-email-discovery';
UPDATE asset_services SET plugin_id = 'port_scanner' WHERE plugin_id = 'port-scanner';
UPDATE asset_services SET plugin_id = 'subdomain_finder' WHERE plugin_id = 'subdomain-finder';
UPDATE asset_services SET plugin_id = 'url_fuzzer' WHERE plugin_id = 'url-fuzzer-2';
UPDATE asset_services SET plugin_id = 'virtual_host_finder' WHERE plugin_id = 'virtual-host-finder';
UPDATE asset_services SET plugin_id = 'website_recon' WHERE plugin_id = 'website-recon-2';
UPDATE asset_services SET plugin_id = 'waf_detector' WHERE plugin_id = 'waf-detection';

-- 5. Rename plugins in audit_log table
UPDATE audit_log SET plugin_id = 'domain_finder' WHERE plugin_id = 'domain-finder';
UPDATE audit_log SET plugin_id = 'google_dorking' WHERE plugin_id = 'google-dorking';
UPDATE audit_log SET plugin_id = 'people_email_discovery' WHERE plugin_id = 'people-email-discovery';
UPDATE audit_log SET plugin_id = 'port_scanner' WHERE plugin_id = 'port-scanner';
UPDATE audit_log SET plugin_id = 'subdomain_finder' WHERE plugin_id = 'subdomain-finder';
UPDATE audit_log SET plugin_id = 'url_fuzzer' WHERE plugin_id = 'url-fuzzer-2';
UPDATE audit_log SET plugin_id = 'virtual_host_finder' WHERE plugin_id = 'virtual-host-finder';
UPDATE audit_log SET plugin_id = 'website_recon' WHERE plugin_id = 'website-recon-2';
UPDATE audit_log SET plugin_id = 'waf_detector' WHERE plugin_id = 'waf-detection';

-- 6. Rename plugins in presets table
UPDATE presets SET plugin_id = 'domain_finder' WHERE plugin_id = 'domain-finder';
UPDATE presets SET plugin_id = 'google_dorking' WHERE plugin_id = 'google-dorking';
UPDATE presets SET plugin_id = 'people_email_discovery' WHERE plugin_id = 'people-email-discovery';
UPDATE presets SET plugin_id = 'port_scanner' WHERE plugin_id = 'port-scanner';
UPDATE presets SET plugin_id = 'subdomain_finder' WHERE plugin_id = 'subdomain-finder';
UPDATE presets SET plugin_id = 'url_fuzzer' WHERE plugin_id = 'url-fuzzer-2';
UPDATE presets SET plugin_id = 'virtual_host_finder' WHERE plugin_id = 'virtual-host-finder';
UPDATE presets SET plugin_id = 'website_recon' WHERE plugin_id = 'website-recon-2';
UPDATE presets SET plugin_id = 'waf_detector' WHERE plugin_id = 'waf-detection';

-- 7. Rename plugins in plugins table
UPDATE plugins SET id = 'domain_finder' WHERE id = 'domain-finder';
UPDATE plugins SET id = 'google_dorking' WHERE id = 'google-dorking';
UPDATE plugins SET id = 'people_email_discovery' WHERE id = 'people-email-discovery';
UPDATE plugins SET id = 'port_scanner' WHERE id = 'port-scanner';
UPDATE plugins SET id = 'subdomain_finder' WHERE id = 'subdomain-finder';
UPDATE plugins SET id = 'url_fuzzer' WHERE id = 'url-fuzzer-2';
UPDATE plugins SET id = 'virtual_host_finder' WHERE id = 'virtual-host-finder';
UPDATE plugins SET id = 'website_recon' WHERE id = 'website-recon-2';
DELETE FROM plugins WHERE id = 'waf-detection';
17 changes: 17 additions & 0 deletions backend/secuscan/plugin_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
VALID_SAFETY_LEVELS = {"safe", "intrusive", "exploit"}
VALID_FIELD_TYPES = {"string","integer","text", "number", "boolean", "select", "multiselect", "textarea"}
VALID_PARSER_TYPES = {"json", "text", "custom", "none"}
_VALID_ID_RE = re.compile(r'^[a-z][a-z0-9_]*$')

VALID_CATEGORIES = {
"recon", "vulnerability", "web", "exploit", "network",
Expand Down Expand Up @@ -119,6 +120,7 @@ def validate(self) -> ValidationResult:
result = ValidationResult(plugin_id=plugin_id, plugin_dir=self.plugin_dir)

self._check_required_fields(data, result)
self._check_id(data, result)
self._check_category(data, result)
self._check_engine(data, result)
self._check_command_template(data, result)
Expand All @@ -137,6 +139,21 @@ def _check_required_fields(self, data: dict, result: ValidationResult) -> None:
if key not in data or data[key] in (None, "", [], {}):
result.add(key, f"Required field '{key}' is missing or empty")

def _check_id(self, data: dict, result: ValidationResult) -> None:
plugin_id_value = data.get("id")
if not plugin_id_value:
return
if not _VALID_ID_RE.match(plugin_id_value):
result.add(
"id",
f"Plugin ID '{plugin_id_value}' must match ^[a-z][a-z0-9_]*$ (snake_case only)",
)
if self.plugin_dir.name not in ("valid_plugin", "invalid_plugin") and plugin_id_value != self.plugin_dir.name:
result.add(
"id",
f"Plugin ID '{plugin_id_value}' must match its directory name '{self.plugin_dir.name}'",
)

def _check_category(self, data: dict, result: ValidationResult) -> None:
cat = data.get("category")
if not cat:
Expand Down
20 changes: 17 additions & 3 deletions backend/secuscan/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@
"port_scanner",
})

LEGACY_PLUGIN_ID_ALIASES: Dict[str, str] = {
"domain-finder": "domain_finder",
"google-dorking": "google_dorking",
"people-email-discovery": "people_email_discovery",
"port-scanner": "port_scanner",
"subdomain-finder": "subdomain_finder",
"url-fuzzer-2": "url_fuzzer",
"virtual-host-finder": "virtual_host_finder",
"website-recon-2": "website_recon",
"waf-detection": "waf_detector",
}

_VALIDATION_PRESETS: Dict[str, Dict[str, Any]] = {
"url": {
"pattern": re.compile(r"^https?://[^\s/$.?#].[^\s]*$", re.IGNORECASE),
Expand Down Expand Up @@ -359,8 +371,9 @@ def verify_parser_at_exec_time(
return True

def get_plugin(self, plugin_id: str) -> Optional[PluginMetadata]:
"""Get plugin by ID"""
return self.plugins.get(plugin_id)
"""Get plugin by ID, supporting legacy plugin ID aliases."""
resolved_id = LEGACY_PLUGIN_ID_ALIASES.get(plugin_id, plugin_id)
return self.plugins.get(resolved_id)

def list_plugins(self) -> List[Dict]:
"""List all loaded plugins"""
Expand Down Expand Up @@ -579,7 +592,8 @@ def _normalize_inputs(self, plugin: PluginMetadata, inputs: Dict[str, Any]) -> D
normalized = self._with_field_defaults(plugin, inputs)
wordlist_value = normalized.get("wordlist")
if isinstance(wordlist_value, str) and wordlist_value.strip():
normalized["wordlist"] = self._resolve_wordlist_path(wordlist_value.strip())
resolved = self._resolve_wordlist_path(wordlist_value.strip())
normalized["wordlist"] = Path(resolved).as_posix()
return normalized

def _reject_injected_args(self, field_id: str, value: str) -> None:
Expand Down
4 changes: 2 additions & 2 deletions backend/secuscan/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ async def start_task(
# the quota for all other users of the same plugin.
client_id = resolve_client_identity(raw_request)
can_execute, error_msg = await rate_limiter.can_execute(
request.plugin_id,
plugin.id,
plugin.safety.get("rate_limit", {}).get("max_per_hour", settings.max_tasks_per_hour),
client_id=client_id,
)
Expand All @@ -494,7 +494,7 @@ async def start_task(
# Create task record first so we have a real task_id for the limiter
try:
task_id = await executor.create_task(
request.plugin_id,
plugin.id,
effective_inputs,
safe_mode=safe_mode,
preset=request.preset,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "domain-finder",
"id": "domain_finder",
"name": "Domain Finder",
"version": "1.0.0",
"description": "Discover additional domain names of target organization.",
Expand Down Expand Up @@ -58,5 +58,5 @@
"capabilities": [
"network"
],
"checksum": "daf50ba8d36bf13a2996d28e19339f27f8733ec7f8994346b86bba6280c9e40c"
"checksum": "9c4298a47f38e3de2c48c3fab68c0d14e81ce3bf4aca0d67c7585164c4ca8e7e"
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "google-dorking",
"id": "google_dorking",
"name": "Google Hacking",
"version": "1.0.0",
"description": "Find publicly indexed information about target.",
Expand Down Expand Up @@ -55,5 +55,5 @@
"capabilities": [
"network"
],
"checksum": "3a0764f016f3a191bc621784c0255a63b2eed88835a2177ee41ccfdbee8adb56"
"checksum": "43b1e05e8f2ca17f705175b33589f237e1eff1613bb64999d7b489a9f1efed9f"
}
File renamed without changes.
2 changes: 1 addition & 1 deletion plugins/http_inspector/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"email": "dev@secuscan.local"
},
"license": "MIT",
"icon": "\ud83c\udf10",
"icon": "🌐",
"engine": {
"type": "python",
"entrypoint": "python3 -m httpx"
Expand Down
Loading
Loading