Detect available security tools
(self)
| 859 | def _generate_api_key(self) -> str: |
| 860 | """Get or generate a stable API key for ZAP. |
| 861 | |
| 862 | Persists the key to a file so that if multiple scanner instances |
| 863 | exist (e.g. due to race conditions), they all use the same key |
| 864 | and can share a single ZAP daemon without fighting. |
| 865 | """ |
| 866 | import secrets |
| 867 | key_file = os.path.join(self.shared_data.currentdir, 'data', '.zap_api_key') |
| 868 | try: |
| 869 | os.makedirs(os.path.dirname(key_file), exist_ok=True) |
| 870 | if os.path.exists(key_file): |
| 871 | stored = open(key_file, 'r').read().strip() |
| 872 | if len(stored) >= 16: |
| 873 | return stored |
| 874 | except Exception: |
| 875 | pass |
| 876 | key = secrets.token_hex(self.ZAP_API_KEY_LENGTH // 2) |
| 877 | try: |
| 878 | with open(key_file, 'w') as f: |
| 879 | f.write(key) |
| 880 | except Exception: |
| 881 | pass |
| 882 | return key |
| 883 | |
| 884 | # Bin dirs to check when a tool isn't on PATH — a systemd service can run |
| 885 | # with a thin PATH that misses /usr/bin, hiding an apt-installed nmap. |
| 886 | _TOOL_BIN_DIRS = ('/usr/bin', '/usr/local/bin', '/usr/sbin', '/bin', |
| 887 | '/sbin', '/snap/bin') |
| 888 | |
| 889 | def _resolve_tool(self, tool): |
| 890 | """Absolute path to `tool` via PATH, or a common bin dir, else None.""" |
| 891 | path = shutil.which(tool) |
| 892 | if path: |
| 893 | return path |
| 894 | for d in self._TOOL_BIN_DIRS: |
| 895 | cand = os.path.join(d, tool) |
| 896 | if os.path.exists(cand): |
| 897 | return cand |
| 898 | return None |
| 899 | |
| 900 | def _detect_tools(self): |
| 901 | """Detect available security tools""" |
| 902 | tools = ['nuclei', 'nikto', 'sqlmap', 'nmap', 'whatweb'] |
| 903 | for tool in tools: |
| 904 | path = self._resolve_tool(tool) |
| 905 | self._tool_paths[tool] = path |
| 906 | if path: |
| 907 | logger.info(f"Found {tool} at {path}") |
| 908 | else: |
| 909 | logger.debug(f"{tool} not found in PATH or common bin dirs") |
| 910 | |
| 911 | # Detect ZAP - check multiple possible locations |
| 912 | # Priority: Ragnar tools dir > /opt > standard locations > PATH |
| 913 | ragnar_dir = os.path.dirname(os.path.abspath(__file__)) |
| 914 | import sys |
| 915 | is_windows = sys.platform == 'win32' |
| 916 | |
| 917 | if is_windows: |
| 918 | # Windows ZAP locations |