Z-Sans uses ToolOrchestrator to uniformly orchestrate external tools and built-in implementations. All tool results are returned synchronously as Python data structures; external tools write temporary files that are read and parsed, then deleted after use.

Tool Probe Order

Each tool looks up an available implementation in this order:

  1. Configuration external_tools.paths.<name>
  2. Built-in directories (tools/<name>/..., assets/<name>)
  3. System PATH (which / where)

When no external binary is found, a built-in implementation fallback is used, ensuring the scan is never interrupted.

Low-Privilege Environment Fallback

When /tmp is not writable, the temporary file directory automatically falls back to tools/.tmp; it can also be specified via the environment variable ZSANS_TEMP_DIR / $TMPDIR.

Tool Overview

Tool method What is invoked Purpose Timeout Fallback if missing
run_subfinder binary subfinder Subdomain discovery 120s Built-in DNS resolution
run_naabu binary naabu Port scanning 240s Built-in TCP scanner
run_jsfinder python assets/JSfinder.py JS/link extraction jsfinder_timeout (default 30s) Built-in requests+BS4
run_free_subfinder python assets/free-subfinder.py Free passive subdomain aggregation 120s
run_ehole binary EHole Web fingerprinting 60s WhatWeb / title
run_whatweb Ruby script whatweb Fingerprinting (richer) 90s EHole / title
run_fingerprint Unified fingerprint entry Select engine per configuration
_run_internal_dns_resolver python assets/dnsxs.py Built-in DNS resolution 60s
_run_internal_port_scanner python assets/port.py Built-in port scanning 180s
_internal_jsfinder Pure Python implementation Built-in JS/link extraction http.timeout

Overriding Tools

Plugins can register or override tools via ToolOrchestrator.register_tool(name, path, version, extra_args); extra_args are appended to the end of the command line.

Fingerprinting

Fingerprinting is dispatched through the unified entry run_fingerprint:

external_tools:
  fingerprint:
    enabled: true    # Enable fingerprinting
    engine: auto     # auto | ehole | whatweb | both | none
engine Behavior
auto Auto-select by availability (prefers EHole)
ehole EHole only
whatweb WhatWeb only
both Run both and merge the results
none Skip fingerprinting

EHole

  • Built-in multi-architecture binaries: tools/ehole/{darwin,linux,windows}/{arch}/EHole[.exe], each shipped with finger.json + config.ini
  • Resolution priority: configured path → built-in multi-architecture directory → legacy single-directory → system PATH
  • Command: EHole finger -u <url> -o <tmp.json>
  • Output fields: {url, fingerprints, cms, server, status_code, title}

WhatWeb

  • Command: whatweb --no-errors --log-json=<tmp> <url>
  • Filters out ~40 metainfo noise plugins (Title/HTTPServer/IP etc.) before counting fingerprints
  • The first non-noise match is used as cms; server is taken from HTTPServer/WebServer/X-Powered-By

Built-in Scripts

Script Based on Parses Engine usage
assets/JSfinder.py Threezh1 JSFinder Extracts URLs and subdomains from pages + inline JS -u input, -ou/-os write result files
assets/dnsxs.py dnspython, connecting directly to 6 public DNS servers A/AAAA/CNAME/MX/TXT/NS/SOA/PTR/ANY -t A -q internal resolution fallback
assets/free-subfinder.py 5 free sources, concurrent aggregation CRT.sh / AlienVault OTX / HackerTarget / DNSdumpster / Anubis Extends internal DNS results
assets/port.py Python socket concurrent connection TCP connect_ex -p <range> -q built-in port scanning

Free Passive Sources (free-subfinder.py)

Aggregates the following public sources:

Source Type
CRT.sh Certificate transparency logs
AlienVault OTX OTX passive DNS
HackerTarget Passive DNS API
DNSdumpster Fetches a CSRF token first, then POSTs
Anubis (jonlu.ca) Subdomain search engine

Default timeout 15s, 5 threads, 2 retries with 1s backoff.

Built-in DNS (dnsxs.py)

8.8.8.8 / 8.8.4.4 / 1.1.1.1 / 1.0.0.1 / 114.114.114.114 / 223.5.5.5

Six public DNS servers, queried over direct UDP connections.

Built-in Port Scanner (port.py)

  • Default 50 threads, 1.0s timeout per port
  • Default port range 1-1024; also supports 80,443,1000-2000 format
  • Domains are resolved to IPs before scanning

The default port range used by the engine internally (asset_types.ip.tools.port_range):

1-1024,3306,3389,5432,5900,6379,7001,8000-8500,8888,9000-9100,9200,27017,11211

Concurrency Control

  • Global: concurrency.max_tasks (default 20)
  • Each tool independently: concurrency.tools.<name> (subfinder / naabu / jsfinder)
  • ToolOrchestrator provides reduce_concurrency() / increase_concurrency() to dynamically adjust concurrency (cap 20)
  • get_running_tasks() returns the currently running tasks (func/args/running_time)