fix: Expand tool installer to cover all 48 tools#6
Conversation
- Go tools: 22 packages (ProjectDiscovery, tomnomnom, hakluke, etc.) - Python tools: dnsgen, altdns, arjun, paramspider, linkfinder, s3scanner - APT packages: amass, findomain, massdns, nmap, masscan, whatweb, etc. - Special installs: kiterunner (make), x8 (cargo), favfreak, webanalyze, cloud_enum - Creates resolvers.txt for DNS tools - Updates nuclei templates after install Now installs all tools listed in macaron -L
There was a problem hiding this comment.
Pull request overview
This PR significantly expands the tool installation functionality to install 48 reconnaissance tools across multiple package managers and installation methods. The changes transform a basic Go-based installer into a comprehensive security tooling setup script.
Key Changes:
- Expanded Go tools from 11 to 23 tools with better categorization (subdomain enumeration, DNS, ASN/IP, ports, HTTP, URLs, JS, content, takeover, screenshots)
- Added Python tools installation via pip3 (6 tools)
- Added APT package installation for Debian-based systems (12 tools)
- Added special installation handlers for 7 tools requiring custom installation methods (kiterunner, x8, favfreak, webanalyze, cloud_enum, emailfinder)
- Improved user feedback with categorized installation progress messages
- Added error handling for Go tool installation failures
- Created automatic resolvers file generation for DNS tools
- Added nuclei template updates
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/assetnote/kiterunner.git 2>/dev/null && cd kiterunner && make build && cp dist/kr /usr/local/bin/ 2>/dev/null" | ||
| ], capture_output=True) | ||
|
|
||
| # x8 (Rust) | ||
| console.print(" [dim]Installing[/] x8") if console else None | ||
| subprocess.run(["cargo", "install", "x8"], capture_output=True) | ||
|
|
||
| # favfreak | ||
| console.print(" [dim]Installing[/] favfreak") if console else None | ||
| subprocess.run([ | ||
| "bash", "-c", | ||
| "pip3 install mmh3 && cd /tmp && git clone https://github.com/devanshbatham/FavFreak.git 2>/dev/null && cp FavFreak/favfreak.py /usr/local/bin/favfreak && chmod +x /usr/local/bin/favfreak 2>/dev/null" | ||
| ], capture_output=True) | ||
|
|
||
| # webanalyze | ||
| console.print(" [dim]Installing[/] webanalyze") if console else None | ||
| subprocess.run(["go", "install", "github.com/rverton/webanalyze/cmd/webanalyze@latest"], capture_output=True) | ||
|
|
||
| # cloud_enum | ||
| console.print(" [dim]Installing[/] cloud_enum") if console else None | ||
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" | ||
| ], capture_output=True) |
There was a problem hiding this comment.
The kiterunner, favfreak, and cloud_enum installations use /tmp directory for cloning repositories, but there's no cleanup after installation. This leaves repository data in /tmp which could accumulate over multiple runs. Consider cleaning up the temporary directories after installation or using a more appropriate temporary directory with automatic cleanup.
| subprocess.run([ | ||
| "bash", "-c", | ||
| "pip3 install mmh3 && cd /tmp && git clone https://github.com/devanshbatham/FavFreak.git 2>/dev/null && cp FavFreak/favfreak.py /usr/local/bin/favfreak && chmod +x /usr/local/bin/favfreak 2>/dev/null" | ||
| ], capture_output=True) | ||
|
|
||
| # webanalyze | ||
| console.print(" [dim]Installing[/] webanalyze") if console else None | ||
| subprocess.run(["go", "install", "github.com/rverton/webanalyze/cmd/webanalyze@latest"], capture_output=True) | ||
|
|
||
| # cloud_enum | ||
| console.print(" [dim]Installing[/] cloud_enum") if console else None | ||
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" | ||
| ], capture_output=True) | ||
|
|
||
| # emailfinder | ||
| console.print(" [dim]Installing[/] emailfinder") if console else None | ||
| subprocess.run(["pip3", "install", "emailfinder"], capture_output=True) | ||
|
|
||
| # Update nuclei templates | ||
| console.print("\n[bold cyan]Updating nuclei templates...[/]") if console else None | ||
| subprocess.run(["nuclei", "-update-templates"], capture_output=True) | ||
|
|
There was a problem hiding this comment.
The installation process runs sequentially and could take a very long time to complete since it's installing 48+ tools. Consider implementing parallel installation where safe (e.g., Go tools could be installed concurrently) to improve installation performance and user experience.
| subprocess.run([ | |
| "bash", "-c", | |
| "pip3 install mmh3 && cd /tmp && git clone https://github.com/devanshbatham/FavFreak.git 2>/dev/null && cp FavFreak/favfreak.py /usr/local/bin/favfreak && chmod +x /usr/local/bin/favfreak 2>/dev/null" | |
| ], capture_output=True) | |
| # webanalyze | |
| console.print(" [dim]Installing[/] webanalyze") if console else None | |
| subprocess.run(["go", "install", "github.com/rverton/webanalyze/cmd/webanalyze@latest"], capture_output=True) | |
| # cloud_enum | |
| console.print(" [dim]Installing[/] cloud_enum") if console else None | |
| subprocess.run([ | |
| "bash", "-c", | |
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" | |
| ], capture_output=True) | |
| # emailfinder | |
| console.print(" [dim]Installing[/] emailfinder") if console else None | |
| subprocess.run(["pip3", "install", "emailfinder"], capture_output=True) | |
| # Update nuclei templates | |
| console.print("\n[bold cyan]Updating nuclei templates...[/]") if console else None | |
| subprocess.run(["nuclei", "-update-templates"], capture_output=True) | |
| # Prepare installation and update commands to run in parallel | |
| favfreak_cmd = [ | |
| "bash", "-c", | |
| "pip3 install mmh3 && cd /tmp && git clone https://github.com/devanshbatham/FavFreak.git 2>/dev/null && cp FavFreak/favfreak.py /usr/local/bin/favfreak && chmod +x /usr/local/bin/favfreak 2>/dev/null" | |
| ] | |
| # webanalyze | |
| console.print(" [dim]Installing[/] webanalyze") if console else None | |
| webanalyze_cmd = ["go", "install", "github.com/rverton/webanalyze/cmd/webanalyze@latest"] | |
| # cloud_enum | |
| console.print(" [dim]Installing[/] cloud_enum") if console else None | |
| cloud_enum_cmd = [ | |
| "bash", "-c", | |
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" | |
| ] | |
| # emailfinder | |
| console.print(" [dim]Installing[/] emailfinder") if console else None | |
| emailfinder_cmd = ["pip3", "install", "emailfinder"] | |
| # Update nuclei templates | |
| console.print("\n[bold cyan]Updating nuclei templates...[/]") if console else None | |
| nuclei_cmd = ["nuclei", "-update-templates"] | |
| install_commands = [ | |
| favfreak_cmd, | |
| webanalyze_cmd, | |
| cloud_enum_cmd, | |
| emailfinder_cmd, | |
| nuclei_cmd, | |
| ] | |
| # Run the installation and update commands in parallel to improve performance | |
| max_workers = min(4, len(install_commands)) | |
| with ThreadPoolExecutor(max_workers=max_workers) as executor: | |
| futures = [ | |
| executor.submit(subprocess.run, cmd, capture_output=True) | |
| for cmd in install_commands | |
| ] | |
| for future in as_completed(futures): | |
| # Ensure all commands complete and surface any exceptions | |
| future.result() | |
| console.print("\n[bold cyan]Installing Python tools...[/]") if console else None | ||
| for tool in pip_tools: | ||
| console.print(f" [dim]pip install[/] {tool}") if console else print(f"Installing {tool}") | ||
| subprocess.run(["pip3", "install", tool], capture_output=True) |
There was a problem hiding this comment.
The pip3 installation command for Python tools lacks error handling. If pip3 is not available or the installation fails, the command will silently fail without informing the user. Consider capturing the return code and displaying a warning similar to the Go tools installation pattern.
| subprocess.run(["pip3", "install", tool], capture_output=True) | |
| try: | |
| result = subprocess.run(["pip3", "install", tool], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| console.print(f" [yellow]⚠ Failed[/]") if console else None | |
| except FileNotFoundError: | |
| if console: | |
| console.print(" [yellow]⚠ pip3 not found; skipping Python tools installation[/]") | |
| else: | |
| print("Warning: pip3 not found; skipping Python tools installation") | |
| break |
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/assetnote/kiterunner.git 2>/dev/null && cd kiterunner && make build && cp dist/kr /usr/local/bin/ 2>/dev/null" | ||
| ], capture_output=True) |
There was a problem hiding this comment.
The kiterunner installation uses a complex bash command with git clone and make build that could fail at multiple points, but all failures are silently ignored. If git, make, or the repository is unavailable, users won't know the installation failed. Consider adding error checking or at least capturing and reporting the return code.
| subprocess.run([ | |
| "bash", "-c", | |
| "cd /tmp && git clone https://github.com/assetnote/kiterunner.git 2>/dev/null && cd kiterunner && make build && cp dist/kr /usr/local/bin/ 2>/dev/null" | |
| ], capture_output=True) | |
| kr_result = subprocess.run( | |
| [ | |
| "bash", | |
| "-c", | |
| "cd /tmp && git clone https://github.com/assetnote/kiterunner.git 2>/dev/null && cd kiterunner && make build && cp dist/kr /usr/local/bin/ 2>/dev/null", | |
| ], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if kr_result.returncode != 0: | |
| err_msg = f" Failed to install kiterunner (exit code {kr_result.returncode})." | |
| if console: | |
| console.print(f"[red]{err_msg}[/]") | |
| if kr_result.stderr: | |
| console.print(f"[red]{kr_result.stderr}[/]") | |
| else: | |
| print(err_msg, file=sys.stderr) | |
| if kr_result.stderr: | |
| print(kr_result.stderr, file=sys.stderr) |
|
|
||
| # x8 (Rust) | ||
| console.print(" [dim]Installing[/] x8") if console else None | ||
| subprocess.run(["cargo", "install", "x8"], capture_output=True) |
There was a problem hiding this comment.
The x8 installation via cargo has no error handling and no check if cargo (Rust toolchain) is installed. If cargo is not available, the installation will fail silently without informing the user. Consider checking if cargo exists or adding error handling similar to the Go tools pattern.
| subprocess.run(["cargo", "install", "x8"], capture_output=True) | |
| cargo_path = shutil.which("cargo") | |
| if not cargo_path: | |
| msg = "cargo (Rust toolchain) not found; skipping x8 installation. Please install Rust to use x8." | |
| if console: | |
| console.print(f"[yellow]{msg}[/]") | |
| else: | |
| print(msg, file=sys.stderr) | |
| else: | |
| result = subprocess.run([cargo_path, "install", "x8"], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| error_output = (result.stderr or result.stdout or "").strip() | |
| msg = "Failed to install x8 via cargo." | |
| if error_output: | |
| msg = f"{msg} Details: {error_output}" | |
| if console: | |
| console.print(f"[red]{msg}[/]") | |
| else: | |
| print(msg, file=sys.stderr) |
| subprocess.run(["apt-get", "update", "-qq"], capture_output=True) | ||
| for tool in apt_tools: | ||
| console.print(f" [dim]apt install[/] {tool}") if console else print(f"Installing {tool}") | ||
| subprocess.run(["apt-get", "install", "-y", "-qq", tool], capture_output=True) |
There was a problem hiding this comment.
The APT package installation commands require root privileges but no check is performed and no error is reported if the command fails due to insufficient permissions. Users will not know why the installation failed. Consider adding privilege checks or error handling to inform users they need to run with sudo.
| subprocess.run(["apt-get", "update", "-qq"], capture_output=True) | |
| for tool in apt_tools: | |
| console.print(f" [dim]apt install[/] {tool}") if console else print(f"Installing {tool}") | |
| subprocess.run(["apt-get", "install", "-y", "-qq", tool], capture_output=True) | |
| if hasattr(os, "geteuid") and os.geteuid() != 0: | |
| msg = ( | |
| "Skipping APT package installation because root privileges are required. " | |
| "Please re-run this command with sudo or as root to install system packages." | |
| ) | |
| console.print(f"[yellow]{msg}[/]") if console else print(msg) | |
| else: | |
| update_result = subprocess.run( | |
| ["apt-get", "update", "-qq"], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if update_result.returncode != 0: | |
| warn_msg = ( | |
| f"apt-get update failed with exit code {update_result.returncode}. " | |
| "You may need to run this command with sudo or as root." | |
| ) | |
| console.print(f"[yellow]{warn_msg}[/]") if console else print(warn_msg) | |
| for tool in apt_tools: | |
| console.print(f" [dim]apt install[/] {tool}") if console else print(f"Installing {tool}") | |
| install_result = subprocess.run( | |
| ["apt-get", "install", "-y", "-qq", tool], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if install_result.returncode != 0: | |
| warn_msg = ( | |
| f"Failed to install {tool} via apt-get (exit code {install_result.returncode}). " | |
| "You may need to run this command with sudo or as root." | |
| ) | |
| console.print(f" [yellow]⚠ {warn_msg}[/]") if console else print(warn_msg) |
| console.print(" [dim]Installing[/] cloud_enum") if console else None | ||
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" |
There was a problem hiding this comment.
The cloud_enum installation uses a symlink to /usr/local/bin which requires root privileges but has no error handling. The 2>/dev/null suppresses errors, so users won't know if the symlink creation failed due to insufficient permissions. Consider adding error handling or privilege checks.
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" | |
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && if [ -w /usr/local/bin ] || [ \"$(id -u)\" -eq 0 ]; then ln -sf \"$(pwd)/cloud_enum.py\" /usr/local/bin/cloud_enum; else echo 'Warning: insufficient permissions to create /usr/local/bin/cloud_enum symlink. Please run macaron with elevated privileges or create the symlink manually.' >&2; fi" |
| subprocess.run([ | ||
| "bash", "-c", | ||
| "pip3 install mmh3 && cd /tmp && git clone https://github.com/devanshbatham/FavFreak.git 2>/dev/null && cp FavFreak/favfreak.py /usr/local/bin/favfreak && chmod +x /usr/local/bin/favfreak 2>/dev/null" |
There was a problem hiding this comment.
This command first runs pip3 install mmh3 and then clones https://github.com/devanshbatham/FavFreak.git from a mutable HEAD and copies its script into /usr/local/bin without any version pinning or integrity checks. A compromised PyPI package or GitHub repo here would immediately execute attacker-controlled code during installation and persist it as a system-wide tool. Prefer pinning to specific versions/commits with known hashes or trusted packages you control rather than installing arbitrary latest code each time.
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/initstring/cloud_enum.git 2>/dev/null && cd cloud_enum && pip3 install -r requirements.txt && ln -sf $(pwd)/cloud_enum.py /usr/local/bin/cloud_enum 2>/dev/null" |
There was a problem hiding this comment.
Here you clone https://github.com/initstring/cloud_enum.git at HEAD and run pip3 install -r requirements.txt, allowing that repo to specify arbitrary dependencies and install scripts with no integrity verification. If the repo or its requirements.txt is tampered with, this installer grants an attacker code execution and lets them drop or modify tools under /usr/local/bin. Consider pinning to a specific commit or tagged release and using a locked requirements file with hashes so you do not execute or install arbitrary updated code on each run.
| subprocess.run([ | ||
| "bash", "-c", | ||
| "cd /tmp && git clone https://github.com/assetnote/kiterunner.git 2>/dev/null && cd kiterunner && make build && cp dist/kr /usr/local/bin/ 2>/dev/null" |
There was a problem hiding this comment.
This git clone + make build pipeline pulls and executes arbitrary code from the HEAD of a third-party GitHub repo with no pinning or integrity verification. If the repo or its build scripts are compromised, running this installer gives an attacker code execution with the installer’s privileges and lets them drop a binary into /usr/local/bin. To reduce this supply-chain risk, pin to a specific commit or signed release and verify it (or vendor a known-good artifact) instead of building whatever is on the default branch at install time.
Now installs Go, Python, APT, and special tools.