From 192d17bbfd9d226ef162d4766a82c644cc04dd5d Mon Sep 17 00:00:00 2001 From: Vexx Date: Thu, 28 May 2026 12:20:37 +0530 Subject: [PATCH 1/5] Add per-plugin capability permissions and pre-execution enforcement Plugins declare a capabilities list in metadata.json (network, filesystem, docker, credentials, intrusive, exploit). CapabilityEnforcer checks those declarations against the operator-configured SECUSCAN_DENIED_CAPABILITIES list before any command is built or subprocess is spawned. Legacy plugins without an explicit capabilities field fall back to an implied set derived from their safety level so enforcement works across the full plugin catalogue without a hard cut-over. - capabilities.py: Capability enum, CapabilityEnforcer, CapabilityDeniedError, validate_capability_list, effective_capabilities, build_enforcer_from_settings - models.py: optional capabilities field on PluginMetadata - config.py: denied_capabilities setting (SECUSCAN_DENIED_CAPABILITIES env var) - executor.py: capability check before command build; dedicated audit log event on denial; CapabilityDeniedError caught and persisted as task failure - plugins.py: validate capabilities at load time; expose in list_plugins response - All 60 plugin metadata.json files: explicit capabilities declarations - test_capabilities.py: 36 unit tests covering allow, deny, normalisation, legacy fallback, error metadata, and settings integration - .env.example: documented SECUSCAN_DENIED_CAPABILITIES with examples Closes #205 --- .env.example | 7 + backend/secuscan/capabilities.py | 165 ++++++++++++ backend/secuscan/config.py | 1 + backend/secuscan/executor.py | 45 +++- backend/secuscan/models.py | 1 + backend/secuscan/plugins.py | 12 +- plugins/amass/metadata.json | 7 +- plugins/api_scanner/metadata.json | 8 +- plugins/cloud_scanner/metadata.json | 8 +- plugins/cloud_storage_auditor/metadata.json | 7 +- plugins/code_analyzer/metadata.json | 7 +- plugins/container_scanner/metadata.json | 8 +- plugins/crawler/metadata.json | 8 +- plugins/dir_discovery/metadata.json | 8 +- plugins/dns_enum/metadata.json | 7 +- plugins/dnsx/metadata.json | 7 +- plugins/domain-finder/metadata.json | 7 +- plugins/droopescan/metadata.json | 8 +- plugins/fuzzer/metadata.json | 9 +- plugins/google-dorking/metadata.json | 7 +- plugins/hashcat/metadata.json | 9 +- plugins/http_inspector/metadata.json | 7 +- plugins/http_request_logger/metadata.json | 8 +- plugins/httpx/metadata.json | 7 +- plugins/iac_scanner/metadata.json | 7 +- plugins/icmp_ping/metadata.json | 7 +- plugins/joomscan/metadata.json | 8 +- plugins/katana/metadata.json | 8 +- plugins/kubernetes_scanner/metadata.json | 8 +- plugins/metasploit/metadata.json | 9 +- plugins/network_scanner/metadata.json | 8 +- plugins/nikto/metadata.json | 8 +- plugins/nmap/metadata.json | 7 +- plugins/nuclei/metadata.json | 8 +- plugins/password_auditor/metadata.json | 9 +- plugins/people-email-discovery/metadata.json | 7 +- plugins/port-scanner/metadata.json | 8 +- plugins/scapy_recon/metadata.json | 7 +- plugins/secret_scanner/metadata.json | 7 +- plugins/sharepoint_scanner/metadata.json | 8 +- plugins/sitemap_gen/metadata.json | 8 +- plugins/sniper/metadata.json | 9 +- plugins/spider/metadata.json | 8 +- plugins/sqli_checker/metadata.json | 8 +- plugins/sqli_exploiter/metadata.json | 9 +- plugins/sqlmap/metadata.json | 9 +- plugins/ssh_runner/metadata.json | 9 +- plugins/subdomain-finder/metadata.json | 7 +- plugins/subdomain_discovery/metadata.json | 7 +- plugins/subdomain_takeover/metadata.json | 8 +- plugins/subfinder/metadata.json | 7 +- plugins/theharvester/metadata.json | 7 +- plugins/tls_inspector/metadata.json | 7 +- plugins/uncover/metadata.json | 7 +- plugins/url-fuzzer-2/metadata.json | 8 +- plugins/urlfinder/metadata.json | 7 +- plugins/virtual-host-finder/metadata.json | 8 +- plugins/volatility/metadata.json | 8 +- plugins/waf-detection/metadata.json | 7 +- plugins/waf_detector/metadata.json | 5 +- plugins/website-recon-2/metadata.json | 7 +- plugins/whois_lookup/metadata.json | 7 +- plugins/wpscan/metadata.json | 8 +- plugins/xss_exploiter/metadata.json | 9 +- plugins/yara_scan/metadata.json | 8 +- plugins/zap_scanner/metadata.json | 9 +- testing/backend/unit/test_capabilities.py | 253 +++++++++++++++++++ 67 files changed, 824 insertions(+), 122 deletions(-) create mode 100644 backend/secuscan/capabilities.py create mode 100644 testing/backend/unit/test_capabilities.py diff --git a/.env.example b/.env.example index b0448ff2..e7680277 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,13 @@ SECUSCAN_VAULT_KEY=replace-with-output-of-secrets.token_hex-32 # SECUSCAN_PLUGIN_SIGNATURE_KEY=replace-with-your-signing-key # SECUSCAN_ENFORCE_PLUGIN_SIGNATURES=false +# Plugin Capability Policy +# Comma-separated list of capabilities to deny across all plugins. +# Plugins that require any denied capability will fail before execution. +# Supported values: network, filesystem, docker, credentials, intrusive, exploit +# Example: deny all exploitation and credential-accessing plugins: +# SECUSCAN_DENIED_CAPABILITIES=exploit,credentials + # Frontend Overrides # Leave these unset for the default local dev flow. # VITE_API_PROXY_TARGET=http://127.0.0.1:8000 diff --git a/backend/secuscan/capabilities.py b/backend/secuscan/capabilities.py new file mode 100644 index 00000000..18cc8598 --- /dev/null +++ b/backend/secuscan/capabilities.py @@ -0,0 +1,165 @@ +""" +Per-plugin capability declarations and pre-execution enforcement. + +Plugins declare a list of capabilities they require in their metadata.json under +the ``capabilities`` key. The enforcer checks that list against the operator's +``denied_capabilities`` setting (``SECUSCAN_DENIED_CAPABILITIES`` env var, comma- +separated) before any command is built or process is spawned. + +Supported capabilities +---------------------- +network - plugin makes outbound network connections +filesystem - plugin reads or writes paths on the local filesystem +docker - plugin requires the Docker daemon at runtime +credentials - plugin pulls secrets from the credential vault +intrusive - plugin performs active probing that may affect target systems +exploit - plugin attempts to exploit vulnerabilities (highest risk, opt-in only) +""" + +from __future__ import annotations + +from enum import Enum +from typing import FrozenSet, List, Optional, Set + +import logging + +logger = logging.getLogger(__name__) + + +class Capability(str, Enum): + """All recognised plugin capability tokens.""" + + NETWORK = "network" + FILESYSTEM = "filesystem" + DOCKER = "docker" + CREDENTIALS = "credentials" + INTRUSIVE = "intrusive" + EXPLOIT = "exploit" + + +ALL_CAPABILITIES: FrozenSet[str] = frozenset(c.value for c in Capability) + +# Capabilities that are implicitly required by a plugin's safety level when the +# plugin has not declared them explicitly. This lets older plugins without a +# ``capabilities`` field degrade gracefully while still being enforceable. +_SAFETY_LEVEL_IMPLIED: dict[str, List[str]] = { + "safe": ["network"], + "intrusive": ["network", "intrusive"], + "exploit": ["network", "intrusive", "exploit"], +} + + +class CapabilityDeniedError(PermissionError): + """Raised when a plugin attempts to use a capability that the operator has denied.""" + + def __init__(self, plugin_id: str, denied: Set[str]) -> None: + self.plugin_id = plugin_id + self.denied_capabilities = denied + caps = ", ".join(sorted(denied)) + super().__init__( + f"Plugin '{plugin_id}' requires capabilities [{caps}] that are denied by " + "operator policy. Update SECUSCAN_DENIED_CAPABILITIES to allow them or " + "choose a plugin that does not require these capabilities." + ) + + +def validate_capability_list(capabilities: List[str], plugin_id: str) -> List[str]: + """Return the normalised capability list, raising ValueError for unknowns.""" + normalised: List[str] = [] + for raw in capabilities: + token = raw.strip().lower() + if token not in ALL_CAPABILITIES: + raise ValueError( + f"Plugin '{plugin_id}' declares unknown capability '{raw}'. " + f"Supported capabilities: {sorted(ALL_CAPABILITIES)}" + ) + normalised.append(token) + return normalised + + +def effective_capabilities( + declared: Optional[List[str]], + safety_level: str, + plugin_id: str, +) -> Set[str]: + """Combine explicitly declared capabilities with safety-level implied ones. + + If the plugin declares an explicit capability list, that list is the source + of truth (implied capabilities are *not* added on top โ€” they were already + considered by the plugin author). If no capabilities are declared at all the + implied set for the plugin's safety level is used so that legacy plugins + remain enforceable. + """ + if declared is not None and len(declared) > 0: + validated = validate_capability_list(declared, plugin_id) + return set(validated) + + implied = _SAFETY_LEVEL_IMPLIED.get(safety_level, ["network"]) + return set(implied) + + +class CapabilityEnforcer: + """Checks plugin capabilities against the operator-configured denied set. + + Instantiate once and reuse across the application lifetime. The denied set + is fixed at construction time so that the enforcer is deterministic and + testable independently of the global settings object. + """ + + def __init__(self, denied_capabilities: Optional[List[str]] = None) -> None: + raw = denied_capabilities or [] + self._denied: FrozenSet[str] = frozenset( + tok.strip().lower() for tok in raw if tok.strip() + ) + if self._denied: + logger.info( + "CapabilityEnforcer: operator has denied capabilities: %s", + sorted(self._denied), + ) + + @property + def denied(self) -> FrozenSet[str]: + return self._denied + + def check( + self, + plugin_id: str, + declared: Optional[List[str]], + safety_level: str, + ) -> None: + """Raise CapabilityDeniedError if the plugin needs a denied capability. + + Args: + plugin_id: The plugin's ``id`` field from metadata. + declared: The ``capabilities`` list from the plugin's metadata (may be None). + safety_level: The plugin's safety level (``safe``, ``intrusive``, ``exploit``). + + Raises: + CapabilityDeniedError: when any required capability is denied. + """ + if not self._denied: + return + + required = effective_capabilities(declared, safety_level, plugin_id) + blocked = required & self._denied + + if blocked: + logger.warning( + "Blocked plugin '%s': requires denied capabilities %s", + plugin_id, + sorted(blocked), + ) + raise CapabilityDeniedError(plugin_id, blocked) + + logger.debug( + "Capability check passed for plugin '%s': required=%s", + plugin_id, + sorted(required), + ) + + +def build_enforcer_from_settings() -> CapabilityEnforcer: + """Construct a CapabilityEnforcer from the global application settings.""" + from .config import settings # local import to avoid circular dependency + + return CapabilityEnforcer(denied_capabilities=list(settings.denied_capabilities)) diff --git a/backend/secuscan/config.py b/backend/secuscan/config.py index a7deae68..4b8583a0 100644 --- a/backend/secuscan/config.py +++ b/backend/secuscan/config.py @@ -55,6 +55,7 @@ class Settings(BaseSettings): plugin_signature_key: Optional[str] = None enforce_plugin_signatures: bool = False vault_key: Optional[str] = None + denied_capabilities: List[str] = [] # Rate Limiting max_concurrent_tasks: int = 3 diff --git a/backend/secuscan/executor.py b/backend/secuscan/executor.py index 01a68020..5cdf7c83 100644 --- a/backend/secuscan/executor.py +++ b/backend/secuscan/executor.py @@ -21,6 +21,7 @@ from .models import TaskStatus, ScanPhase from .ratelimit import concurrent_limiter from .risk_scoring import compute_risk_score, compute_risk_factors +from .capabilities import CapabilityEnforcer, CapabilityDeniedError, build_enforcer_from_settings def _parse_discovered_at(finding: dict) -> Optional[datetime]: @@ -88,6 +89,7 @@ def __init__(self): self.running_tasks: Dict[str, asyncio.Task] = {} # PubSub: Map of task_id to list of active async queues listening for output/status updates self._listeners: Dict[str, List[asyncio.Queue]] = {} + self._capability_enforcer: CapabilityEnforcer = build_enforcer_from_settings() def subscribe(self, task_id: str) -> asyncio.Queue: """Subscribe to a task's real-time events.""" @@ -326,8 +328,13 @@ async def execute_task(self, task_id: str): if not plugin: raise ValueError(f"Plugin not found: {plugin_id}") - # Pending records for assets removed - + # Enforce capability policy before any command is built or process spawned + self._capability_enforcer.check( + plugin_id=plugin.id, + declared=plugin.capabilities, + safety_level=plugin.safety.get("level", "safe"), + ) + command = plugin_manager.build_command(plugin_id, inputs) if not command: @@ -456,6 +463,40 @@ async def execute_task(self, task_id: str): await self._invalidate_cached_views() raise # let asyncio complete the cancellation + except CapabilityDeniedError as e: + logger.warning("Task %s blocked by capability policy: %s", task_id, e) + duration = (time.time() - start_time) if "start_time" in locals() else 0 + await db.execute( + """ + UPDATE tasks SET + status = ?, + completed_at = ?, + duration_seconds = ?, + error_message = ? + WHERE id = ? + """, + ( + TaskStatus.FAILED.value, + datetime.now().isoformat(), + duration, + str(e), + task_id, + ), + ) + await self._broadcast(task_id, "status", TaskStatus.FAILED.value) + await self._invalidate_cached_views() + await db.log_audit( + "task_capability_denied", + f"Task blocked by capability policy: {str(e)}", + severity="warning", + context={ + "task_id": task_id, + "denied_capabilities": sorted(e.denied_capabilities), + "plugin_id": plugin_id, + }, + task_id=task_id, + ) + except Exception as e: logger.error(f"Task {task_id} failed: {e}", exc_info=True) diff --git a/backend/secuscan/models.py b/backend/secuscan/models.py index f1792be2..9882de78 100644 --- a/backend/secuscan/models.py +++ b/backend/secuscan/models.py @@ -79,6 +79,7 @@ class PluginMetadata(BaseModel): output: Dict[str, Any] safety: Dict[str, Any] + capabilities: Optional[List[str]] = None learning: Optional[Dict[str, Any]] = None dependencies: Optional[Dict[str, List[str]]] = None docker_image: Optional[str] = None diff --git a/backend/secuscan/plugins.py b/backend/secuscan/plugins.py index 45285a3d..3a65361a 100644 --- a/backend/secuscan/plugins.py +++ b/backend/secuscan/plugins.py @@ -12,8 +12,9 @@ import hashlib import hmac -from .models import PluginMetadata +from .models import PluginMetadata, PluginFieldType from .config import settings +from .capabilities import validate_capability_list, ALL_CAPABILITIES logger = logging.getLogger(__name__) @@ -112,6 +113,14 @@ async def _validate_plugin(self, plugin: PluginMetadata, plugin_dir: Path) -> bo logger.error(f"Invalid safety level: {safety_level}") return False + # Validate declared capabilities against the known set + if plugin.capabilities is not None: + try: + validate_capability_list(plugin.capabilities, plugin.id) + except ValueError as exc: + logger.error("Invalid capabilities in plugin %s: %s", plugin.id, exc) + return False + if not self._verify_plugin_integrity(plugin, plugin_dir): return False @@ -242,6 +251,7 @@ def list_plugins(self) -> List[Dict]: "icon": plugin.icon, "requires_consent": bool(plugin.safety.get("requires_consent", False)), "consent_message": plugin.safety.get("consent_message"), + "capabilities": plugin.capabilities or [], "availability": { "runnable": len(missing_binaries) == 0, "missing_binaries": missing_binaries, diff --git a/plugins/amass/metadata.json b/plugins/amass/metadata.json index 9c2021d6..079e1b85 100644 --- a/plugins/amass/metadata.json +++ b/plugins/amass/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "amass" @@ -55,5 +55,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "d15de85963abb77d529487e5000519ef554171826f37d3a223b44f67725b6312" + "checksum": "d15de85963abb77d529487e5000519ef554171826f37d3a223b44f67725b6312", + "capabilities": [ + "network" + ] } diff --git a/plugins/api_scanner/metadata.json b/plugins/api_scanner/metadata.json index 703c959d..7167c251 100644 --- a/plugins/api_scanner/metadata.json +++ b/plugins/api_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "nuclei" @@ -57,5 +57,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "21271574173b2881599dd103a10c678456031d36fab90cc7b35683413613d154" + "checksum": "21271574173b2881599dd103a10c678456031d36fab90cc7b35683413613d154", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/cloud_scanner/metadata.json b/plugins/cloud_scanner/metadata.json index a34b80e9..e24ee86b 100644 --- a/plugins/cloud_scanner/metadata.json +++ b/plugins/cloud_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -53,5 +53,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "3485a50459568754718e8494b5e46f141d99df609cb9da1131e19b12fa8dd3c3" + "checksum": "3485a50459568754718e8494b5e46f141d99df609cb9da1131e19b12fa8dd3c3", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/cloud_storage_auditor/metadata.json b/plugins/cloud_storage_auditor/metadata.json index 67b72213..141f14e3 100644 --- a/plugins/cloud_storage_auditor/metadata.json +++ b/plugins/cloud_storage_auditor/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "uncover" @@ -64,5 +64,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "8327c42108570fc0b7b5379661f811d3c36b26abf904182e6634bd22145b9c7a" + "checksum": "8327c42108570fc0b7b5379661f811d3c36b26abf904182e6634bd22145b9c7a", + "capabilities": [ + "network" + ] } diff --git a/plugins/code_analyzer/metadata.json b/plugins/code_analyzer/metadata.json index e9f02ea3..8eb56eb4 100644 --- a/plugins/code_analyzer/metadata.json +++ b/plugins/code_analyzer/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udcc3", + "icon": "๐Ÿ“ƒ", "engine": { "type": "cli", "binary": "bandit" @@ -88,5 +88,8 @@ "system_packages": [] }, "docker_image": "pyfound/bandit:latest", - "checksum": "7daaed25ba96309668e1792c653abeec3f6d407324878b08e1d7a607a5f728db" + "checksum": "7daaed25ba96309668e1792c653abeec3f6d407324878b08e1d7a607a5f728db", + "capabilities": [ + "filesystem" + ] } diff --git a/plugins/container_scanner/metadata.json b/plugins/container_scanner/metadata.json index 792f5476..c7502c87 100644 --- a/plugins/container_scanner/metadata.json +++ b/plugins/container_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "Apache-2.0", - "icon": "\ud83d\udce6", + "icon": "๐Ÿ“ฆ", "engine": { "type": "cli", "binary": "trivy" @@ -67,5 +67,9 @@ "system_packages": [] }, "docker_image": "aquasec/trivy", - "checksum": "934f32fba8427ff8c93a44f9865612b8c38f2da81af25cd1e0e06dd0ca37b57a" + "checksum": "934f32fba8427ff8c93a44f9865612b8c38f2da81af25cd1e0e06dd0ca37b57a", + "capabilities": [ + "network", + "docker" + ] } diff --git a/plugins/crawler/metadata.json b/plugins/crawler/metadata.json index df72d142..a30611b0 100644 --- a/plugins/crawler/metadata.json +++ b/plugins/crawler/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "katana" @@ -68,5 +68,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "12ba96873661ba9a316f3b8ab5fd75daf9f66a259e4f4a706cc3895a86ef59d4" + "checksum": "12ba96873661ba9a316f3b8ab5fd75daf9f66a259e4f4a706cc3895a86ef59d4", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/dir_discovery/metadata.json b/plugins/dir_discovery/metadata.json index cb168cf7..2c95aad9 100644 --- a/plugins/dir_discovery/metadata.json +++ b/plugins/dir_discovery/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udcc2", + "icon": "๐Ÿ“‚", "engine": { "type": "cli", "binary": "ffuf" @@ -185,5 +185,9 @@ "system_packages": [] }, "docker_image": "secuscan/ffuf", - "checksum": "2c66081a09e2c3a765316987257ce7a46116ccd4c19e18fc66cb6b14b60e0742" + "checksum": "2c66081a09e2c3a765316987257ce7a46116ccd4c19e18fc66cb6b14b60e0742", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/dns_enum/metadata.json b/plugins/dns_enum/metadata.json index b3dba071..d0b28f79 100644 --- a/plugins/dns_enum/metadata.json +++ b/plugins/dns_enum/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udce6", + "icon": "๐Ÿ“ฆ", "engine": { "type": "cli", "binary": "dnsrecon" @@ -88,5 +88,8 @@ ] }, "docker_image": "darkoperator/dnsrecon:latest", - "checksum": "a9d9107ad715c542acc252d495337b6f7de790210e3d300971e1aba2e19260f4" + "checksum": "a9d9107ad715c542acc252d495337b6f7de790210e3d300971e1aba2e19260f4", + "capabilities": [ + "network" + ] } diff --git a/plugins/dnsx/metadata.json b/plugins/dnsx/metadata.json index d1126c4e..3515ebe7 100644 --- a/plugins/dnsx/metadata.json +++ b/plugins/dnsx/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "dnsx" @@ -52,5 +52,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "8f1a5ecf2f4e2192010470c00853682ab7f70714325cdc8d59c8000a1598f44a" + "checksum": "8f1a5ecf2f4e2192010470c00853682ab7f70714325cdc8d59c8000a1598f44a", + "capabilities": [ + "network" + ] } diff --git a/plugins/domain-finder/metadata.json b/plugins/domain-finder/metadata.json index 00e40a60..1f4e1df9 100644 --- a/plugins/domain-finder/metadata.json +++ b/plugins/domain-finder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "amass" @@ -55,5 +55,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "b8bc0ebc7ba6b739c3f81be706c98cc354e9ffdad1468d62ef00600a3e8907d6" + "checksum": "b8bc0ebc7ba6b739c3f81be706c98cc354e9ffdad1468d62ef00600a3e8907d6", + "capabilities": [ + "network" + ] } diff --git a/plugins/droopescan/metadata.json b/plugins/droopescan/metadata.json index e7a36fc0..bd9e705a 100644 --- a/plugins/droopescan/metadata.json +++ b/plugins/droopescan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee1\ufe0f", + "icon": "๐Ÿ›ก๏ธ", "engine": { "type": "cli", "binary": "droopescan" @@ -60,5 +60,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "d06997f1c1fc57665492e772ff146100ba387bbd30438f9493998eae94f71645" + "checksum": "d06997f1c1fc57665492e772ff146100ba387bbd30438f9493998eae94f71645", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/fuzzer/metadata.json b/plugins/fuzzer/metadata.json index da88c931..393a25c4 100644 --- a/plugins/fuzzer/metadata.json +++ b/plugins/fuzzer/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -53,5 +53,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "eaac1036bce6e14dbc2ebd48ec97c262461bae5c1611510c51581d9b907a0fe4" + "checksum": "eaac1036bce6e14dbc2ebd48ec97c262461bae5c1611510c51581d9b907a0fe4", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/plugins/google-dorking/metadata.json b/plugins/google-dorking/metadata.json index 8848acf2..38d855c6 100644 --- a/plugins/google-dorking/metadata.json +++ b/plugins/google-dorking/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "python3" @@ -52,5 +52,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "7284033396542bcc591307d2339b525d7eebd0da5365a252b79ff21a0160f208" + "checksum": "7284033396542bcc591307d2339b525d7eebd0da5365a252b79ff21a0160f208", + "capabilities": [ + "network" + ] } diff --git a/plugins/hashcat/metadata.json b/plugins/hashcat/metadata.json index f50e164c..dde82353 100644 --- a/plugins/hashcat/metadata.json +++ b/plugins/hashcat/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\u26a1", + "icon": "โšก", "engine": { "type": "cli", "binary": "hashcat" @@ -96,5 +96,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "3c62c9b154b651149426d4fa7fefc9fdfb16f05c20e0850b0ed63a8f44c22b89" + "checksum": "3c62c9b154b651149426d4fa7fefc9fdfb16f05c20e0850b0ed63a8f44c22b89", + "capabilities": [ + "filesystem", + "intrusive", + "exploit" + ] } diff --git a/plugins/http_inspector/metadata.json b/plugins/http_inspector/metadata.json index 814b4855..22eb5a71 100644 --- a/plugins/http_inspector/metadata.json +++ b/plugins/http_inspector/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83c\udf10", + "icon": "๐ŸŒ", "engine": { "type": "python", "entrypoint": "python3 -m httpx" @@ -92,5 +92,8 @@ "system_packages": [] }, "docker_image": "curlimages/curl", - "checksum": "435436b8dbde27b30f545c3f5701089db880794a8a3f3a47e08cf2e6dbffa447" + "checksum": "435436b8dbde27b30f545c3f5701089db880794a8a3f3a47e08cf2e6dbffa447", + "capabilities": [ + "network" + ] } diff --git a/plugins/http_request_logger/metadata.json b/plugins/http_request_logger/metadata.json index ac8bfe7b..bd77f674 100644 --- a/plugins/http_request_logger/metadata.json +++ b/plugins/http_request_logger/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "httpx" @@ -60,5 +60,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "1914157eced0f91a400d858291ca4dc5b9fe9c5b286d4178339deb7fc7f4fb06" + "checksum": "1914157eced0f91a400d858291ca4dc5b9fe9c5b286d4178339deb7fc7f4fb06", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/httpx/metadata.json b/plugins/httpx/metadata.json index 8a2a7bee..518aaeb6 100644 --- a/plugins/httpx/metadata.json +++ b/plugins/httpx/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "httpx" @@ -55,5 +55,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "b74defa2b8d5595ae6a8fbd8020c35ce05a214beb65d11f31847ae28d6517e2f" + "checksum": "b74defa2b8d5595ae6a8fbd8020c35ce05a214beb65d11f31847ae28d6517e2f", + "capabilities": [ + "network" + ] } diff --git a/plugins/iac_scanner/metadata.json b/plugins/iac_scanner/metadata.json index e8346a24..f87461e1 100644 --- a/plugins/iac_scanner/metadata.json +++ b/plugins/iac_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -54,5 +54,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "35c01e2544805a78b46b75fb168a98a486a7b80dd926a21ca3c91d721e02af78" + "checksum": "35c01e2544805a78b46b75fb168a98a486a7b80dd926a21ca3c91d721e02af78", + "capabilities": [ + "filesystem" + ] } diff --git a/plugins/icmp_ping/metadata.json b/plugins/icmp_ping/metadata.json index f1d96bfd..d1cc1078 100644 --- a/plugins/icmp_ping/metadata.json +++ b/plugins/icmp_ping/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "ping" @@ -69,5 +69,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "bee1387121a61c22a4d5c8d8c99024ff3ed0a45397dc240b6507792b1193d676" + "checksum": "bee1387121a61c22a4d5c8d8c99024ff3ed0a45397dc240b6507792b1193d676", + "capabilities": [ + "network" + ] } diff --git a/plugins/joomscan/metadata.json b/plugins/joomscan/metadata.json index 05fcb09c..2634854b 100644 --- a/plugins/joomscan/metadata.json +++ b/plugins/joomscan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83c\udff7\ufe0f", + "icon": "๐Ÿท๏ธ", "engine": { "type": "cli", "binary": "joomscan" @@ -56,5 +56,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "fc1b9feab32f4f65d995b6613ba450d32d0f318cb5209e6144be3e961e38e392" + "checksum": "fc1b9feab32f4f65d995b6613ba450d32d0f318cb5209e6144be3e961e38e392", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/katana/metadata.json b/plugins/katana/metadata.json index f3e71f40..050ce28e 100644 --- a/plugins/katana/metadata.json +++ b/plugins/katana/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "katana" @@ -53,5 +53,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "6c03736ab9bf88e97d7656ae4816922f68c3ef2a07a66b1ccfa7cb2c570b6a1a" + "checksum": "6c03736ab9bf88e97d7656ae4816922f68c3ef2a07a66b1ccfa7cb2c570b6a1a", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/kubernetes_scanner/metadata.json b/plugins/kubernetes_scanner/metadata.json index 5e324c89..42b4628a 100644 --- a/plugins/kubernetes_scanner/metadata.json +++ b/plugins/kubernetes_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -53,5 +53,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "830b2c5f4e397d2d2ad28f71ec8d1a3a5f9bd3e439d8318f71a5b8a371f7ccdb" + "checksum": "830b2c5f4e397d2d2ad28f71ec8d1a3a5f9bd3e439d8318f71a5b8a371f7ccdb", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/metasploit/metadata.json b/plugins/metasploit/metadata.json index d038e9c9..61059abc 100644 --- a/plugins/metasploit/metadata.json +++ b/plugins/metasploit/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\ude80", + "icon": "๐Ÿš€", "engine": { "type": "cli", "binary": "msfconsole" @@ -70,5 +70,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "80a4db676d52e83a27dcb1e51780678f8b8476b5745ff81b2896b35662cf2767" + "checksum": "80a4db676d52e83a27dcb1e51780678f8b8476b5745ff81b2896b35662cf2767", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/plugins/network_scanner/metadata.json b/plugins/network_scanner/metadata.json index 0593f1c6..589e94e4 100644 --- a/plugins/network_scanner/metadata.json +++ b/plugins/network_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "nmap" @@ -65,5 +65,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "54d4478eaeec8b047841ac1ab9971842b02cafa6c5895b82469c2c3e2ce97652" + "checksum": "54d4478eaeec8b047841ac1ab9971842b02cafa6c5895b82469c2c3e2ce97652", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/nikto/metadata.json b/plugins/nikto/metadata.json index 99ab4878..e0602305 100644 --- a/plugins/nikto/metadata.json +++ b/plugins/nikto/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "nikto" @@ -333,5 +333,9 @@ ] }, "docker_image": "sullo/nikto", - "checksum": "0fb0bf4aa20a24934291e650701373b2501b2d44a1d1b0e9445ff9bf84857315" + "checksum": "0fb0bf4aa20a24934291e650701373b2501b2d44a1d1b0e9445ff9bf84857315", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/nmap/metadata.json b/plugins/nmap/metadata.json index 56ca2ef8..0f0db704 100644 --- a/plugins/nmap/metadata.json +++ b/plugins/nmap/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0d", + "icon": "๐Ÿ”", "engine": { "type": "cli", "binary": "nmap" @@ -202,5 +202,8 @@ "system_packages": [] }, "docker_image": "instrumentisto/nmap", - "checksum": "d0870be0230b0553a8fa662a2e1aaeee20a7d81fc603a40e14d4560bc9e21c4c" + "checksum": "d0870be0230b0553a8fa662a2e1aaeee20a7d81fc603a40e14d4560bc9e21c4c", + "capabilities": [ + "network" + ] } diff --git a/plugins/nuclei/metadata.json b/plugins/nuclei/metadata.json index e0ec4ea1..612e4016 100644 --- a/plugins/nuclei/metadata.json +++ b/plugins/nuclei/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83e\uddec", + "icon": "๐Ÿงฌ", "engine": { "type": "cli", "binary": "nuclei" @@ -130,5 +130,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "ecdacab5d26ebd95d07fe61cc781768e046ab004438a254f2ed80efd167c728d" + "checksum": "ecdacab5d26ebd95d07fe61cc781768e046ab004438a254f2ed80efd167c728d", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/password_auditor/metadata.json b/plugins/password_auditor/metadata.json index 61ea6071..904d3daf 100644 --- a/plugins/password_auditor/metadata.json +++ b/plugins/password_auditor/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -53,5 +53,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "d3606fc8426a11a890874be7159b75b534da6c0f5aa56f8291578feb29934358" + "checksum": "d3606fc8426a11a890874be7159b75b534da6c0f5aa56f8291578feb29934358", + "capabilities": [ + "network", + "intrusive", + "credentials" + ] } diff --git a/plugins/people-email-discovery/metadata.json b/plugins/people-email-discovery/metadata.json index dc7ccf94..2f2ea4e2 100644 --- a/plugins/people-email-discovery/metadata.json +++ b/plugins/people-email-discovery/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "theHarvester" @@ -53,5 +53,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "0f0889733638a60dc134a94a668c6683cbdb435560d60f61e7735d47114334c6" + "checksum": "0f0889733638a60dc134a94a668c6683cbdb435560d60f61e7735d47114334c6", + "capabilities": [ + "network" + ] } diff --git a/plugins/port-scanner/metadata.json b/plugins/port-scanner/metadata.json index c305a172..1d985c97 100644 --- a/plugins/port-scanner/metadata.json +++ b/plugins/port-scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "nmap" @@ -65,5 +65,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "6d9c153513574048757d52702889c4e9f90819cb1d418970c60782f0fadd31b6" + "checksum": "6d9c153513574048757d52702889c4e9f90819cb1d418970c60782f0fadd31b6", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/scapy_recon/metadata.json b/plugins/scapy_recon/metadata.json index 879b86a0..d25a6951 100644 --- a/plugins/scapy_recon/metadata.json +++ b/plugins/scapy_recon/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udce1", + "icon": "๐Ÿ“ก", "engine": { "type": "cli", "binary": "python3" @@ -86,5 +86,8 @@ ] }, "docker_image": "secdev/scapy:latest", - "checksum": "688f16afba16bcf122728cd46a060717622ffeb96dad29b964593b9025b79682" + "checksum": "688f16afba16bcf122728cd46a060717622ffeb96dad29b964593b9025b79682", + "capabilities": [ + "network" + ] } diff --git a/plugins/secret_scanner/metadata.json b/plugins/secret_scanner/metadata.json index 1dd98e84..d52b74b6 100644 --- a/plugins/secret_scanner/metadata.json +++ b/plugins/secret_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd11", + "icon": "๐Ÿ”‘", "engine": { "type": "cli", "binary": "gitleaks" @@ -66,5 +66,8 @@ "system_packages": [] }, "docker_image": "zricethezav/gitleaks:latest", - "checksum": "4287007a8a060b41dea04e2f3e2ce98c4db0fefa8e0cca318168bc0ce4aaa032" + "checksum": "4287007a8a060b41dea04e2f3e2ce98c4db0fefa8e0cca318168bc0ce4aaa032", + "capabilities": [ + "filesystem" + ] } diff --git a/plugins/sharepoint_scanner/metadata.json b/plugins/sharepoint_scanner/metadata.json index 2fb76177..4aafc162 100644 --- a/plugins/sharepoint_scanner/metadata.json +++ b/plugins/sharepoint_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "nuclei" @@ -57,5 +57,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "ba08df50150ea7d4df2784f4a5ba4d257a1669dd98d532f4fab6d84edc83fb0a" + "checksum": "ba08df50150ea7d4df2784f4a5ba4d257a1669dd98d532f4fab6d84edc83fb0a", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/sitemap_gen/metadata.json b/plugins/sitemap_gen/metadata.json index 163b50d3..6e899578 100644 --- a/plugins/sitemap_gen/metadata.json +++ b/plugins/sitemap_gen/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "katana" @@ -68,5 +68,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "1555d4793ba9f18eaec55b737fcf28ca7f4236d2b253b1637485ae952409d512" + "checksum": "1555d4793ba9f18eaec55b737fcf28ca7f4236d2b253b1637485ae952409d512", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/sniper/metadata.json b/plugins/sniper/metadata.json index 0f82dd94..a77d333a 100644 --- a/plugins/sniper/metadata.json +++ b/plugins/sniper/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -53,5 +53,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "fe09afd19995b8a925c2be8e35ec10779925a58380991d904b7fa1a6d46d3f87" + "checksum": "fe09afd19995b8a925c2be8e35ec10779925a58380991d904b7fa1a6d46d3f87", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/plugins/spider/metadata.json b/plugins/spider/metadata.json index e18599a0..ec96309d 100644 --- a/plugins/spider/metadata.json +++ b/plugins/spider/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "katana" @@ -69,5 +69,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "8b0111447c9ebc7d1487e2a8fafd4b2ee9c23e191eab39a124e372632b288254" + "checksum": "8b0111447c9ebc7d1487e2a8fafd4b2ee9c23e191eab39a124e372632b288254", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/sqli_checker/metadata.json b/plugins/sqli_checker/metadata.json index c9c8f4f4..060e1101 100644 --- a/plugins/sqli_checker/metadata.json +++ b/plugins/sqli_checker/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83e\uddea", + "icon": "๐Ÿงช", "engine": { "type": "cli", "binary": "ghauri" @@ -124,5 +124,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "c6fd02c9a458bf582009d1b4838384afb07a64b1a3837fb8d93f6cc3d8dd4dfa" + "checksum": "c6fd02c9a458bf582009d1b4838384afb07a64b1a3837fb8d93f6cc3d8dd4dfa", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/sqli_exploiter/metadata.json b/plugins/sqli_exploiter/metadata.json index 5d380e0b..3c724e6c 100644 --- a/plugins/sqli_exploiter/metadata.json +++ b/plugins/sqli_exploiter/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "sqlmap" @@ -83,5 +83,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "afc0b21532397682b6d160ebeac0f8d4830f0f95cdde362b2b384a12a38de596" + "checksum": "afc0b21532397682b6d160ebeac0f8d4830f0f95cdde362b2b384a12a38de596", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/plugins/sqlmap/metadata.json b/plugins/sqlmap/metadata.json index 160cb500..4f794a9f 100644 --- a/plugins/sqlmap/metadata.json +++ b/plugins/sqlmap/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "GPLv2", - "icon": "\ud83d\udc89", + "icon": "๐Ÿ’‰", "engine": { "type": "cli", "binary": "sqlmap" @@ -124,5 +124,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "7312d73eb00289dfe5bc1c2cdb6491054a3a348f198a65b36f69f125c9ae3d50" + "checksum": "7312d73eb00289dfe5bc1c2cdb6491054a3a348f198a65b36f69f125c9ae3d50", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/plugins/ssh_runner/metadata.json b/plugins/ssh_runner/metadata.json index 6673d75a..f32543d2 100644 --- a/plugins/ssh_runner/metadata.json +++ b/plugins/ssh_runner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udda5\ufe0f", + "icon": "๐Ÿ–ฅ๏ธ", "engine": { "type": "cli", "binary": "ssh" @@ -88,5 +88,10 @@ ] }, "docker_image": "alpine:latest", - "checksum": "6d4495f58635dedb184e763e0e4467adf17dc3754a6b1ed7c2dca0d6b30a88ec" + "checksum": "6d4495f58635dedb184e763e0e4467adf17dc3754a6b1ed7c2dca0d6b30a88ec", + "capabilities": [ + "network", + "intrusive", + "credentials" + ] } diff --git a/plugins/subdomain-finder/metadata.json b/plugins/subdomain-finder/metadata.json index fa5288c2..37c3bcdf 100644 --- a/plugins/subdomain-finder/metadata.json +++ b/plugins/subdomain-finder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "subfinder" @@ -52,5 +52,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "4570d8047a567282f230d970a59aa2c6d5bbbcfb37dfd4b9182fc9a0ac8172ea" + "checksum": "4570d8047a567282f230d970a59aa2c6d5bbbcfb37dfd4b9182fc9a0ac8172ea", + "capabilities": [ + "network" + ] } diff --git a/plugins/subdomain_discovery/metadata.json b/plugins/subdomain_discovery/metadata.json index edfe60e6..dbfd2775 100644 --- a/plugins/subdomain_discovery/metadata.json +++ b/plugins/subdomain_discovery/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83c\udf10", + "icon": "๐ŸŒ", "engine": { "type": "cli", "binary": "subfinder" @@ -88,5 +88,8 @@ "system_packages": [] }, "docker_image": "projectdiscovery/subfinder:latest", - "checksum": "34c426cb7ea665b795595723b7f6f0b4bd302ebd69971268ee9eebde4fbac5d5" + "checksum": "34c426cb7ea665b795595723b7f6f0b4bd302ebd69971268ee9eebde4fbac5d5", + "capabilities": [ + "network" + ] } diff --git a/plugins/subdomain_takeover/metadata.json b/plugins/subdomain_takeover/metadata.json index 3999e311..5579ddcc 100644 --- a/plugins/subdomain_takeover/metadata.json +++ b/plugins/subdomain_takeover/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "subfinder" @@ -53,5 +53,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "f735cebb8c673daee580c4026eb8cdef2ebdb5232a3a3591350dda3aa231fc75" + "checksum": "f735cebb8c673daee580c4026eb8cdef2ebdb5232a3a3591350dda3aa231fc75", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/subfinder/metadata.json b/plugins/subfinder/metadata.json index e62a3f73..b87baa29 100644 --- a/plugins/subfinder/metadata.json +++ b/plugins/subfinder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "subfinder" @@ -52,5 +52,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "a1cb24265eea66c6059544857e22a5a5cd6c4fc0c1049b329f1b1f970d516312" + "checksum": "a1cb24265eea66c6059544857e22a5a5cd6c4fc0c1049b329f1b1f970d516312", + "capabilities": [ + "network" + ] } diff --git a/plugins/theharvester/metadata.json b/plugins/theharvester/metadata.json index bde2cdd9..1c4e8fac 100644 --- a/plugins/theharvester/metadata.json +++ b/plugins/theharvester/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "theHarvester" @@ -53,5 +53,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "23786b990637464c07c163aa714ba1ce60f47a94cc2d64e4beb42c419ec2da56" + "checksum": "23786b990637464c07c163aa714ba1ce60f47a94cc2d64e4beb42c419ec2da56", + "capabilities": [ + "network" + ] } diff --git a/plugins/tls_inspector/metadata.json b/plugins/tls_inspector/metadata.json index eace0c65..e1f14d64 100644 --- a/plugins/tls_inspector/metadata.json +++ b/plugins/tls_inspector/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd10", + "icon": "๐Ÿ”", "engine": { "type": "cli", "binary": "openssl" @@ -137,5 +137,8 @@ "system_packages": [] }, "docker_image": "alpine:latest", - "checksum": "fac3b48892e86d2727495b46df5cf0b6ff6bcc72f1c40f1d218ef9afc79e1615" + "checksum": "fac3b48892e86d2727495b46df5cf0b6ff6bcc72f1c40f1d218ef9afc79e1615", + "capabilities": [ + "network" + ] } diff --git a/plugins/uncover/metadata.json b/plugins/uncover/metadata.json index 451b8202..86279893 100644 --- a/plugins/uncover/metadata.json +++ b/plugins/uncover/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "uncover" @@ -63,5 +63,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "f66a6bcd537fe6a4c23d38fe7daeac3174e088c158de8dbf98230dd7891c5026" + "checksum": "f66a6bcd537fe6a4c23d38fe7daeac3174e088c158de8dbf98230dd7891c5026", + "capabilities": [ + "network" + ] } diff --git a/plugins/url-fuzzer-2/metadata.json b/plugins/url-fuzzer-2/metadata.json index f77673c2..cb0626c0 100644 --- a/plugins/url-fuzzer-2/metadata.json +++ b/plugins/url-fuzzer-2/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "ffuf" @@ -74,5 +74,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "92487e75582ea0ca9680f5626a2aa93bcb8ecc72c64a35a78f8704175033ddc5" + "checksum": "92487e75582ea0ca9680f5626a2aa93bcb8ecc72c64a35a78f8704175033ddc5", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/urlfinder/metadata.json b/plugins/urlfinder/metadata.json index d3c063ac..8ff200e0 100644 --- a/plugins/urlfinder/metadata.json +++ b/plugins/urlfinder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "urlfinder" @@ -52,5 +52,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "ad631fba1c00d10388761902571c4d35d69de55e1b7a1419ac301f5f3157447d" + "checksum": "ad631fba1c00d10388761902571c4d35d69de55e1b7a1419ac301f5f3157447d", + "capabilities": [ + "network" + ] } diff --git a/plugins/virtual-host-finder/metadata.json b/plugins/virtual-host-finder/metadata.json index eb03eec0..5e8494aa 100644 --- a/plugins/virtual-host-finder/metadata.json +++ b/plugins/virtual-host-finder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "ffuf" @@ -66,5 +66,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "4f1b41fc1b9a02462f05122f660660cb096b8762a9a7986064e4acf6da9c5175" + "checksum": "4f1b41fc1b9a02462f05122f660660cb096b8762a9a7986064e4acf6da9c5175", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/volatility/metadata.json b/plugins/volatility/metadata.json index 125555fc..3f1a8380 100644 --- a/plugins/volatility/metadata.json +++ b/plugins/volatility/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83e\udde0", + "icon": "๐Ÿง ", "engine": { "type": "cli", "binary": "volatility3" @@ -66,5 +66,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "b4c467aea9e0da68860eb34289de63f16d8fb03851cb5e85c1895345e3d892fc" + "checksum": "b4c467aea9e0da68860eb34289de63f16d8fb03851cb5e85c1895345e3d892fc", + "capabilities": [ + "filesystem", + "intrusive" + ] } diff --git a/plugins/waf-detection/metadata.json b/plugins/waf-detection/metadata.json index bd07a22d..7c477731 100644 --- a/plugins/waf-detection/metadata.json +++ b/plugins/waf-detection/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "wafw00f" @@ -54,5 +54,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "018805888afabfb26b74dfe7bc04b5fd99507c0db53eec802bf7381b6ccff12d" + "checksum": "018805888afabfb26b74dfe7bc04b5fd99507c0db53eec802bf7381b6ccff12d", + "capabilities": [ + "network" + ] } diff --git a/plugins/waf_detector/metadata.json b/plugins/waf_detector/metadata.json index b642dcda..ba151906 100644 --- a/plugins/waf_detector/metadata.json +++ b/plugins/waf_detector/metadata.json @@ -54,5 +54,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "60b54af15ff7bad498a02cdbf08ee8611622e117944a3a65301cb3cae1582bb2" + "checksum": "60b54af15ff7bad498a02cdbf08ee8611622e117944a3a65301cb3cae1582bb2", + "capabilities": [ + "network" + ] } diff --git a/plugins/website-recon-2/metadata.json b/plugins/website-recon-2/metadata.json index f5865d5a..eabee6a8 100644 --- a/plugins/website-recon-2/metadata.json +++ b/plugins/website-recon-2/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "httpx" @@ -60,5 +60,8 @@ "python_packages": [], "system_packages": [] }, - "checksum": "53ac15d9af192a5ac70225f2faaf1f3c086868ea67438d9b588de6645555ef01" + "checksum": "53ac15d9af192a5ac70225f2faaf1f3c086868ea67438d9b588de6645555ef01", + "capabilities": [ + "network" + ] } diff --git a/plugins/whois_lookup/metadata.json b/plugins/whois_lookup/metadata.json index f4c942fa..0493d643 100644 --- a/plugins/whois_lookup/metadata.json +++ b/plugins/whois_lookup/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.in" }, "license": "MIT", - "icon": "\ud83d\udd0e", + "icon": "๐Ÿ”Ž", "engine": { "type": "cli", "binary": "python3" @@ -58,5 +58,8 @@ "system_packages": [] }, "docker_image": "alpine:latest", - "checksum": "ecda30e7a979e1a0200d6f58c6c01fe6bae7dbe985cc0f47c5047165c46f3a53" + "checksum": "ecda30e7a979e1a0200d6f58c6c01fe6bae7dbe985cc0f47c5047165c46f3a53", + "capabilities": [ + "network" + ] } diff --git a/plugins/wpscan/metadata.json b/plugins/wpscan/metadata.json index 51e1ec87..6ac889c4 100644 --- a/plugins/wpscan/metadata.json +++ b/plugins/wpscan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udcdd", + "icon": "๐Ÿ“", "engine": { "type": "cli", "binary": "wpscan" @@ -85,5 +85,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "34320c076a9f36b37394d19a010e9f3fddbb9d08df8b50fb016fe8d0c748069e" + "checksum": "34320c076a9f36b37394d19a010e9f3fddbb9d08df8b50fb016fe8d0c748069e", + "capabilities": [ + "network", + "intrusive" + ] } diff --git a/plugins/xss_exploiter/metadata.json b/plugins/xss_exploiter/metadata.json index aab83857..1171ce6b 100644 --- a/plugins/xss_exploiter/metadata.json +++ b/plugins/xss_exploiter/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -57,5 +57,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "0a720290e3991401b7a44becae9bb9c748ad782ae57d89443847c59913d10aab" + "checksum": "0a720290e3991401b7a44becae9bb9c748ad782ae57d89443847c59913d10aab", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/plugins/yara_scan/metadata.json b/plugins/yara_scan/metadata.json index 26512737..7636dd3b 100644 --- a/plugins/yara_scan/metadata.json +++ b/plugins/yara_scan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udd2c", + "icon": "๐Ÿ”ฌ", "engine": { "type": "cli", "binary": "yara" @@ -71,5 +71,9 @@ "python_packages": [], "system_packages": [] }, - "checksum": "411d3d894c61a405a16dcbe86ae9075720922b10e238161284def026f5e0baac" + "checksum": "411d3d894c61a405a16dcbe86ae9075720922b10e238161284def026f5e0baac", + "capabilities": [ + "filesystem", + "intrusive" + ] } diff --git a/plugins/zap_scanner/metadata.json b/plugins/zap_scanner/metadata.json index 45ee7da3..bb1cbce6 100644 --- a/plugins/zap_scanner/metadata.json +++ b/plugins/zap_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "\ud83d\udee0\ufe0f", + "icon": "๐Ÿ› ๏ธ", "engine": { "type": "cli", "binary": "python3" @@ -57,5 +57,10 @@ "python_packages": [], "system_packages": [] }, - "checksum": "90b303f8523cee4e707be0003b9506dbb443d3e90ab41cd8f9c59f1e2654fe55" + "checksum": "90b303f8523cee4e707be0003b9506dbb443d3e90ab41cd8f9c59f1e2654fe55", + "capabilities": [ + "network", + "intrusive", + "exploit" + ] } diff --git a/testing/backend/unit/test_capabilities.py b/testing/backend/unit/test_capabilities.py new file mode 100644 index 00000000..de838562 --- /dev/null +++ b/testing/backend/unit/test_capabilities.py @@ -0,0 +1,253 @@ +""" +Unit tests for the per-plugin capability enforcement system. + +Covers: +- CapabilityEnforcer allows plugins whose capabilities are not denied +- CapabilityEnforcer blocks plugins that require a denied capability +- Partial denial: only the matching capability triggers a block +- Empty denied set: all plugins pass +- Legacy plugins (no declared capabilities) fall back to safety-level implied set +- validate_capability_list rejects unknown tokens +- effective_capabilities logic for declared vs implied sets +- Exploit capability is correctly blocked/allowed +- CapabilityDeniedError carries the right metadata +- build_enforcer_from_settings round-trips through Settings +""" + +import pytest +from unittest.mock import patch + +from backend.secuscan.capabilities import ( + ALL_CAPABILITIES, + Capability, + CapabilityDeniedError, + CapabilityEnforcer, + effective_capabilities, + validate_capability_list, + build_enforcer_from_settings, +) + + +# --------------------------------------------------------------------------- +# validate_capability_list +# --------------------------------------------------------------------------- + + +class TestValidateCapabilityList: + def test_all_known_capabilities_accepted(self): + known = list(ALL_CAPABILITIES) + result = validate_capability_list(known, "test_plugin") + assert set(result) == ALL_CAPABILITIES + + def test_unknown_token_raises(self): + with pytest.raises(ValueError, match="unknown capability"): + validate_capability_list(["network", "xray_vision"], "test_plugin") + + def test_empty_list_is_valid(self): + assert validate_capability_list([], "test_plugin") == [] + + def test_normalises_to_lowercase(self): + result = validate_capability_list(["NETWORK", "Intrusive"], "test_plugin") + assert result == ["network", "intrusive"] + + def test_whitespace_is_stripped(self): + result = validate_capability_list([" network "], "test_plugin") + assert result == ["network"] + + +# --------------------------------------------------------------------------- +# effective_capabilities +# --------------------------------------------------------------------------- + + +class TestEffectiveCapabilities: + def test_explicit_list_returned_as_is(self): + caps = effective_capabilities(["network", "credentials"], "safe", "plugin") + assert caps == {"network", "credentials"} + + def test_empty_declared_list_falls_back_to_implied(self): + # An *empty* list (no capabilities declared) falls back to implied + caps = effective_capabilities(None, "safe", "plugin") + assert "network" in caps + + def test_intrusive_implied_set(self): + caps = effective_capabilities(None, "intrusive", "plugin") + assert caps >= {"network", "intrusive"} + + def test_exploit_implied_set(self): + caps = effective_capabilities(None, "exploit", "plugin") + assert caps >= {"network", "intrusive", "exploit"} + + def test_safe_implied_set(self): + caps = effective_capabilities(None, "safe", "plugin") + assert "network" in caps + assert "exploit" not in caps + + def test_explicit_empty_list_falls_back_to_implied(self): + # An empty list [] means "no explicit declarations" โ†’ use implied + caps = effective_capabilities([], "intrusive", "plugin") + assert "intrusive" in caps + + def test_explicit_list_overrides_implied(self): + # Plugin explicitly declares only "filesystem" even though it's intrusive + caps = effective_capabilities(["filesystem"], "intrusive", "plugin") + assert caps == {"filesystem"} + assert "network" not in caps + + +# --------------------------------------------------------------------------- +# CapabilityEnforcer โ€“ basic allow/deny +# --------------------------------------------------------------------------- + + +class TestCapabilityEnforcerAllow: + def test_no_denied_capabilities_always_passes(self): + enforcer = CapabilityEnforcer(denied_capabilities=[]) + # Should not raise for any combination + enforcer.check("nuclei", ["network", "intrusive"], "intrusive") + enforcer.check("sqlmap", ["network", "intrusive", "exploit"], "exploit") + enforcer.check("nmap", ["network"], "safe") + + def test_unrelated_denial_does_not_block(self): + enforcer = CapabilityEnforcer(denied_capabilities=["docker"]) + enforcer.check("nmap", ["network"], "safe") + + def test_plugin_passes_when_all_its_caps_allowed(self): + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + enforcer.check("nuclei", ["network", "intrusive"], "intrusive") + + def test_exploit_plugin_allowed_when_exploit_not_denied(self): + enforcer = CapabilityEnforcer(denied_capabilities=["docker"]) + enforcer.check("sqlmap", ["network", "intrusive", "exploit"], "exploit") + + +class TestCapabilityEnforcerDeny: + def test_single_denied_capability_blocks(self): + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check("sqlmap", ["network", "intrusive", "exploit"], "exploit") + assert "exploit" in str(exc_info.value) + + def test_blocks_when_any_required_cap_denied(self): + enforcer = CapabilityEnforcer(denied_capabilities=["intrusive"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("nuclei", ["network", "intrusive"], "intrusive") + + def test_multiple_denied_some_matching(self): + enforcer = CapabilityEnforcer(denied_capabilities=["docker", "exploit"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check("zap", ["network", "exploit"], "exploit") + error = exc_info.value + assert "exploit" in error.denied_capabilities + + def test_error_carries_plugin_id(self): + enforcer = CapabilityEnforcer(denied_capabilities=["credentials"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check("ssh_runner", ["network", "credentials"], "intrusive") + assert exc_info.value.plugin_id == "ssh_runner" + + def test_error_carries_denied_set(self): + enforcer = CapabilityEnforcer(denied_capabilities=["exploit", "credentials"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check("metasploit", ["network", "exploit", "credentials"], "exploit") + blocked = exc_info.value.denied_capabilities + assert "exploit" in blocked + assert "credentials" in blocked + + def test_legacy_plugin_no_caps_blocked_via_implied(self): + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + with pytest.raises(CapabilityDeniedError): + # No declared capabilities, safety=exploit โ†’ implied includes exploit + enforcer.check("legacy_exploiter", None, "exploit") + + def test_legacy_intrusive_plugin_blocked(self): + enforcer = CapabilityEnforcer(denied_capabilities=["intrusive"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("old_scanner", None, "intrusive") + + def test_legacy_safe_plugin_not_blocked_by_intrusive_denial(self): + enforcer = CapabilityEnforcer(denied_capabilities=["intrusive"]) + # Safe plugin implied set does not include intrusive + enforcer.check("passive_scanner", None, "safe") + + def test_filesystem_denial_blocks_filesystem_plugin(self): + enforcer = CapabilityEnforcer(denied_capabilities=["filesystem"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("yara_scan", ["filesystem", "intrusive"], "intrusive") + + def test_docker_denial_blocks_docker_plugin(self): + enforcer = CapabilityEnforcer(denied_capabilities=["docker"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("container_scanner", ["network", "docker"], "safe") + + +# --------------------------------------------------------------------------- +# CapabilityEnforcer โ€“ denied set normalisation +# --------------------------------------------------------------------------- + + +class TestCapabilityEnforcerNormalisation: + def test_whitespace_in_denied_list_stripped(self): + enforcer = CapabilityEnforcer(denied_capabilities=[" exploit "]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("sqlmap", ["exploit"], "exploit") + + def test_uppercase_denied_capability_normalised(self): + enforcer = CapabilityEnforcer(denied_capabilities=["EXPLOIT"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("sqlmap", ["exploit"], "exploit") + + def test_empty_strings_in_denied_list_ignored(self): + enforcer = CapabilityEnforcer(denied_capabilities=["", " ", "network"]) + enforcer.check("nmap", ["filesystem"], "safe") + + def test_denied_property_is_frozenset(self): + enforcer = CapabilityEnforcer(denied_capabilities=["network", "exploit"]) + assert isinstance(enforcer.denied, frozenset) + + +# --------------------------------------------------------------------------- +# CapabilityDeniedError +# --------------------------------------------------------------------------- + + +class TestCapabilityDeniedError: + def test_message_contains_plugin_id(self): + err = CapabilityDeniedError("my_plugin", {"exploit"}) + assert "my_plugin" in str(err) + + def test_message_contains_capability_name(self): + err = CapabilityDeniedError("my_plugin", {"credentials", "exploit"}) + assert "credentials" in str(err) + assert "exploit" in str(err) + + def test_is_permission_error(self): + err = CapabilityDeniedError("my_plugin", {"exploit"}) + assert isinstance(err, PermissionError) + + +# --------------------------------------------------------------------------- +# build_enforcer_from_settings +# --------------------------------------------------------------------------- + + +class TestBuildEnforcerFromSettings: + def test_empty_denied_capabilities_in_settings(self): + with patch("backend.secuscan.config.settings") as mock_settings: + mock_settings.denied_capabilities = [] + enforcer = build_enforcer_from_settings() + assert enforcer.denied == frozenset() + + def test_denied_capabilities_propagated_from_settings(self): + with patch("backend.secuscan.config.settings") as mock_settings: + mock_settings.denied_capabilities = ["exploit", "docker"] + enforcer = build_enforcer_from_settings() + assert "exploit" in enforcer.denied + assert "docker" in enforcer.denied + + def test_enforcer_blocks_based_on_settings(self): + with patch("backend.secuscan.config.settings") as mock_settings: + mock_settings.denied_capabilities = ["exploit"] + enforcer = build_enforcer_from_settings() + with pytest.raises(CapabilityDeniedError): + enforcer.check("sqlmap", ["network", "exploit"], "exploit") From 1081c3479d73b1c04bc718c413dafcf2a1fcf55e Mon Sep 17 00:00:00 2001 From: Vexx Date: Fri, 29 May 2026 00:44:55 +0530 Subject: [PATCH 2/5] fix(capabilities): revert bulk plugin metadata, fix duplicate WAF name, document backward compat Revert all 60 plugin metadata.json changes: adding capabilities to every plugin file invalidated their checksums (capabilities is included in the digest computation), causing all plugins to fail to load and collapsing the test suite. Enforcement works for all existing plugins via the safety-level implied-capability fallback, so no metadata changes are needed for the feature to be active. Fix duplicate plugin display name: both waf-detection and waf_detector had the name "WAF Detector"; rename waf_detector to "WAF Detector (wafw00f)" and refresh its checksum. Add explicit backward-compatibility documentation to capabilities.py explaining the implied-set fallback and the checksum-refresh requirement for plugin authors who opt in to explicit capability declarations. --- backend/secuscan/capabilities.py | 23 +++++++ plugins/amass/metadata.json | 7 +- plugins/api_scanner/metadata.json | 8 +-- plugins/cloud_scanner/metadata.json | 8 +-- plugins/cloud_storage_auditor/metadata.json | 7 +- plugins/code_analyzer/metadata.json | 7 +- plugins/container_scanner/metadata.json | 8 +-- plugins/crawler/metadata.json | 8 +-- plugins/dir_discovery/metadata.json | 8 +-- plugins/dns_enum/metadata.json | 7 +- plugins/dnsx/metadata.json | 7 +- plugins/domain-finder/metadata.json | 7 +- plugins/droopescan/metadata.json | 8 +-- plugins/fuzzer/metadata.json | 9 +-- plugins/google-dorking/metadata.json | 7 +- plugins/hashcat/metadata.json | 9 +-- plugins/http_inspector/metadata.json | 7 +- plugins/http_request_logger/metadata.json | 8 +-- plugins/httpx/metadata.json | 7 +- plugins/iac_scanner/metadata.json | 7 +- plugins/icmp_ping/metadata.json | 7 +- plugins/joomscan/metadata.json | 8 +-- plugins/katana/metadata.json | 8 +-- plugins/kubernetes_scanner/metadata.json | 8 +-- plugins/metasploit/metadata.json | 9 +-- plugins/network_scanner/metadata.json | 8 +-- plugins/nikto/metadata.json | 8 +-- plugins/nmap/metadata.json | 7 +- plugins/nuclei/metadata.json | 8 +-- plugins/password_auditor/metadata.json | 9 +-- plugins/people-email-discovery/metadata.json | 7 +- plugins/port-scanner/metadata.json | 8 +-- plugins/scapy_recon/metadata.json | 7 +- plugins/secret_scanner/metadata.json | 7 +- plugins/sharepoint_scanner/metadata.json | 8 +-- plugins/sitemap_gen/metadata.json | 8 +-- plugins/sniper/metadata.json | 9 +-- plugins/spider/metadata.json | 8 +-- plugins/sqli_checker/metadata.json | 8 +-- plugins/sqli_exploiter/metadata.json | 9 +-- plugins/sqlmap/metadata.json | 9 +-- plugins/ssh_runner/metadata.json | 9 +-- plugins/subdomain-finder/metadata.json | 7 +- plugins/subdomain_discovery/metadata.json | 7 +- plugins/subdomain_takeover/metadata.json | 8 +-- plugins/subfinder/metadata.json | 7 +- plugins/theharvester/metadata.json | 7 +- plugins/tls_inspector/metadata.json | 7 +- plugins/uncover/metadata.json | 7 +- plugins/url-fuzzer-2/metadata.json | 8 +-- plugins/urlfinder/metadata.json | 7 +- plugins/virtual-host-finder/metadata.json | 8 +-- plugins/volatility/metadata.json | 8 +-- plugins/waf-detection/metadata.json | 7 +- plugins/waf_detector/metadata.json | 71 ++++++++++---------- plugins/website-recon-2/metadata.json | 7 +- plugins/whois_lookup/metadata.json | 7 +- plugins/wpscan/metadata.json | 8 +-- plugins/xss_exploiter/metadata.json | 9 +-- plugins/yara_scan/metadata.json | 8 +-- plugins/zap_scanner/metadata.json | 9 +-- 61 files changed, 175 insertions(+), 376 deletions(-) diff --git a/backend/secuscan/capabilities.py b/backend/secuscan/capabilities.py index 18cc8598..fe530205 100644 --- a/backend/secuscan/capabilities.py +++ b/backend/secuscan/capabilities.py @@ -14,6 +14,29 @@ credentials - plugin pulls secrets from the credential vault intrusive - plugin performs active probing that may affect target systems exploit - plugin attempts to exploit vulnerabilities (highest risk, opt-in only) + +Backward compatibility / migration +----------------------------------- +Plugins that do **not** declare a ``capabilities`` list (i.e. all plugins that +pre-date this feature) are **not broken**. Instead, an implied capability set is +derived from their ``safety.level`` field: + + safe โ†’ ["network"] + intrusive โ†’ ["network", "intrusive"] + exploit โ†’ ["network", "intrusive", "exploit"] + +This means: + +* Existing plugins without a ``capabilities`` field continue to load and execute + normally. No plugin metadata files need to be updated for the enforcement + system to become active. +* Operators can still deny capabilities (e.g. ``SECUSCAN_DENIED_CAPABILITIES=exploit``) + and all exploit-level plugins will be blocked even if they lack an explicit + ``capabilities`` declaration. +* Plugin authors are encouraged to add an explicit ``capabilities`` list to their + metadata.json so operators have fine-grained visibility. After adding or + changing the ``capabilities`` field the plugin checksum must be regenerated + (run ``python -m backend.secuscan.plugins_validate --refresh ``). """ from __future__ import annotations diff --git a/plugins/amass/metadata.json b/plugins/amass/metadata.json index 079e1b85..9c2021d6 100644 --- a/plugins/amass/metadata.json +++ b/plugins/amass/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "amass" @@ -55,8 +55,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "d15de85963abb77d529487e5000519ef554171826f37d3a223b44f67725b6312", - "capabilities": [ - "network" - ] + "checksum": "d15de85963abb77d529487e5000519ef554171826f37d3a223b44f67725b6312" } diff --git a/plugins/api_scanner/metadata.json b/plugins/api_scanner/metadata.json index 7167c251..703c959d 100644 --- a/plugins/api_scanner/metadata.json +++ b/plugins/api_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "nuclei" @@ -57,9 +57,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "21271574173b2881599dd103a10c678456031d36fab90cc7b35683413613d154", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "21271574173b2881599dd103a10c678456031d36fab90cc7b35683413613d154" } diff --git a/plugins/cloud_scanner/metadata.json b/plugins/cloud_scanner/metadata.json index e24ee86b..a34b80e9 100644 --- a/plugins/cloud_scanner/metadata.json +++ b/plugins/cloud_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -53,9 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "3485a50459568754718e8494b5e46f141d99df609cb9da1131e19b12fa8dd3c3", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "3485a50459568754718e8494b5e46f141d99df609cb9da1131e19b12fa8dd3c3" } diff --git a/plugins/cloud_storage_auditor/metadata.json b/plugins/cloud_storage_auditor/metadata.json index 141f14e3..67b72213 100644 --- a/plugins/cloud_storage_auditor/metadata.json +++ b/plugins/cloud_storage_auditor/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "uncover" @@ -64,8 +64,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "8327c42108570fc0b7b5379661f811d3c36b26abf904182e6634bd22145b9c7a", - "capabilities": [ - "network" - ] + "checksum": "8327c42108570fc0b7b5379661f811d3c36b26abf904182e6634bd22145b9c7a" } diff --git a/plugins/code_analyzer/metadata.json b/plugins/code_analyzer/metadata.json index 8eb56eb4..e9f02ea3 100644 --- a/plugins/code_analyzer/metadata.json +++ b/plugins/code_analyzer/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ“ƒ", + "icon": "\ud83d\udcc3", "engine": { "type": "cli", "binary": "bandit" @@ -88,8 +88,5 @@ "system_packages": [] }, "docker_image": "pyfound/bandit:latest", - "checksum": "7daaed25ba96309668e1792c653abeec3f6d407324878b08e1d7a607a5f728db", - "capabilities": [ - "filesystem" - ] + "checksum": "7daaed25ba96309668e1792c653abeec3f6d407324878b08e1d7a607a5f728db" } diff --git a/plugins/container_scanner/metadata.json b/plugins/container_scanner/metadata.json index c7502c87..792f5476 100644 --- a/plugins/container_scanner/metadata.json +++ b/plugins/container_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "Apache-2.0", - "icon": "๐Ÿ“ฆ", + "icon": "\ud83d\udce6", "engine": { "type": "cli", "binary": "trivy" @@ -67,9 +67,5 @@ "system_packages": [] }, "docker_image": "aquasec/trivy", - "checksum": "934f32fba8427ff8c93a44f9865612b8c38f2da81af25cd1e0e06dd0ca37b57a", - "capabilities": [ - "network", - "docker" - ] + "checksum": "934f32fba8427ff8c93a44f9865612b8c38f2da81af25cd1e0e06dd0ca37b57a" } diff --git a/plugins/crawler/metadata.json b/plugins/crawler/metadata.json index a30611b0..df72d142 100644 --- a/plugins/crawler/metadata.json +++ b/plugins/crawler/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "katana" @@ -68,9 +68,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "12ba96873661ba9a316f3b8ab5fd75daf9f66a259e4f4a706cc3895a86ef59d4", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "12ba96873661ba9a316f3b8ab5fd75daf9f66a259e4f4a706cc3895a86ef59d4" } diff --git a/plugins/dir_discovery/metadata.json b/plugins/dir_discovery/metadata.json index 2c95aad9..cb168cf7 100644 --- a/plugins/dir_discovery/metadata.json +++ b/plugins/dir_discovery/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ“‚", + "icon": "\ud83d\udcc2", "engine": { "type": "cli", "binary": "ffuf" @@ -185,9 +185,5 @@ "system_packages": [] }, "docker_image": "secuscan/ffuf", - "checksum": "2c66081a09e2c3a765316987257ce7a46116ccd4c19e18fc66cb6b14b60e0742", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "2c66081a09e2c3a765316987257ce7a46116ccd4c19e18fc66cb6b14b60e0742" } diff --git a/plugins/dns_enum/metadata.json b/plugins/dns_enum/metadata.json index d0b28f79..b3dba071 100644 --- a/plugins/dns_enum/metadata.json +++ b/plugins/dns_enum/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ“ฆ", + "icon": "\ud83d\udce6", "engine": { "type": "cli", "binary": "dnsrecon" @@ -88,8 +88,5 @@ ] }, "docker_image": "darkoperator/dnsrecon:latest", - "checksum": "a9d9107ad715c542acc252d495337b6f7de790210e3d300971e1aba2e19260f4", - "capabilities": [ - "network" - ] + "checksum": "a9d9107ad715c542acc252d495337b6f7de790210e3d300971e1aba2e19260f4" } diff --git a/plugins/dnsx/metadata.json b/plugins/dnsx/metadata.json index 3515ebe7..d1126c4e 100644 --- a/plugins/dnsx/metadata.json +++ b/plugins/dnsx/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "dnsx" @@ -52,8 +52,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "8f1a5ecf2f4e2192010470c00853682ab7f70714325cdc8d59c8000a1598f44a", - "capabilities": [ - "network" - ] + "checksum": "8f1a5ecf2f4e2192010470c00853682ab7f70714325cdc8d59c8000a1598f44a" } diff --git a/plugins/domain-finder/metadata.json b/plugins/domain-finder/metadata.json index 1f4e1df9..00e40a60 100644 --- a/plugins/domain-finder/metadata.json +++ b/plugins/domain-finder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "amass" @@ -55,8 +55,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "b8bc0ebc7ba6b739c3f81be706c98cc354e9ffdad1468d62ef00600a3e8907d6", - "capabilities": [ - "network" - ] + "checksum": "b8bc0ebc7ba6b739c3f81be706c98cc354e9ffdad1468d62ef00600a3e8907d6" } diff --git a/plugins/droopescan/metadata.json b/plugins/droopescan/metadata.json index bd9e705a..e7a36fc0 100644 --- a/plugins/droopescan/metadata.json +++ b/plugins/droopescan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ›ก๏ธ", + "icon": "\ud83d\udee1\ufe0f", "engine": { "type": "cli", "binary": "droopescan" @@ -60,9 +60,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "d06997f1c1fc57665492e772ff146100ba387bbd30438f9493998eae94f71645", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "d06997f1c1fc57665492e772ff146100ba387bbd30438f9493998eae94f71645" } diff --git a/plugins/fuzzer/metadata.json b/plugins/fuzzer/metadata.json index 393a25c4..da88c931 100644 --- a/plugins/fuzzer/metadata.json +++ b/plugins/fuzzer/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -53,10 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "eaac1036bce6e14dbc2ebd48ec97c262461bae5c1611510c51581d9b907a0fe4", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "eaac1036bce6e14dbc2ebd48ec97c262461bae5c1611510c51581d9b907a0fe4" } diff --git a/plugins/google-dorking/metadata.json b/plugins/google-dorking/metadata.json index 38d855c6..8848acf2 100644 --- a/plugins/google-dorking/metadata.json +++ b/plugins/google-dorking/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "python3" @@ -52,8 +52,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "7284033396542bcc591307d2339b525d7eebd0da5365a252b79ff21a0160f208", - "capabilities": [ - "network" - ] + "checksum": "7284033396542bcc591307d2339b525d7eebd0da5365a252b79ff21a0160f208" } diff --git a/plugins/hashcat/metadata.json b/plugins/hashcat/metadata.json index dde82353..f50e164c 100644 --- a/plugins/hashcat/metadata.json +++ b/plugins/hashcat/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "โšก", + "icon": "\u26a1", "engine": { "type": "cli", "binary": "hashcat" @@ -96,10 +96,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "3c62c9b154b651149426d4fa7fefc9fdfb16f05c20e0850b0ed63a8f44c22b89", - "capabilities": [ - "filesystem", - "intrusive", - "exploit" - ] + "checksum": "3c62c9b154b651149426d4fa7fefc9fdfb16f05c20e0850b0ed63a8f44c22b89" } diff --git a/plugins/http_inspector/metadata.json b/plugins/http_inspector/metadata.json index 22eb5a71..814b4855 100644 --- a/plugins/http_inspector/metadata.json +++ b/plugins/http_inspector/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐ŸŒ", + "icon": "\ud83c\udf10", "engine": { "type": "python", "entrypoint": "python3 -m httpx" @@ -92,8 +92,5 @@ "system_packages": [] }, "docker_image": "curlimages/curl", - "checksum": "435436b8dbde27b30f545c3f5701089db880794a8a3f3a47e08cf2e6dbffa447", - "capabilities": [ - "network" - ] + "checksum": "435436b8dbde27b30f545c3f5701089db880794a8a3f3a47e08cf2e6dbffa447" } diff --git a/plugins/http_request_logger/metadata.json b/plugins/http_request_logger/metadata.json index bd77f674..ac8bfe7b 100644 --- a/plugins/http_request_logger/metadata.json +++ b/plugins/http_request_logger/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "httpx" @@ -60,9 +60,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "1914157eced0f91a400d858291ca4dc5b9fe9c5b286d4178339deb7fc7f4fb06", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "1914157eced0f91a400d858291ca4dc5b9fe9c5b286d4178339deb7fc7f4fb06" } diff --git a/plugins/httpx/metadata.json b/plugins/httpx/metadata.json index 518aaeb6..8a2a7bee 100644 --- a/plugins/httpx/metadata.json +++ b/plugins/httpx/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "httpx" @@ -55,8 +55,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "b74defa2b8d5595ae6a8fbd8020c35ce05a214beb65d11f31847ae28d6517e2f", - "capabilities": [ - "network" - ] + "checksum": "b74defa2b8d5595ae6a8fbd8020c35ce05a214beb65d11f31847ae28d6517e2f" } diff --git a/plugins/iac_scanner/metadata.json b/plugins/iac_scanner/metadata.json index f87461e1..e8346a24 100644 --- a/plugins/iac_scanner/metadata.json +++ b/plugins/iac_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -54,8 +54,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "35c01e2544805a78b46b75fb168a98a486a7b80dd926a21ca3c91d721e02af78", - "capabilities": [ - "filesystem" - ] + "checksum": "35c01e2544805a78b46b75fb168a98a486a7b80dd926a21ca3c91d721e02af78" } diff --git a/plugins/icmp_ping/metadata.json b/plugins/icmp_ping/metadata.json index d1cc1078..f1d96bfd 100644 --- a/plugins/icmp_ping/metadata.json +++ b/plugins/icmp_ping/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "ping" @@ -69,8 +69,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "bee1387121a61c22a4d5c8d8c99024ff3ed0a45397dc240b6507792b1193d676", - "capabilities": [ - "network" - ] + "checksum": "bee1387121a61c22a4d5c8d8c99024ff3ed0a45397dc240b6507792b1193d676" } diff --git a/plugins/joomscan/metadata.json b/plugins/joomscan/metadata.json index 2634854b..05fcb09c 100644 --- a/plugins/joomscan/metadata.json +++ b/plugins/joomscan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿท๏ธ", + "icon": "\ud83c\udff7\ufe0f", "engine": { "type": "cli", "binary": "joomscan" @@ -56,9 +56,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "fc1b9feab32f4f65d995b6613ba450d32d0f318cb5209e6144be3e961e38e392", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "fc1b9feab32f4f65d995b6613ba450d32d0f318cb5209e6144be3e961e38e392" } diff --git a/plugins/katana/metadata.json b/plugins/katana/metadata.json index 050ce28e..f3e71f40 100644 --- a/plugins/katana/metadata.json +++ b/plugins/katana/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "katana" @@ -53,9 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "6c03736ab9bf88e97d7656ae4816922f68c3ef2a07a66b1ccfa7cb2c570b6a1a", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "6c03736ab9bf88e97d7656ae4816922f68c3ef2a07a66b1ccfa7cb2c570b6a1a" } diff --git a/plugins/kubernetes_scanner/metadata.json b/plugins/kubernetes_scanner/metadata.json index 42b4628a..5e324c89 100644 --- a/plugins/kubernetes_scanner/metadata.json +++ b/plugins/kubernetes_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -53,9 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "830b2c5f4e397d2d2ad28f71ec8d1a3a5f9bd3e439d8318f71a5b8a371f7ccdb", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "830b2c5f4e397d2d2ad28f71ec8d1a3a5f9bd3e439d8318f71a5b8a371f7ccdb" } diff --git a/plugins/metasploit/metadata.json b/plugins/metasploit/metadata.json index 61059abc..d038e9c9 100644 --- a/plugins/metasploit/metadata.json +++ b/plugins/metasploit/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿš€", + "icon": "\ud83d\ude80", "engine": { "type": "cli", "binary": "msfconsole" @@ -70,10 +70,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "80a4db676d52e83a27dcb1e51780678f8b8476b5745ff81b2896b35662cf2767", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "80a4db676d52e83a27dcb1e51780678f8b8476b5745ff81b2896b35662cf2767" } diff --git a/plugins/network_scanner/metadata.json b/plugins/network_scanner/metadata.json index 589e94e4..0593f1c6 100644 --- a/plugins/network_scanner/metadata.json +++ b/plugins/network_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "nmap" @@ -65,9 +65,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "54d4478eaeec8b047841ac1ab9971842b02cafa6c5895b82469c2c3e2ce97652", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "54d4478eaeec8b047841ac1ab9971842b02cafa6c5895b82469c2c3e2ce97652" } diff --git a/plugins/nikto/metadata.json b/plugins/nikto/metadata.json index e0602305..99ab4878 100644 --- a/plugins/nikto/metadata.json +++ b/plugins/nikto/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "nikto" @@ -333,9 +333,5 @@ ] }, "docker_image": "sullo/nikto", - "checksum": "0fb0bf4aa20a24934291e650701373b2501b2d44a1d1b0e9445ff9bf84857315", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "0fb0bf4aa20a24934291e650701373b2501b2d44a1d1b0e9445ff9bf84857315" } diff --git a/plugins/nmap/metadata.json b/plugins/nmap/metadata.json index 0f0db704..56ca2ef8 100644 --- a/plugins/nmap/metadata.json +++ b/plugins/nmap/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”", + "icon": "\ud83d\udd0d", "engine": { "type": "cli", "binary": "nmap" @@ -202,8 +202,5 @@ "system_packages": [] }, "docker_image": "instrumentisto/nmap", - "checksum": "d0870be0230b0553a8fa662a2e1aaeee20a7d81fc603a40e14d4560bc9e21c4c", - "capabilities": [ - "network" - ] + "checksum": "d0870be0230b0553a8fa662a2e1aaeee20a7d81fc603a40e14d4560bc9e21c4c" } diff --git a/plugins/nuclei/metadata.json b/plugins/nuclei/metadata.json index 612e4016..e0ec4ea1 100644 --- a/plugins/nuclei/metadata.json +++ b/plugins/nuclei/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿงฌ", + "icon": "\ud83e\uddec", "engine": { "type": "cli", "binary": "nuclei" @@ -130,9 +130,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "ecdacab5d26ebd95d07fe61cc781768e046ab004438a254f2ed80efd167c728d", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "ecdacab5d26ebd95d07fe61cc781768e046ab004438a254f2ed80efd167c728d" } diff --git a/plugins/password_auditor/metadata.json b/plugins/password_auditor/metadata.json index 904d3daf..61ea6071 100644 --- a/plugins/password_auditor/metadata.json +++ b/plugins/password_auditor/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -53,10 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "d3606fc8426a11a890874be7159b75b534da6c0f5aa56f8291578feb29934358", - "capabilities": [ - "network", - "intrusive", - "credentials" - ] + "checksum": "d3606fc8426a11a890874be7159b75b534da6c0f5aa56f8291578feb29934358" } diff --git a/plugins/people-email-discovery/metadata.json b/plugins/people-email-discovery/metadata.json index 2f2ea4e2..dc7ccf94 100644 --- a/plugins/people-email-discovery/metadata.json +++ b/plugins/people-email-discovery/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "theHarvester" @@ -53,8 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "0f0889733638a60dc134a94a668c6683cbdb435560d60f61e7735d47114334c6", - "capabilities": [ - "network" - ] + "checksum": "0f0889733638a60dc134a94a668c6683cbdb435560d60f61e7735d47114334c6" } diff --git a/plugins/port-scanner/metadata.json b/plugins/port-scanner/metadata.json index 1d985c97..c305a172 100644 --- a/plugins/port-scanner/metadata.json +++ b/plugins/port-scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "nmap" @@ -65,9 +65,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "6d9c153513574048757d52702889c4e9f90819cb1d418970c60782f0fadd31b6", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "6d9c153513574048757d52702889c4e9f90819cb1d418970c60782f0fadd31b6" } diff --git a/plugins/scapy_recon/metadata.json b/plugins/scapy_recon/metadata.json index d25a6951..879b86a0 100644 --- a/plugins/scapy_recon/metadata.json +++ b/plugins/scapy_recon/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ“ก", + "icon": "\ud83d\udce1", "engine": { "type": "cli", "binary": "python3" @@ -86,8 +86,5 @@ ] }, "docker_image": "secdev/scapy:latest", - "checksum": "688f16afba16bcf122728cd46a060717622ffeb96dad29b964593b9025b79682", - "capabilities": [ - "network" - ] + "checksum": "688f16afba16bcf122728cd46a060717622ffeb96dad29b964593b9025b79682" } diff --git a/plugins/secret_scanner/metadata.json b/plugins/secret_scanner/metadata.json index d52b74b6..1dd98e84 100644 --- a/plugins/secret_scanner/metadata.json +++ b/plugins/secret_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”‘", + "icon": "\ud83d\udd11", "engine": { "type": "cli", "binary": "gitleaks" @@ -66,8 +66,5 @@ "system_packages": [] }, "docker_image": "zricethezav/gitleaks:latest", - "checksum": "4287007a8a060b41dea04e2f3e2ce98c4db0fefa8e0cca318168bc0ce4aaa032", - "capabilities": [ - "filesystem" - ] + "checksum": "4287007a8a060b41dea04e2f3e2ce98c4db0fefa8e0cca318168bc0ce4aaa032" } diff --git a/plugins/sharepoint_scanner/metadata.json b/plugins/sharepoint_scanner/metadata.json index 4aafc162..2fb76177 100644 --- a/plugins/sharepoint_scanner/metadata.json +++ b/plugins/sharepoint_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "nuclei" @@ -57,9 +57,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "ba08df50150ea7d4df2784f4a5ba4d257a1669dd98d532f4fab6d84edc83fb0a", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "ba08df50150ea7d4df2784f4a5ba4d257a1669dd98d532f4fab6d84edc83fb0a" } diff --git a/plugins/sitemap_gen/metadata.json b/plugins/sitemap_gen/metadata.json index 6e899578..163b50d3 100644 --- a/plugins/sitemap_gen/metadata.json +++ b/plugins/sitemap_gen/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "katana" @@ -68,9 +68,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "1555d4793ba9f18eaec55b737fcf28ca7f4236d2b253b1637485ae952409d512", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "1555d4793ba9f18eaec55b737fcf28ca7f4236d2b253b1637485ae952409d512" } diff --git a/plugins/sniper/metadata.json b/plugins/sniper/metadata.json index a77d333a..0f82dd94 100644 --- a/plugins/sniper/metadata.json +++ b/plugins/sniper/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -53,10 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "fe09afd19995b8a925c2be8e35ec10779925a58380991d904b7fa1a6d46d3f87", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "fe09afd19995b8a925c2be8e35ec10779925a58380991d904b7fa1a6d46d3f87" } diff --git a/plugins/spider/metadata.json b/plugins/spider/metadata.json index ec96309d..e18599a0 100644 --- a/plugins/spider/metadata.json +++ b/plugins/spider/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "katana" @@ -69,9 +69,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "8b0111447c9ebc7d1487e2a8fafd4b2ee9c23e191eab39a124e372632b288254", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "8b0111447c9ebc7d1487e2a8fafd4b2ee9c23e191eab39a124e372632b288254" } diff --git a/plugins/sqli_checker/metadata.json b/plugins/sqli_checker/metadata.json index 060e1101..c9c8f4f4 100644 --- a/plugins/sqli_checker/metadata.json +++ b/plugins/sqli_checker/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿงช", + "icon": "\ud83e\uddea", "engine": { "type": "cli", "binary": "ghauri" @@ -124,9 +124,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "c6fd02c9a458bf582009d1b4838384afb07a64b1a3837fb8d93f6cc3d8dd4dfa", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "c6fd02c9a458bf582009d1b4838384afb07a64b1a3837fb8d93f6cc3d8dd4dfa" } diff --git a/plugins/sqli_exploiter/metadata.json b/plugins/sqli_exploiter/metadata.json index 3c724e6c..5d380e0b 100644 --- a/plugins/sqli_exploiter/metadata.json +++ b/plugins/sqli_exploiter/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "sqlmap" @@ -83,10 +83,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "afc0b21532397682b6d160ebeac0f8d4830f0f95cdde362b2b384a12a38de596", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "afc0b21532397682b6d160ebeac0f8d4830f0f95cdde362b2b384a12a38de596" } diff --git a/plugins/sqlmap/metadata.json b/plugins/sqlmap/metadata.json index 4f794a9f..160cb500 100644 --- a/plugins/sqlmap/metadata.json +++ b/plugins/sqlmap/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "GPLv2", - "icon": "๐Ÿ’‰", + "icon": "\ud83d\udc89", "engine": { "type": "cli", "binary": "sqlmap" @@ -124,10 +124,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "7312d73eb00289dfe5bc1c2cdb6491054a3a348f198a65b36f69f125c9ae3d50", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "7312d73eb00289dfe5bc1c2cdb6491054a3a348f198a65b36f69f125c9ae3d50" } diff --git a/plugins/ssh_runner/metadata.json b/plugins/ssh_runner/metadata.json index f32543d2..6673d75a 100644 --- a/plugins/ssh_runner/metadata.json +++ b/plugins/ssh_runner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ–ฅ๏ธ", + "icon": "\ud83d\udda5\ufe0f", "engine": { "type": "cli", "binary": "ssh" @@ -88,10 +88,5 @@ ] }, "docker_image": "alpine:latest", - "checksum": "6d4495f58635dedb184e763e0e4467adf17dc3754a6b1ed7c2dca0d6b30a88ec", - "capabilities": [ - "network", - "intrusive", - "credentials" - ] + "checksum": "6d4495f58635dedb184e763e0e4467adf17dc3754a6b1ed7c2dca0d6b30a88ec" } diff --git a/plugins/subdomain-finder/metadata.json b/plugins/subdomain-finder/metadata.json index 37c3bcdf..fa5288c2 100644 --- a/plugins/subdomain-finder/metadata.json +++ b/plugins/subdomain-finder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "subfinder" @@ -52,8 +52,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "4570d8047a567282f230d970a59aa2c6d5bbbcfb37dfd4b9182fc9a0ac8172ea", - "capabilities": [ - "network" - ] + "checksum": "4570d8047a567282f230d970a59aa2c6d5bbbcfb37dfd4b9182fc9a0ac8172ea" } diff --git a/plugins/subdomain_discovery/metadata.json b/plugins/subdomain_discovery/metadata.json index dbfd2775..edfe60e6 100644 --- a/plugins/subdomain_discovery/metadata.json +++ b/plugins/subdomain_discovery/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐ŸŒ", + "icon": "\ud83c\udf10", "engine": { "type": "cli", "binary": "subfinder" @@ -88,8 +88,5 @@ "system_packages": [] }, "docker_image": "projectdiscovery/subfinder:latest", - "checksum": "34c426cb7ea665b795595723b7f6f0b4bd302ebd69971268ee9eebde4fbac5d5", - "capabilities": [ - "network" - ] + "checksum": "34c426cb7ea665b795595723b7f6f0b4bd302ebd69971268ee9eebde4fbac5d5" } diff --git a/plugins/subdomain_takeover/metadata.json b/plugins/subdomain_takeover/metadata.json index 5579ddcc..3999e311 100644 --- a/plugins/subdomain_takeover/metadata.json +++ b/plugins/subdomain_takeover/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "subfinder" @@ -53,9 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "f735cebb8c673daee580c4026eb8cdef2ebdb5232a3a3591350dda3aa231fc75", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "f735cebb8c673daee580c4026eb8cdef2ebdb5232a3a3591350dda3aa231fc75" } diff --git a/plugins/subfinder/metadata.json b/plugins/subfinder/metadata.json index b87baa29..e62a3f73 100644 --- a/plugins/subfinder/metadata.json +++ b/plugins/subfinder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "subfinder" @@ -52,8 +52,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "a1cb24265eea66c6059544857e22a5a5cd6c4fc0c1049b329f1b1f970d516312", - "capabilities": [ - "network" - ] + "checksum": "a1cb24265eea66c6059544857e22a5a5cd6c4fc0c1049b329f1b1f970d516312" } diff --git a/plugins/theharvester/metadata.json b/plugins/theharvester/metadata.json index 1c4e8fac..bde2cdd9 100644 --- a/plugins/theharvester/metadata.json +++ b/plugins/theharvester/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "theHarvester" @@ -53,8 +53,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "23786b990637464c07c163aa714ba1ce60f47a94cc2d64e4beb42c419ec2da56", - "capabilities": [ - "network" - ] + "checksum": "23786b990637464c07c163aa714ba1ce60f47a94cc2d64e4beb42c419ec2da56" } diff --git a/plugins/tls_inspector/metadata.json b/plugins/tls_inspector/metadata.json index e1f14d64..eace0c65 100644 --- a/plugins/tls_inspector/metadata.json +++ b/plugins/tls_inspector/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”", + "icon": "\ud83d\udd10", "engine": { "type": "cli", "binary": "openssl" @@ -137,8 +137,5 @@ "system_packages": [] }, "docker_image": "alpine:latest", - "checksum": "fac3b48892e86d2727495b46df5cf0b6ff6bcc72f1c40f1d218ef9afc79e1615", - "capabilities": [ - "network" - ] + "checksum": "fac3b48892e86d2727495b46df5cf0b6ff6bcc72f1c40f1d218ef9afc79e1615" } diff --git a/plugins/uncover/metadata.json b/plugins/uncover/metadata.json index 86279893..451b8202 100644 --- a/plugins/uncover/metadata.json +++ b/plugins/uncover/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "uncover" @@ -63,8 +63,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "f66a6bcd537fe6a4c23d38fe7daeac3174e088c158de8dbf98230dd7891c5026", - "capabilities": [ - "network" - ] + "checksum": "f66a6bcd537fe6a4c23d38fe7daeac3174e088c158de8dbf98230dd7891c5026" } diff --git a/plugins/url-fuzzer-2/metadata.json b/plugins/url-fuzzer-2/metadata.json index cb0626c0..f77673c2 100644 --- a/plugins/url-fuzzer-2/metadata.json +++ b/plugins/url-fuzzer-2/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "ffuf" @@ -74,9 +74,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "92487e75582ea0ca9680f5626a2aa93bcb8ecc72c64a35a78f8704175033ddc5", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "92487e75582ea0ca9680f5626a2aa93bcb8ecc72c64a35a78f8704175033ddc5" } diff --git a/plugins/urlfinder/metadata.json b/plugins/urlfinder/metadata.json index 8ff200e0..d3c063ac 100644 --- a/plugins/urlfinder/metadata.json +++ b/plugins/urlfinder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "urlfinder" @@ -52,8 +52,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "ad631fba1c00d10388761902571c4d35d69de55e1b7a1419ac301f5f3157447d", - "capabilities": [ - "network" - ] + "checksum": "ad631fba1c00d10388761902571c4d35d69de55e1b7a1419ac301f5f3157447d" } diff --git a/plugins/virtual-host-finder/metadata.json b/plugins/virtual-host-finder/metadata.json index 5e8494aa..eb03eec0 100644 --- a/plugins/virtual-host-finder/metadata.json +++ b/plugins/virtual-host-finder/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "ffuf" @@ -66,9 +66,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "4f1b41fc1b9a02462f05122f660660cb096b8762a9a7986064e4acf6da9c5175", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "4f1b41fc1b9a02462f05122f660660cb096b8762a9a7986064e4acf6da9c5175" } diff --git a/plugins/volatility/metadata.json b/plugins/volatility/metadata.json index 3f1a8380..125555fc 100644 --- a/plugins/volatility/metadata.json +++ b/plugins/volatility/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿง ", + "icon": "\ud83e\udde0", "engine": { "type": "cli", "binary": "volatility3" @@ -66,9 +66,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "b4c467aea9e0da68860eb34289de63f16d8fb03851cb5e85c1895345e3d892fc", - "capabilities": [ - "filesystem", - "intrusive" - ] + "checksum": "b4c467aea9e0da68860eb34289de63f16d8fb03851cb5e85c1895345e3d892fc" } diff --git a/plugins/waf-detection/metadata.json b/plugins/waf-detection/metadata.json index 7c477731..bd07a22d 100644 --- a/plugins/waf-detection/metadata.json +++ b/plugins/waf-detection/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "wafw00f" @@ -54,8 +54,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "018805888afabfb26b74dfe7bc04b5fd99507c0db53eec802bf7381b6ccff12d", - "capabilities": [ - "network" - ] + "checksum": "018805888afabfb26b74dfe7bc04b5fd99507c0db53eec802bf7381b6ccff12d" } diff --git a/plugins/waf_detector/metadata.json b/plugins/waf_detector/metadata.json index ba151906..398eedcd 100644 --- a/plugins/waf_detector/metadata.json +++ b/plugins/waf_detector/metadata.json @@ -1,61 +1,58 @@ { - "id": "waf_detector", - "name": "WAF Detection Scanner", - "version": "1.0.0", - "description": "Automatically identify Web Application Firewalls protecting targets.", - "long_description": "Automatically identify Web Application Firewalls protecting targets.", - "category": "robots", "author": { - "name": "SecuScan Contributors", - "email": "dev@secuscan.local" - }, - "license": "MIT", - "icon": "๐Ÿ› ๏ธ", - "engine": { - "type": "cli", - "binary": "wafw00f" + "email": "dev@secuscan.local", + "name": "SecuScan Contributors" }, + "category": "robots", + "checksum": "510412b642b93adaaf5bb782d3d86c4e3bf987e92adaf58bfede7224e5ec7af3", "command_template": [ "wafw00f", "{target}" ], + "dependencies": { + "binaries": [ + "wafw00f" + ], + "python_packages": [], + "system_packages": [] + }, + "description": "Automatically identify Web Application Firewalls protecting targets.", + "engine": { + "binary": "wafw00f", + "type": "cli" + }, "fields": [ { "id": "target", "label": "Target URL", - "type": "string", - "required": true, "placeholder": "https://secuscan.in", + "required": true, + "type": "string", "validation": { - "pattern": "^https?://", - "message": "Must be a valid HTTP(S) URL" + "message": "Must be a valid HTTP(S) URL", + "pattern": "^https?://" } } ], - "presets": { - "default": {} - }, + "icon": "\ud83d\udee0\ufe0f", + "id": "waf_detector", + "license": "MIT", + "long_description": "Automatically identify Web Application Firewalls protecting targets.", + "name": "WAF Detector (wafw00f)", "output": { "format": "text", "parser": "custom" }, + "presets": { + "default": {} + }, "safety": { "level": "safe", - "requires_consent": false, "rate_limit": { - "max_per_hour": 20, - "max_concurrent": 1 - } - }, - "dependencies": { - "binaries": [ - "wafw00f" - ], - "python_packages": [], - "system_packages": [] + "max_concurrent": 1, + "max_per_hour": 20 + }, + "requires_consent": false }, - "checksum": "60b54af15ff7bad498a02cdbf08ee8611622e117944a3a65301cb3cae1582bb2", - "capabilities": [ - "network" - ] -} + "version": "1.0.0" +} \ No newline at end of file diff --git a/plugins/website-recon-2/metadata.json b/plugins/website-recon-2/metadata.json index eabee6a8..f5865d5a 100644 --- a/plugins/website-recon-2/metadata.json +++ b/plugins/website-recon-2/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "httpx" @@ -60,8 +60,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "53ac15d9af192a5ac70225f2faaf1f3c086868ea67438d9b588de6645555ef01", - "capabilities": [ - "network" - ] + "checksum": "53ac15d9af192a5ac70225f2faaf1f3c086868ea67438d9b588de6645555ef01" } diff --git a/plugins/whois_lookup/metadata.json b/plugins/whois_lookup/metadata.json index 0493d643..f4c942fa 100644 --- a/plugins/whois_lookup/metadata.json +++ b/plugins/whois_lookup/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.in" }, "license": "MIT", - "icon": "๐Ÿ”Ž", + "icon": "\ud83d\udd0e", "engine": { "type": "cli", "binary": "python3" @@ -58,8 +58,5 @@ "system_packages": [] }, "docker_image": "alpine:latest", - "checksum": "ecda30e7a979e1a0200d6f58c6c01fe6bae7dbe985cc0f47c5047165c46f3a53", - "capabilities": [ - "network" - ] + "checksum": "ecda30e7a979e1a0200d6f58c6c01fe6bae7dbe985cc0f47c5047165c46f3a53" } diff --git a/plugins/wpscan/metadata.json b/plugins/wpscan/metadata.json index 6ac889c4..51e1ec87 100644 --- a/plugins/wpscan/metadata.json +++ b/plugins/wpscan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ“", + "icon": "\ud83d\udcdd", "engine": { "type": "cli", "binary": "wpscan" @@ -85,9 +85,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "34320c076a9f36b37394d19a010e9f3fddbb9d08df8b50fb016fe8d0c748069e", - "capabilities": [ - "network", - "intrusive" - ] + "checksum": "34320c076a9f36b37394d19a010e9f3fddbb9d08df8b50fb016fe8d0c748069e" } diff --git a/plugins/xss_exploiter/metadata.json b/plugins/xss_exploiter/metadata.json index 1171ce6b..aab83857 100644 --- a/plugins/xss_exploiter/metadata.json +++ b/plugins/xss_exploiter/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -57,10 +57,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "0a720290e3991401b7a44becae9bb9c748ad782ae57d89443847c59913d10aab", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "0a720290e3991401b7a44becae9bb9c748ad782ae57d89443847c59913d10aab" } diff --git a/plugins/yara_scan/metadata.json b/plugins/yara_scan/metadata.json index 7636dd3b..26512737 100644 --- a/plugins/yara_scan/metadata.json +++ b/plugins/yara_scan/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ”ฌ", + "icon": "\ud83d\udd2c", "engine": { "type": "cli", "binary": "yara" @@ -71,9 +71,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "411d3d894c61a405a16dcbe86ae9075720922b10e238161284def026f5e0baac", - "capabilities": [ - "filesystem", - "intrusive" - ] + "checksum": "411d3d894c61a405a16dcbe86ae9075720922b10e238161284def026f5e0baac" } diff --git a/plugins/zap_scanner/metadata.json b/plugins/zap_scanner/metadata.json index bb1cbce6..45ee7da3 100644 --- a/plugins/zap_scanner/metadata.json +++ b/plugins/zap_scanner/metadata.json @@ -10,7 +10,7 @@ "email": "dev@secuscan.local" }, "license": "MIT", - "icon": "๐Ÿ› ๏ธ", + "icon": "\ud83d\udee0\ufe0f", "engine": { "type": "cli", "binary": "python3" @@ -57,10 +57,5 @@ "python_packages": [], "system_packages": [] }, - "checksum": "90b303f8523cee4e707be0003b9506dbb443d3e90ab41cd8f9c59f1e2654fe55", - "capabilities": [ - "network", - "intrusive", - "exploit" - ] + "checksum": "90b303f8523cee4e707be0003b9506dbb443d3e90ab41cd8f9c59f1e2654fe55" } From fd1ff6368b4eb8d4ae5fae1b20ce0b2957827dfa Mon Sep 17 00:00:00 2001 From: Vexx Date: Mon, 1 Jun 2026 22:04:51 +0530 Subject: [PATCH 3/5] feat(capabilities): add explicit capabilities declaration to waf_detector Adds capabilities: ["network"] as a representative example showing how plugins should declare their required capabilities in metadata.json. Updates checksum to match the new canonical metadata. --- plugins/waf_detector/metadata.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/waf_detector/metadata.json b/plugins/waf_detector/metadata.json index 398eedcd..63838633 100644 --- a/plugins/waf_detector/metadata.json +++ b/plugins/waf_detector/metadata.json @@ -4,7 +4,8 @@ "name": "SecuScan Contributors" }, "category": "robots", - "checksum": "510412b642b93adaaf5bb782d3d86c4e3bf987e92adaf58bfede7224e5ec7af3", + "capabilities": ["network"], + "checksum": "fb043331ef88a1ed0fa59ae5d0075e700978f853252ce2859562274a6f43eeb4", "command_template": [ "wafw00f", "{target}" From 220d4acf76e04364da0f0a9576f906f1982c8854 Mon Sep 17 00:00:00 2001 From: Vexx Date: Mon, 1 Jun 2026 23:38:57 +0530 Subject: [PATCH 4/5] fix(capabilities): validate denied tokens at enforcer construction + inventory tests Two changes: 1. CapabilityEnforcer now raises ValueError at construction time when SECUSCAN_DENIED_CAPABILITIES contains an unrecognised token. A typo in the deny list previously silently produced an empty deny set, which would fail to enforce the intended policy. 2. New inventory test suite scans every plugin metadata.json and asserts: - All declared capability tokens are in ALL_CAPABILITIES - Exploit-level plugins that declare explicit capabilities include "exploit" - Intrusive-level plugins that declare explicit capabilities include "intrusive" - The capabilities field is a JSON array of lowercase strings CapabilityEnforcer construction-time validation is also fully covered. --- backend/secuscan/capabilities.py | 21 ++- .../test_plugin_capabilities_inventory.py | 174 ++++++++++++++++++ 2 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 testing/backend/unit/test_plugin_capabilities_inventory.py diff --git a/backend/secuscan/capabilities.py b/backend/secuscan/capabilities.py index fe530205..ffcabbe3 100644 --- a/backend/secuscan/capabilities.py +++ b/backend/secuscan/capabilities.py @@ -131,9 +131,24 @@ class CapabilityEnforcer: def __init__(self, denied_capabilities: Optional[List[str]] = None) -> None: raw = denied_capabilities or [] - self._denied: FrozenSet[str] = frozenset( - tok.strip().lower() for tok in raw if tok.strip() - ) + normalised: List[str] = [] + unknown: List[str] = [] + for tok in raw: + token = tok.strip().lower() + if not token: + continue + if token not in ALL_CAPABILITIES: + unknown.append(tok.strip()) + else: + normalised.append(token) + if unknown: + raise ValueError( + f"SECUSCAN_DENIED_CAPABILITIES contains unrecognised capability tokens: " + f"{unknown!r}. Supported capabilities: {sorted(ALL_CAPABILITIES)}. " + "Fix the typo or remove the unknown token โ€” a misconfigured deny-list " + "silently fails to enforce the intended policy." + ) + self._denied: FrozenSet[str] = frozenset(normalised) if self._denied: logger.info( "CapabilityEnforcer: operator has denied capabilities: %s", diff --git a/testing/backend/unit/test_plugin_capabilities_inventory.py b/testing/backend/unit/test_plugin_capabilities_inventory.py new file mode 100644 index 00000000..9f3497c3 --- /dev/null +++ b/testing/backend/unit/test_plugin_capabilities_inventory.py @@ -0,0 +1,174 @@ +""" +Plugin capability inventory tests (PR #368). + +Verifies: +- Every plugin whose metadata.json declares a `capabilities` field uses only + recognised capability tokens from the ALL_CAPABILITIES set. +- Plugins with safety.level == "exploit" that declare explicit capabilities + include the "exploit" token (high-risk classification consistency). +- CapabilityEnforcer raises ValueError at construction time on an unknown + denied-capability token (operator typo safety). +- CapabilityEnforcer raises on any unknown token even when mixed with valid ones. +- CapabilityEnforcer accepts the empty denied list without error. +- CapabilityEnforcer accepts all recognised capability tokens without error. +""" + +import json +from pathlib import Path + +import pytest + +from backend.secuscan.capabilities import ALL_CAPABILITIES, CapabilityEnforcer + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +_PLUGINS_ROOT = Path(__file__).parent.parent.parent.parent / "plugins" + + +def _iter_plugin_metadata(): + """Yield (plugin_id, metadata_dict) for every plugin with a metadata.json.""" + for plugin_dir in sorted(_PLUGINS_ROOT.iterdir()): + mf = plugin_dir / "metadata.json" + if not mf.is_file(): + continue + try: + meta = json.loads(mf.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + continue + yield meta.get("id", plugin_dir.name), meta + + +def _plugins_with_explicit_capabilities(): + return [ + (pid, meta) + for pid, meta in _iter_plugin_metadata() + if meta.get("capabilities") is not None + ] + + +def _exploit_plugins_with_explicit_capabilities(): + return [ + (pid, meta) + for pid, meta in _plugins_with_explicit_capabilities() + if meta.get("safety", {}).get("level") == "exploit" + ] + + +# --------------------------------------------------------------------------- +# Capability token inventory +# --------------------------------------------------------------------------- + + +class TestPluginCapabilityInventory: + def test_all_declared_capabilities_are_recognised(self): + """Every capability token declared in any plugin metadata.json must be + in ALL_CAPABILITIES; unknown tokens indicate a typo or an undocumented + capability that was never registered.""" + bad: list[str] = [] + for plugin_id, meta in _plugins_with_explicit_capabilities(): + for token in meta.get("capabilities", []): + if token.strip().lower() not in ALL_CAPABILITIES: + bad.append(f"{plugin_id}: unknown token {token!r}") + assert not bad, ( + "Plugin(s) declare unrecognised capability tokens:\n" + + "\n".join(bad) + + f"\nSupported: {sorted(ALL_CAPABILITIES)}" + ) + + def test_exploit_level_plugins_declare_exploit_capability(self): + """Plugins with safety.level == 'exploit' that explicitly declare + capabilities must include 'exploit' โ€” omitting it would let the + operator's deny-list miss them.""" + missing: list[str] = [] + for plugin_id, meta in _exploit_plugins_with_explicit_capabilities(): + caps = [t.strip().lower() for t in meta.get("capabilities", [])] + if "exploit" not in caps: + missing.append(plugin_id) + assert not missing, ( + "Exploit-level plugin(s) declare explicit capabilities but omit " + f"'exploit': {missing}. Add 'exploit' or remove the explicit " + "declaration to fall back to the implied set." + ) + + def test_intrusive_level_plugins_declare_intrusive_when_explicit(self): + """Plugins with safety.level == 'intrusive' that explicitly declare + capabilities must include 'intrusive'.""" + missing: list[str] = [] + for plugin_id, meta in _plugins_with_explicit_capabilities(): + if meta.get("safety", {}).get("level") != "intrusive": + continue + caps = [t.strip().lower() for t in meta.get("capabilities", [])] + if "intrusive" not in caps: + missing.append(plugin_id) + assert not missing, ( + "Intrusive-level plugin(s) declare explicit capabilities but omit " + f"'intrusive': {missing}." + ) + + def test_capabilities_field_is_a_list_when_present(self): + """The `capabilities` field must be a JSON array, not a string or other type.""" + bad: list[str] = [] + for plugin_id, meta in _plugins_with_explicit_capabilities(): + if not isinstance(meta.get("capabilities"), list): + bad.append( + f"{plugin_id}: 'capabilities' is {type(meta['capabilities']).__name__}, " + "expected list" + ) + assert not bad, "\n".join(bad) + + def test_all_capability_tokens_are_lowercase_strings(self): + """Capability tokens should be lowercase strings (normalisation happens at + runtime, but storing mixed-case values is a maintenance footgun).""" + bad: list[str] = [] + for plugin_id, meta in _plugins_with_explicit_capabilities(): + for token in meta.get("capabilities", []): + if not isinstance(token, str) or token != token.lower(): + bad.append(f"{plugin_id}: non-lowercase token {token!r}") + assert not bad, "\n".join(bad) + + +# --------------------------------------------------------------------------- +# CapabilityEnforcer construction-time token validation +# --------------------------------------------------------------------------- + + +class TestCapabilityEnforcerDeniedTokenValidation: + def test_unknown_token_raises_at_construction(self): + with pytest.raises(ValueError, match="unrecognised capability"): + CapabilityEnforcer(denied_capabilities=["netwrk"]) # typo + + def test_mixed_valid_and_unknown_raises(self): + with pytest.raises(ValueError, match="unrecognised capability"): + CapabilityEnforcer(denied_capabilities=["network", "xray_vision"]) + + def test_error_message_names_the_bad_token(self): + with pytest.raises(ValueError, match="xray_vision"): + CapabilityEnforcer(denied_capabilities=["xray_vision"]) + + def test_error_message_lists_supported_capabilities(self): + with pytest.raises(ValueError, match="Supported capabilities"): + CapabilityEnforcer(denied_capabilities=["bad_token"]) + + def test_empty_denied_list_accepted(self): + enforcer = CapabilityEnforcer(denied_capabilities=[]) + assert enforcer.denied == frozenset() + + def test_none_denied_list_accepted(self): + enforcer = CapabilityEnforcer(denied_capabilities=None) + assert enforcer.denied == frozenset() + + def test_all_recognised_tokens_accepted(self): + enforcer = CapabilityEnforcer(denied_capabilities=list(ALL_CAPABILITIES)) + assert enforcer.denied == ALL_CAPABILITIES + + def test_whitespace_only_tokens_are_ignored(self): + enforcer = CapabilityEnforcer(denied_capabilities=[" ", "\t", "network"]) + assert "network" in enforcer.denied + + def test_case_insensitive_normalisation(self): + enforcer = CapabilityEnforcer(denied_capabilities=["NETWORK", "Exploit"]) + assert "network" in enforcer.denied + assert "exploit" in enforcer.denied From d6b9d9f726c8d20a380629d34bd5a6e35fb057df Mon Sep 17 00:00:00 2001 From: Vexx Date: Mon, 1 Jun 2026 23:51:56 +0530 Subject: [PATCH 5/5] fix(capabilities): clean metadata diff + expand inventory/execution-blocking tests metadata fix: Revert waf_detector/metadata.json to upstream key order, icon encoding, and name. The only intentional change from upstream is adding `capabilities: ["network"]` and updating the checksum. This eliminates all unrelated encoding/ordering churn from the diff. inventory tests (32 total): - effective_capabilities implied-set correctness for all safety levels (safe/intrusive/exploit/unknown) and the explicit-override behaviour - All shipped plugins produce a non-empty effective_capabilities set - CapabilityEnforcer.check() raises CapabilityDeniedError BEFORE any fake build_command is called (execution-blocking regression) - Exploit/intrusive plugins blocked when the matching cap is denied - Safe plugin passes when only exploit is denied - Plugin with explicit capabilities declaration blocked on denied token - Empty deny-list never blocks any shipped plugin - Denying "exploit" blocks EVERY exploit-level plugin in the plugins/ tree --- plugins/waf_detector/metadata.json | 70 +++---- .../test_plugin_capabilities_inventory.py | 177 +++++++++++++++++- 2 files changed, 210 insertions(+), 37 deletions(-) diff --git a/plugins/waf_detector/metadata.json b/plugins/waf_detector/metadata.json index 63838633..6bd96a48 100644 --- a/plugins/waf_detector/metadata.json +++ b/plugins/waf_detector/metadata.json @@ -1,59 +1,59 @@ { + "id": "waf_detector", + "name": "WAF Detection Scanner", + "version": "1.0.0", + "description": "Automatically identify Web Application Firewalls protecting targets.", + "long_description": "Automatically identify Web Application Firewalls protecting targets.", + "category": "robots", "author": { - "email": "dev@secuscan.local", - "name": "SecuScan Contributors" + "name": "SecuScan Contributors", + "email": "dev@secuscan.local" + }, + "license": "MIT", + "icon": "๐Ÿ› ๏ธ", + "engine": { + "type": "cli", + "binary": "wafw00f" }, - "category": "robots", - "capabilities": ["network"], - "checksum": "fb043331ef88a1ed0fa59ae5d0075e700978f853252ce2859562274a6f43eeb4", "command_template": [ "wafw00f", "{target}" ], - "dependencies": { - "binaries": [ - "wafw00f" - ], - "python_packages": [], - "system_packages": [] - }, - "description": "Automatically identify Web Application Firewalls protecting targets.", - "engine": { - "binary": "wafw00f", - "type": "cli" - }, "fields": [ { "id": "target", "label": "Target URL", - "placeholder": "https://secuscan.in", - "required": true, "type": "string", + "required": true, + "placeholder": "https://secuscan.in", "validation": { - "message": "Must be a valid HTTP(S) URL", - "pattern": "^https?://" + "pattern": "^https?://", + "message": "Must be a valid HTTP(S) URL" } } ], - "icon": "\ud83d\udee0\ufe0f", - "id": "waf_detector", - "license": "MIT", - "long_description": "Automatically identify Web Application Firewalls protecting targets.", - "name": "WAF Detector (wafw00f)", + "presets": { + "default": {} + }, "output": { "format": "text", "parser": "custom" }, - "presets": { - "default": {} - }, "safety": { "level": "safe", + "requires_consent": false, "rate_limit": { - "max_concurrent": 1, - "max_per_hour": 20 - }, - "requires_consent": false + "max_per_hour": 20, + "max_concurrent": 1 + } + }, + "dependencies": { + "binaries": [ + "wafw00f" + ], + "python_packages": [], + "system_packages": [] }, - "version": "1.0.0" -} \ No newline at end of file + "capabilities": ["network"], + "checksum": "ac518fd15fe9a14f9327812178fd244ebaa2ee95d29d04b93ee928fa3fda7ffa" +} diff --git a/testing/backend/unit/test_plugin_capabilities_inventory.py b/testing/backend/unit/test_plugin_capabilities_inventory.py index 9f3497c3..72c5a38d 100644 --- a/testing/backend/unit/test_plugin_capabilities_inventory.py +++ b/testing/backend/unit/test_plugin_capabilities_inventory.py @@ -11,14 +11,23 @@ - CapabilityEnforcer raises on any unknown token even when mixed with valid ones. - CapabilityEnforcer accepts the empty denied list without error. - CapabilityEnforcer accepts all recognised capability tokens without error. +- Denied capabilities raise CapabilityDeniedError BEFORE command construction + (execution-blocking regression: executor must enforce policy before any + subprocess is spawned or any command argument is assembled). +- effective_capabilities returns the expected implied set for every known + safety level so legacy plugins remain enforceable without metadata changes. """ import json from pathlib import Path - import pytest -from backend.secuscan.capabilities import ALL_CAPABILITIES, CapabilityEnforcer +from backend.secuscan.capabilities import ( + ALL_CAPABILITIES, + CapabilityDeniedError, + CapabilityEnforcer, + effective_capabilities, +) # --------------------------------------------------------------------------- @@ -172,3 +181,167 @@ def test_case_insensitive_normalisation(self): enforcer = CapabilityEnforcer(denied_capabilities=["NETWORK", "Exploit"]) assert "network" in enforcer.denied assert "exploit" in enforcer.denied + + +# --------------------------------------------------------------------------- +# effective_capabilities โ€” implied set correctness for all safety levels +# --------------------------------------------------------------------------- + + +class TestEffectiveCapabilitiesImpliedSets: + """Verify the implied capability sets for all safety levels so that + legacy plugins (those without an explicit 'capabilities' field) remain + enforceable via deny-list even after the enforcement engine ships.""" + + def test_safe_implies_network(self): + caps = effective_capabilities(None, "safe", "plugin") + assert "network" in caps + + def test_safe_does_not_imply_intrusive_or_exploit(self): + caps = effective_capabilities(None, "safe", "plugin") + assert "intrusive" not in caps + assert "exploit" not in caps + + def test_intrusive_implies_network_and_intrusive(self): + caps = effective_capabilities(None, "intrusive", "plugin") + assert {"network", "intrusive"} <= caps + + def test_intrusive_does_not_imply_exploit(self): + caps = effective_capabilities(None, "intrusive", "plugin") + assert "exploit" not in caps + + def test_exploit_implies_network_intrusive_and_exploit(self): + caps = effective_capabilities(None, "exploit", "plugin") + assert {"network", "intrusive", "exploit"} <= caps + + def test_unknown_level_falls_back_to_network(self): + caps = effective_capabilities(None, "unknown_level", "plugin") + assert "network" in caps + + def test_explicit_empty_list_falls_back_to_implied(self): + caps = effective_capabilities([], "intrusive", "plugin") + assert "intrusive" in caps + + def test_explicit_nonempty_list_overrides_implied(self): + # Plugin explicitly declares only filesystem; implied intrusive set is NOT added. + caps = effective_capabilities(["filesystem"], "intrusive", "plugin") + assert caps == {"filesystem"} + assert "intrusive" not in caps + + def test_all_shipped_plugins_have_enforceable_effective_capabilities(self): + """Every plugin in the plugins/ directory must produce a non-empty + effective_capabilities set so the enforcer can act on it.""" + bad: list[str] = [] + for plugin_id, meta in _iter_plugin_metadata(): + level = meta.get("safety", {}).get("level", "safe") + declared = meta.get("capabilities") + caps = effective_capabilities(declared, level, plugin_id) + if not caps: + bad.append(plugin_id) + assert not bad, ( + f"Plugin(s) produced empty effective_capabilities: {bad}" + ) + + +# --------------------------------------------------------------------------- +# Execution-blocking regression: denied capabilities stop execution BEFORE +# command construction โ€” no subprocess is spawned or command built for a +# plugin whose required capabilities are all denied. +# --------------------------------------------------------------------------- + + +class TestDeniedCapabilityBlocksExecution: + """CapabilityEnforcer.check() must raise CapabilityDeniedError before any + command is built or subprocess is spawned. These tests exercise the + enforcer in isolation and then simulate the executor integration point.""" + + def test_check_raises_capability_denied_error_for_denied_cap(self): + enforcer = CapabilityEnforcer(denied_capabilities=["network"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check("my_plugin", declared=None, safety_level="safe") + assert exc_info.value.plugin_id == "my_plugin" + assert "network" in exc_info.value.denied_capabilities + + def test_check_raises_before_command_construction(self): + """Simulate the executor integration point: enforcer.check() is called + before plugin_manager.build_command(). When the capability is denied, + build_command must never be called.""" + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + build_command_called: list[bool] = [] + + def fake_build_command(*args, **kwargs): + build_command_called.append(True) + return ["exploit_tool", "--target", "example.com"] + + with pytest.raises(CapabilityDeniedError): + enforcer.check("exploit_plugin", declared=None, safety_level="exploit") + # This line must not be reached: + fake_build_command("exploit_plugin", {}) + + assert build_command_called == [], ( + "build_command must not be called when capability check raises" + ) + + def test_exploit_plugin_blocked_when_exploit_denied(self): + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("sqli_exploiter", declared=None, safety_level="exploit") + + def test_intrusive_plugin_blocked_when_intrusive_denied(self): + enforcer = CapabilityEnforcer(denied_capabilities=["intrusive"]) + with pytest.raises(CapabilityDeniedError): + enforcer.check("nikto", declared=None, safety_level="intrusive") + + def test_safe_plugin_passes_when_only_exploit_denied(self): + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + # Should not raise โ€” safe plugins do not require exploit capability. + enforcer.check("whois_lookup", declared=None, safety_level="safe") + + def test_plugin_with_explicit_caps_blocked_on_denied_token(self): + enforcer = CapabilityEnforcer(denied_capabilities=["credentials"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check( + "vault_plugin", + declared=["network", "credentials"], + safety_level="safe", + ) + assert "credentials" in exc_info.value.denied_capabilities + + def test_error_message_names_plugin_and_denied_caps(self): + enforcer = CapabilityEnforcer(denied_capabilities=["docker"]) + with pytest.raises(CapabilityDeniedError) as exc_info: + enforcer.check("container_scanner", declared=["docker"], safety_level="safe") + msg = str(exc_info.value) + assert "container_scanner" in msg + assert "docker" in msg + + def test_empty_denied_set_never_blocks_any_shipped_plugin(self): + """With an empty deny list every plugin must pass the capability check.""" + enforcer = CapabilityEnforcer(denied_capabilities=[]) + for plugin_id, meta in _iter_plugin_metadata(): + level = meta.get("safety", {}).get("level", "safe") + declared = meta.get("capabilities") + # Must not raise. + enforcer.check(plugin_id, declared=declared, safety_level=level) + + def test_deny_exploit_blocks_all_exploit_level_shipped_plugins(self): + """Denying 'exploit' must block every plugin with safety.level == 'exploit' + regardless of whether they have an explicit capabilities declaration.""" + enforcer = CapabilityEnforcer(denied_capabilities=["exploit"]) + blocked: list[str] = [] + not_blocked: list[str] = [] + for plugin_id, meta in _iter_plugin_metadata(): + if meta.get("safety", {}).get("level") != "exploit": + continue + declared = meta.get("capabilities") + try: + enforcer.check(plugin_id, declared=declared, safety_level="exploit") + not_blocked.append(plugin_id) + except CapabilityDeniedError: + blocked.append(plugin_id) + # Every exploit-level plugin must be blocked. + assert not_blocked == [], ( + f"Exploit-level plugin(s) not blocked when 'exploit' is denied: {not_blocked}" + ) + # Sanity: at least one exploit-level plugin must exist in the test corpus. + assert len(blocked) > 0, "No exploit-level plugins found โ€” check plugins/ directory"