usfx.git

usfx

~ Ultimate Subdomain Finder X - CLI tool for internal network subdomain discovery
git clone https://github.com/debuman/usfx.git
commit 668f0ef20136ab025f7d6c2728b4c90ce1fd6937
Author: devastator-x <ICMProtocol@proton.me>
Date: Tue, 20 Jan 2026 11:36:22 +0900

Initial commit: USFX v1.0.0

Ultimate Subdomain Finder X - standalone CLI tool for discovering
subdomains on internal networks using custom DNS servers.

Diffstat:
A.gitignore | 50++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE | 21+++++++++++++++++++++
AREADME.txt | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apyproject.toml | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/__init__.py | 21+++++++++++++++++++++
Asrc/usfx/__main__.py | 8++++++++
Asrc/usfx/cli.py | 310+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/config.py | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/data/__init__.py | 1+
Asrc/usfx/data/wordlist.txt | 3557+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/data/wordlist_large.txt | 627+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/data/wordlist_small.txt | 500+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/engine.py | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/__init__.py | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/base.py | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/cname_chaser.py | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/dns_bruteforce.py | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/dns_records.py | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/dnssec_walker.py | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/permutation.py | 286+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/recursive_enum.py | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/reverse_dns.py | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/tls_analyzer.py | 265+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/vhost_scanner.py | 328+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/modules/zone_transfer.py | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/orchestrator.py | 525+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/utils/__init__.py | 25+++++++++++++++++++++++++
Asrc/usfx/utils/dns_resolver.py | 364+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/utils/output.py | 371+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/utils/validator.py | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/utils/wildcard.py | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/usfx/utils/wordlist.py | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
32 files changed, 10071 insertion(+), 0 deletion(-)

diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,50 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Virtual environments
+venv/
+ENV/
+env/
+.venv/
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+
+# Testing
+.tox/
+.coverage
+.coverage.*
+htmlcov/
+.pytest_cache/
+.mypy_cache/
+
+# Build
+*.manifest
+*.spec
+
+# OS
+.DS_Store
+Thumbs.db
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 LACRYMARIA Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.txt b/README.txt
@@ -0,0 +1,193 @@
+USFX - Ultimate Subdomain Finder X
+===================================
+
+A standalone Python CLI tool for discovering subdomains on internal
+networks using custom DNS servers. Designed for air-gapped environments
+without internet connectivity.
+
+
+FEATURES
+--------
+
+  - Custom DNS Server Support: Specify internal DNS servers
+  - Offline-Only Modules: All techniques work without internet
+  - 10 Enumeration Modules:
+      * DNS Brute Force
+      * Zone Transfer (AXFR)
+      * DNSSEC Walking (NSEC/NSEC3)
+      * DNS Record Mining (MX, NS, TXT, SRV, SOA, CAA)
+      * Reverse DNS Sweep
+      * CNAME Chain Analysis
+      * Subdomain Permutation
+      * Recursive Sub-subdomain Enumeration
+      * Virtual Host Discovery
+      * TLS Certificate SAN Extraction
+  - Multiple Output Formats: JSON, CSV, TXT
+  - Bundled Wordlists: Small (~500), Medium (~3500), Large (~10000)
+  - Progress Tracking: Real-time progress with Rich terminal UI
+
+
+INSTALLATION
+------------
+
+From Source (Development):
+
+    cd /home/devastator/nextvt/usfx
+    python3 -m venv venv
+    source venv/bin/activate
+    pip install -e .
+    usfx --version
+
+From Built Package:
+
+    python -m build
+    pip install dist/usfx-1.0.0-py3-none-any.whl
+
+
+QUICK START
+-----------
+
+Basic Usage:
+
+    usfx corp.local                              # System DNS
+    usfx corp.local -d 192.168.1.1               # Internal DNS
+    usfx corp.local -d 192.168.1.1 -d 10.0.0.1   # Multiple DNS
+
+Wordlist Options:
+
+    usfx corp.local -d 10.0.0.1 -s small         # ~500 words
+    usfx corp.local -d 10.0.0.1 -s medium        # ~3500 words (default)
+    usfx corp.local -d 10.0.0.1 -s large         # ~10000 words
+    usfx corp.local -d 10.0.0.1 -w /path/to/custom.txt
+
+Output Options:
+
+    usfx corp.local -d 10.0.0.1 -o results.json
+    usfx corp.local -d 10.0.0.1 -o results.csv -f csv
+    usfx corp.local -d 10.0.0.1 -o results.txt -f txt
+
+Module Selection:
+
+    usfx corp.local -d 10.0.0.1 -m bruteforce,zone,records
+
+    Available modules:
+      bruteforce  - DNS brute force with wordlist
+      zone        - Zone transfer (AXFR)
+      dnssec      - DNSSEC zone walking
+      records     - DNS record mining
+      reverse     - Reverse DNS sweep
+      cname       - CNAME chain analysis
+      permutation - Subdomain permutation
+      recursive   - Recursive sub-subdomain enumeration
+      vhost       - Virtual host discovery
+      tls         - TLS certificate analysis
+
+Advanced Options:
+
+    usfx corp.local -d 10.0.0.1 -t 50 --timeout 5.0
+    usfx corp.local -d 10.0.0.1 --reverse-range 192.168.0.0/24
+    usfx corp.local -d 10.0.0.1 --vhost-ip 192.168.1.100
+    usfx corp.local -d 10.0.0.1 -v    # Verbose
+    usfx corp.local -d 10.0.0.1 -q    # Quiet
+
+
+CLI REFERENCE
+-------------
+
+Usage: usfx [OPTIONS] DOMAIN
+
+Options:
+  -d, --dns-server TEXT       DNS server IP (can be repeated)
+  -w, --wordlist PATH         Custom wordlist file
+  -s, --wordlist-size TEXT    Wordlist size: small|medium|large
+  -o, --output PATH           Output file path
+  -f, --format TEXT           Output format: json|csv|txt
+  -t, --threads INTEGER       Parallel threads (default: 30, max: 100)
+  --timeout FLOAT             DNS timeout in seconds (default: 3.0)
+  -m, --modules TEXT          Comma-separated module list
+  --reverse-range TEXT        CIDR range for reverse DNS
+  --vhost-ip TEXT             IP for vhost scanning
+  -v, --verbose               Verbose output
+  -q, --quiet                 Suppress non-essential output
+  --no-color                  Disable colored output
+  --version                   Show version
+  --help                      Show help message
+
+
+PYTHON API
+----------
+
+    from usfx import ScanConfig, SubdomainEngine
+    from usfx.config import WordlistSize
+
+    config = ScanConfig(
+        domain='corp.internal',
+        dns_servers=['10.0.0.1', '10.0.0.2'],
+        wordlist_size=WordlistSize.MEDIUM,
+        threads=50,
+        timeout=3.0
+    )
+
+    engine = SubdomainEngine()
+    result = engine.scan(config)
+
+    print(f"Found {result.total_found} subdomains")
+    for sub in result.subdomains:
+        print(f"  {sub.subdomain} -> {sub.ip}")
+
+
+MODULE DESCRIPTIONS
+-------------------
+
+  dns_bruteforce   Wordlist-based DNS queries           Medium
+  zone_transfer    AXFR zone transfer attempts          Fast
+  dnssec_walker    NSEC/NSEC3 zone walking              Fast
+  dns_records      MX/NS/TXT/SRV/SOA/CAA mining         Fast
+  reverse_dns      PTR lookups on IP ranges             Slow
+  cname_chaser     CNAME chain tracking                 Fast
+  permutation      Subdomain variation generation       Medium
+  recursive_enum   Sub-subdomain discovery              Medium
+  vhost_scanner    Host header brute force              Slow
+  tls_analyzer     TLS certificate SAN extraction       Medium
+
+
+REQUIREMENTS
+------------
+
+  - Python 3.10+
+  - dnspython >= 2.4.0
+  - click >= 8.1.0
+  - requests >= 2.28.0
+  - cryptography >= 41.0.0
+  - rich >= 13.0.0
+
+
+USE CASES
+---------
+
+Internal Network Penetration Testing:
+    usfx corp.internal -d 10.0.0.53 -s large -o findings.json
+
+Active Directory Reconnaissance:
+    usfx ad.corp.local -d 192.168.1.1 -m records,reverse,zone
+
+IT Asset Discovery:
+    usfx internal.company -d 172.16.0.1 --reverse-range 172.16.0.0/16
+
+
+COMPARISON WITH WEB USF
+-----------------------
+
+                        USFX            Web USF
+  Internet Required     No              Yes
+  Custom DNS Servers    Yes             No
+  External APIs         No              CT Logs, Wayback, etc.
+  Database Storage      No              MySQL
+  Web Interface         No              Yes
+  Real-time Progress    CLI             WebSocket
+
+
+LICENSE
+-------
+
+MIT License - See LICENSE file for details.
diff --git a/pyproject.toml b/pyproject.toml
@@ -0,0 +1,71 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "usfx"
+version = "1.0.0"
+description = "USFX - Ultimate Subdomain Finder X - Discover subdomains via custom DNS servers"
+readme = "README.txt"
+license = "MIT"
+requires-python = ">=3.10"
+authors = [
+    { name = "LACRYMARIA Team" }
+]
+keywords = ["subdomain", "dns", "security", "pentest", "enumeration", "reconnaissance"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Environment :: Console",
+    "Intended Audience :: Information Technology",
+    "Intended Audience :: System Administrators",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Topic :: Security",
+    "Topic :: System :: Networking",
+]
+dependencies = [
+    "dnspython>=2.4.0",
+    "click>=8.1.0",
+    "requests>=2.28.0",
+    "cryptography>=41.0.0",
+    "rich>=13.0.0",
+]
+
+[project.optional-dependencies]
+dev = [
+    "pytest>=7.0.0",
+    "pytest-cov>=4.0.0",
+    "black>=23.0.0",
+    "ruff>=0.1.0",
+]
+
+[project.scripts]
+usfx = "usfx.cli:main"
+
+[project.urls]
+Homepage = "https://github.com/nextvt/usfx"
+Repository = "https://github.com/nextvt/usfx"
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/usfx"]
+
+[tool.hatch.build.targets.sdist]
+include = [
+    "/src",
+    "/README.txt",
+    "/LICENSE",
+]
+
+[tool.black]
+line-length = 100
+target-version = ["py310", "py311", "py312"]
+
+[tool.ruff]
+line-length = 100
+target-version = "py310"
+select = ["E", "F", "W", "I", "N", "UP", "B"]
+ignore = ["E501"]
diff --git a/src/usfx/__init__.py b/src/usfx/__init__.py
@@ -0,0 +1,21 @@
+"""
+USFX - Ultimate Subdomain Finder for Internal Networks
+
+A standalone CLI tool for discovering subdomains on internal networks
+using custom DNS servers. Designed for air-gapped environments without
+internet connectivity.
+"""
+
+__version__ = "1.0.0"
+__author__ = "NEXTVT Team"
+
+from .config import ScanConfig
+from .engine import SubdomainEngine
+from .orchestrator import SubdomainOrchestrator
+
+__all__ = [
+    "__version__",
+    "ScanConfig",
+    "SubdomainEngine",
+    "SubdomainOrchestrator",
+]
diff --git a/src/usfx/__main__.py b/src/usfx/__main__.py
@@ -0,0 +1,8 @@
+"""
+Entry point for running as a module: python -m usfx
+"""
+
+from .cli import main
+
+if __name__ == "__main__":
+    main()
diff --git a/src/usfx/cli.py b/src/usfx/cli.py
@@ -0,0 +1,310 @@
+"""
+USFX CLI
+
+Command-line interface for the Ultimate Subdomain Finder.
+Supports custom DNS servers for internal network enumeration.
+"""
+
+import logging
+import sys
+from pathlib import Path
+from typing import List, Optional, Tuple
+
+import click
+
+from . import __version__
+from .config import OutputFormat, ScanConfig, WordlistSize
+from .engine import SubdomainEngine
+from .utils.output import ConsoleOutput, OutputFormatter
+
+
+def setup_logging(verbose: bool, quiet: bool) -> None:
+    """Configure logging based on verbosity settings"""
+    if quiet:
+        level = logging.WARNING
+    elif verbose:
+        level = logging.DEBUG
+    else:
+        level = logging.INFO
+
+    logging.basicConfig(
+        level=level,
+        format='%(levelname)s: %(message)s' if not verbose else '%(asctime)s %(levelname)s [%(name)s] %(message)s',
+        handlers=[logging.StreamHandler(sys.stderr)]
+    )
+
+    # Suppress noisy third-party loggers
+    logging.getLogger('urllib3').setLevel(logging.WARNING)
+    logging.getLogger('requests').setLevel(logging.WARNING)
+
+
+def parse_modules(modules_str: Optional[str]) -> Optional[set]:
+    """Parse comma-separated module list"""
+    if not modules_str:
+        return None
+
+    valid_modules = {
+        'bruteforce', 'dns_bruteforce',
+        'zone', 'zone_transfer',
+        'dnssec', 'dnssec_walker',
+        'records', 'dns_records',
+        'reverse', 'reverse_dns',
+        'cname', 'cname_chaser',
+        'permutation',
+        'recursive', 'recursive_enum',
+        'vhost', 'vhost_scanner',
+        'tls', 'tls_analyzer'
+    }
+
+    # Normalize module names
+    module_map = {
+        'bruteforce': 'dns_bruteforce',
+        'zone': 'zone_transfer',
+        'dnssec': 'dnssec_walker',
+        'records': 'dns_records',
+        'reverse': 'reverse_dns',
+        'cname': 'cname_chaser',
+        'recursive': 'recursive_enum',
+        'vhost': 'vhost_scanner',
+        'tls': 'tls_analyzer',
+    }
+
+    modules = set()
+    for m in modules_str.split(','):
+        m = m.strip().lower()
+        if m in module_map:
+            modules.add(module_map[m])
+        elif m in valid_modules:
+            modules.add(m)
+        else:
+            raise click.BadParameter(f"Unknown module: {m}")
+
+    return modules
+
+
+@click.command()
+@click.argument('domain')
+@click.option(
+    '-d', '--dns-server',
+    'dns_servers',
+    multiple=True,
+    help='DNS server IP address (can be specified multiple times for multiple servers)'
+)
+@click.option(
+    '-w', '--wordlist',
+    'wordlist_path',
+    type=click.Path(exists=True),
+    help='Path to custom wordlist file'
+)
+@click.option(
+    '-s', '--wordlist-size',
+    'wordlist_size',
+    type=click.Choice(['small', 'medium', 'large']),
+    default='medium',
+    help='Size of bundled wordlist (default: medium)'
+)
+@click.option(
+    '-o', '--output',
+    'output_path',
+    type=click.Path(),
+    help='Output file path'
+)
+@click.option(
+    '-f', '--format',
+    'output_format',
+    type=click.Choice(['json', 'csv', 'txt']),
+    default='json',
+    help='Output format (default: json)'
+)
+@click.option(
+    '-t', '--threads',
+    default=30,
+    type=click.IntRange(1, 100),
+    help='Number of parallel threads (default: 30)'
+)
+@click.option(
+    '--timeout',
+    default=3.0,
+    type=click.FloatRange(0.5, 30.0),
+    help='DNS query timeout in seconds (default: 3.0)'
+)
+@click.option(
+    '-m', '--modules',
+    'modules_str',
+    help='Comma-separated list of modules to run (e.g., bruteforce,zone,dnssec)'
+)
+@click.option(
+    '--reverse-range',
+    'reverse_ranges',
+    multiple=True,
+    help='CIDR range for reverse DNS scanning (e.g., 192.168.0.0/24)'
+)
+@click.option(
+    '--vhost-ip',
+    'vhost_ips',
+    multiple=True,
+    help='IP address for virtual host scanning'
+)
+@click.option(
+    '-v', '--verbose',
+    is_flag=True,
+    help='Enable verbose output'
+)
+@click.option(
+    '-q', '--quiet',
+    is_flag=True,
+    help='Suppress non-essential output'
+)
+@click.option(
+    '--no-color',
+    is_flag=True,
+    help='Disable colored output'
+)
+@click.version_option(version=__version__, prog_name='USFX')
+def main(
+    domain: str,
+    dns_servers: Tuple[str, ...],
+    wordlist_path: Optional[str],
+    wordlist_size: str,
+    output_path: Optional[str],
+    output_format: str,
+    threads: int,
+    timeout: float,
+    modules_str: Optional[str],
+    reverse_ranges: Tuple[str, ...],
+    vhost_ips: Tuple[str, ...],
+    verbose: bool,
+    quiet: bool,
+    no_color: bool
+) -> None:
+    """
+    USFX - Ultimate Subdomain Finder X for Internal Networks
+
+    Discover subdomains on internal networks using custom DNS servers.
+
+    Examples:
+
+        # Basic scan using system DNS
+        usfx corp.local
+
+        # Scan with internal DNS server
+        usfx corp.local -d 192.168.1.1
+
+        # Scan with multiple DNS servers
+        usfx corp.local -d 192.168.1.1 -d 192.168.1.2
+
+        # Quick scan with small wordlist
+        usfx corp.local -d 10.0.0.1 -s small
+
+        # Full scan with large wordlist
+        usfx corp.local -d 10.0.0.1 -s large -t 50
+
+        # Save results to JSON file
+        usfx corp.local -d 10.0.0.1 -o results.json
+
+        # Save as CSV
+        usfx corp.local -d 10.0.0.1 -o results.csv -f csv
+
+        # Only run specific modules
+        usfx corp.local -d 10.0.0.1 -m bruteforce,zone,records
+
+        # Scan with reverse DNS range
+        usfx corp.local -d 10.0.0.1 --reverse-range 192.168.0.0/16
+
+        # Scan with vhost discovery on specific IP
+        usfx corp.local -d 10.0.0.1 --vhost-ip 192.168.1.100
+    """
+    # Setup logging
+    setup_logging(verbose, quiet)
+
+    # Create console output helper
+    console = ConsoleOutput(use_rich=not no_color, quiet=quiet)
+
+    # Print banner
+    console.print_banner(__version__)
+
+    # Parse modules
+    try:
+        modules = parse_modules(modules_str)
+    except click.BadParameter as e:
+        console.print_error(str(e))
+        sys.exit(1)
+
+    # Create scan configuration
+    try:
+        config = ScanConfig(
+            domain=domain,
+            dns_servers=list(dns_servers),
+            wordlist_size=WordlistSize(wordlist_size),
+            custom_wordlist=Path(wordlist_path) if wordlist_path else None,
+            threads=threads,
+            timeout=timeout,
+            modules=modules,
+            reverse_dns_ranges=list(reverse_ranges),
+            vhost_ips=list(vhost_ips),
+            output_file=Path(output_path) if output_path else None,
+            output_format=OutputFormat(output_format),
+            verbose=verbose,
+            quiet=quiet
+        )
+    except Exception as e:
+        console.print_error(f"Invalid configuration: {e}")
+        sys.exit(1)
+
+    # Print configuration
+    enabled_modules = sorted(config.get_enabled_modules())
+    console.print_config(
+        domain=config.domain,
+        dns_servers=config.dns_servers,
+        modules=enabled_modules
+    )
+
+    # Create engine and run scan
+    engine = SubdomainEngine()
+
+    # Progress tracking
+    last_phase = None
+
+    def progress_callback(state):
+        nonlocal last_phase
+        if state.current_phase and state.current_phase != last_phase:
+            console.print_phase(state.current_phase.value)
+            last_phase = state.current_phase
+
+    try:
+        result = engine.scan(config, progress_callback=progress_callback if not quiet else None)
+    except KeyboardInterrupt:
+        console.print_error("Scan cancelled by user")
+        engine.cancel_scan()
+        sys.exit(130)
+    except Exception as e:
+        console.print_error(f"Scan failed: {e}")
+        if verbose:
+            import traceback
+            traceback.print_exc()
+        sys.exit(1)
+
+    # Print results
+    console.print_result_table(result)
+    console.print_summary(result)
+
+    # Write output file if requested
+    if config.output_file:
+        try:
+            OutputFormatter.write_file(
+                result,
+                config.output_file,
+                config.output_format.value
+            )
+            if not quiet:
+                click.echo(f"\nResults saved to: {config.output_file}")
+        except Exception as e:
+            console.print_error(f"Failed to write output file: {e}")
+            sys.exit(1)
+
+    # Exit with appropriate code
+    sys.exit(0 if result.total_found > 0 else 1)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/usfx/config.py b/src/usfx/config.py
@@ -0,0 +1,132 @@
+"""
+Scan Configuration
+
+Dataclasses for configuring subdomain enumeration scans.
+"""
+
+from dataclasses import dataclass, field
+from enum import Enum
+from pathlib import Path
+from typing import List, Optional, Set
+
+
+class WordlistSize(Enum):
+    """Predefined wordlist sizes"""
+    SMALL = "small"      # ~500 words - quick scan
+    MEDIUM = "medium"    # ~3500 words - default
+    LARGE = "large"      # ~10000 words - thorough
+
+
+class OutputFormat(Enum):
+    """Output format types"""
+    JSON = "json"
+    CSV = "csv"
+    TXT = "txt"
+
+
+@dataclass
+class ScanConfig:
+    """Configuration for a subdomain scan"""
+
+    # Target domain (required)
+    domain: str
+
+    # DNS server configuration (core feature for internal networks)
+    dns_servers: List[str] = field(default_factory=list)
+
+    # Wordlist configuration
+    wordlist_size: WordlistSize = WordlistSize.MEDIUM
+    custom_wordlist: Optional[Path] = None
+
+    # Performance settings
+    threads: int = 30
+    timeout: float = 3.0
+
+    # Module selection (None = all modules)
+    modules: Optional[Set[str]] = None
+
+    # Additional options
+    reverse_dns_ranges: List[str] = field(default_factory=list)  # CIDR ranges
+    vhost_ips: List[str] = field(default_factory=list)  # IPs for vhost scanning
+
+    # Output configuration
+    output_file: Optional[Path] = None
+    output_format: OutputFormat = OutputFormat.JSON
+
+    # Verbosity
+    verbose: bool = False
+    quiet: bool = False
+
+    def __post_init__(self):
+        """Validate and normalize configuration"""
+        # Normalize domain
+        self.domain = self.domain.lower().strip('.')
+
+        # Validate threads
+        if self.threads < 1:
+            self.threads = 1
+        elif self.threads > 100:
+            self.threads = 100
+
+        # Validate timeout
+        if self.timeout < 0.5:
+            self.timeout = 0.5
+        elif self.timeout > 30.0:
+            self.timeout = 30.0
+
+    @property
+    def uses_custom_dns(self) -> bool:
+        """Check if custom DNS servers are configured"""
+        return len(self.dns_servers) > 0
+
+    def get_enabled_modules(self) -> Set[str]:
+        """Get the set of enabled module names"""
+        all_modules = {
+            "dns_bruteforce",
+            "zone_transfer",
+            "dnssec_walker",
+            "dns_records",
+            "reverse_dns",
+            "cname_chaser",
+            "permutation",
+            "recursive_enum",
+            "vhost_scanner",
+            "tls_analyzer",
+        }
+
+        if self.modules is None:
+            return all_modules
+
+        return self.modules & all_modules
+
+
+@dataclass
+class SubdomainResult:
+    """Result for a single discovered subdomain"""
+    subdomain: str
+    ip: Optional[str] = None
+    discovered_by: str = "unknown"
+    is_active: bool = False
+    cname_chain: List[str] = field(default_factory=list)
+
+
+@dataclass
+class ModuleResult:
+    """Result from a single module execution"""
+    module_name: str
+    found_count: int
+    subdomains: List[SubdomainResult]
+    duration_seconds: float
+    error: Optional[str] = None
+
+
+@dataclass
+class ScanResult:
+    """Complete scan result"""
+    domain: str
+    total_found: int
+    subdomains: List[SubdomainResult]
+    module_results: List[ModuleResult]
+    duration_seconds: float
+    dns_servers_used: List[str]
+    config: ScanConfig
diff --git a/src/usfx/data/__init__.py b/src/usfx/data/__init__.py
@@ -0,0 +1 @@
+# Data package marker for importlib.resources
diff --git a/src/usfx/data/wordlist.txt b/src/usfx/data/wordlist.txt
@@ -0,0 +1,3557 @@
+# Ultimate Subdomain Finder Wordlist
+# 10,000+ common subdomain names for brute force enumeration
+
+# === Common ===
+www
+mail
+ftp
+localhost
+webmail
+smtp
+pop
+pop3
+ns1
+ns2
+ns3
+ns4
+dns
+dns1
+dns2
+mx
+mx1
+mx2
+imap
+remote
+blog
+shop
+store
+forum
+wiki
+cpanel
+whm
+webdisk
+direct
+www2
+www3
+www4
+img
+image
+images
+media
+static
+assets
+cdn
+api
+dev
+test
+stage
+staging
+prod
+production
+beta
+alpha
+demo
+qa
+uat
+sandbox
+
+# === Web Servers ===
+web
+web1
+web2
+web3
+web01
+web02
+web03
+app
+app1
+app2
+app3
+app01
+app02
+app03
+server
+server1
+server2
+server01
+server02
+srv
+srv1
+srv2
+srv01
+srv02
+node
+node1
+node2
+node01
+node02
+host
+host1
+host2
+origin
+origin-www
+gateway
+proxy
+reverse
+frontend
+backend
+
+# === Development ===
+dev
+dev1
+dev2
+dev01
+dev02
+develop
+development
+test
+test1
+test2
+test01
+test02
+testing
+stage
+stage1
+stage2
+staging
+staging1
+staging2
+pre
+preprod
+pre-prod
+pre-production
+uat
+uat1
+uat2
+qa
+qa1
+qa2
+sandbox
+sandbox1
+sandbox2
+demo
+demo1
+demo2
+alpha
+beta
+gamma
+local
+localhost
+preview
+canary
+nightly
+edge
+
+# === Production ===
+prod
+prod1
+prod2
+prod01
+prod02
+production
+production1
+production2
+live
+live1
+live2
+stable
+release
+master
+main
+primary
+secondary
+
+# === API ===
+api
+api1
+api2
+api3
+api-v1
+api-v2
+api-v3
+api-gateway
+api-dev
+api-test
+api-staging
+api-prod
+rest
+rest-api
+restapi
+graphql
+graphql-api
+gql
+grpc
+rpc
+ws
+websocket
+socket
+realtime
+stream
+
+# === Admin ===
+admin
+admin1
+admin2
+administrator
+administrator1
+adm
+superadmin
+sysadmin
+webadmin
+control
+controlpanel
+control-panel
+panel
+cpanel
+whm
+plesk
+horde
+roundcube
+manage
+management
+console
+dashboard
+
+# === Authentication ===
+auth
+auth1
+auth2
+authentication
+login
+signin
+signon
+sign-in
+sign-on
+logout
+signout
+sign-out
+sso
+single-sign-on
+oauth
+oauth2
+openid
+saml
+identity
+id
+idp
+identityprovider
+accounts
+account
+users
+user
+members
+member
+register
+registration
+signup
+sign-up
+password
+forgot-password
+reset-password
+
+# === Database ===
+db
+db1
+db2
+db01
+db02
+database
+database1
+database2
+mysql
+mysql1
+mysql2
+mariadb
+postgres
+postgresql
+pgsql
+mongo
+mongodb
+redis
+redis1
+redis2
+memcache
+memcached
+cache
+cache1
+cache2
+elastic
+elasticsearch
+es
+es1
+es2
+solr
+lucene
+sphinx
+cassandra
+couchdb
+dynamodb
+rds
+aurora
+
+# === Mail ===
+mail
+mail1
+mail2
+mail01
+mail02
+smtp
+smtp1
+smtp2
+pop
+pop3
+imap
+imap4
+webmail
+webmail1
+webmail2
+email
+email1
+email2
+mx
+mx1
+mx2
+mx3
+mx4
+relay
+relay1
+relay2
+mailserver
+exchange
+owa
+outlook
+autodiscover
+autoconfig
+postfix
+sendmail
+exim
+dovecot
+roundcube
+horde
+squirrelmail
+newsletter
+mailing
+lists
+listserv
+
+# === Security ===
+secure
+security
+sec
+ssl
+tls
+https
+vpn
+vpn1
+vpn2
+openvpn
+ipsec
+firewall
+fw
+waf
+ids
+ips
+siem
+soc
+cert
+certs
+certificates
+pki
+ca
+key
+keys
+vault
+secrets
+2fa
+mfa
+otp
+totp
+
+# === Infrastructure ===
+ns
+ns1
+ns2
+ns3
+ns4
+dns
+dns1
+dns2
+dns3
+dns4
+nameserver
+nameserver1
+nameserver2
+resolver
+recursive
+authoritative
+ntp
+ntp1
+ntp2
+time
+ldap
+ldap1
+ldap2
+ad
+ads
+active-directory
+directory
+radius
+tacacs
+dhcp
+dhcp1
+dhcp2
+tftp
+pxe
+boot
+
+# === Monitoring ===
+monitor
+monitoring
+mon
+nagios
+zabbix
+prometheus
+grafana
+kibana
+splunk
+datadog
+newrelic
+dynatrace
+elastic
+elk
+logstash
+graylog
+sentry
+pagerduty
+status
+uptime
+healthcheck
+health
+metrics
+stats
+statistics
+analytics
+apm
+trace
+tracing
+
+# === Storage ===
+storage
+storage1
+storage2
+files
+file
+fileserver
+nas
+san
+nfs
+smb
+samba
+cifs
+ftp
+sftp
+ftps
+tftp
+s3
+minio
+backup
+backup1
+backup2
+backups
+archive
+archives
+data
+data1
+data2
+media
+media1
+media2
+upload
+uploads
+download
+downloads
+share
+shares
+public
+private
+
+# === CI/CD & DevOps ===
+ci
+cd
+cicd
+ci-cd
+jenkins
+jenkins1
+jenkins2
+bamboo
+travis
+circleci
+gitlab
+gitlab-ci
+github
+bitbucket
+git
+git1
+git2
+gitea
+gogs
+svn
+subversion
+hg
+mercurial
+cvs
+repo
+repos
+repository
+repositories
+artifact
+artifacts
+artifactory
+nexus
+sonatype
+registry
+docker
+docker-registry
+container
+containers
+k8s
+kubernetes
+rancher
+openshift
+helm
+chart
+charts
+
+# === Cloud ===
+cloud
+cloud1
+cloud2
+aws
+amazon
+azure
+gcp
+google
+googlecloud
+digitalocean
+linode
+vultr
+heroku
+vercel
+netlify
+cloudflare
+fastly
+akamai
+cloudfront
+aliyun
+alibaba
+tencent
+huawei
+oracle
+ibm
+ovh
+hetzner
+scaleway
+upcloud
+
+# === CDN & Assets ===
+cdn
+cdn1
+cdn2
+static
+static1
+static2
+assets
+assets1
+assets2
+img
+image
+images
+images1
+images2
+photo
+photos
+pic
+pics
+video
+videos
+media
+media1
+media2
+js
+javascript
+css
+stylesheet
+fonts
+font
+resources
+resource
+content
+files
+download
+downloads
+public
+pub
+
+# === Mobile ===
+mobile
+m
+mobile-api
+mobileapi
+app-api
+appapi
+ios
+android
+mobile-web
+webapp
+pwa
+
+# === Internal ===
+internal
+intranet
+int
+corp
+corporate
+company
+office
+hq
+headquarters
+staff
+employee
+employees
+hr
+human-resources
+payroll
+finance
+accounting
+sales
+marketing
+legal
+it
+helpdesk
+support
+ticket
+tickets
+service
+servicedesk
+
+# === Regional ===
+us
+us1
+us2
+us-east
+us-west
+na
+northamerica
+eu
+eu1
+eu2
+europe
+uk
+gb
+de
+germany
+fr
+france
+es
+spain
+it
+italy
+nl
+netherlands
+asia
+ap
+apac
+jp
+japan
+kr
+korea
+cn
+china
+sg
+singapore
+au
+australia
+in
+india
+br
+brazil
+latam
+la
+emea
+mea
+africa
+
+# === Services ===
+www
+web
+portal
+gateway
+proxy
+lb
+loadbalancer
+load-balancer
+haproxy
+nginx
+apache
+httpd
+tomcat
+jboss
+wildfly
+weblogic
+websphere
+iis
+glassfish
+jetty
+gunicorn
+unicorn
+puma
+passenger
+uwsgi
+supervisor
+pm2
+
+# === Communication ===
+chat
+im
+instant-messaging
+slack
+teams
+zoom
+meet
+conference
+video
+voip
+sip
+asterisk
+pbx
+phone
+telephone
+call
+calls
+fax
+sms
+mms
+push
+notification
+notifications
+
+# === E-commerce ===
+shop
+store
+ecommerce
+e-commerce
+cart
+checkout
+payment
+payments
+pay
+billing
+invoice
+invoices
+order
+orders
+catalog
+product
+products
+inventory
+shipping
+tracking
+
+# === CMS ===
+cms
+wordpress
+wp
+blog
+drupal
+joomla
+magento
+shopify
+wix
+squarespace
+woocommerce
+prestashop
+opencart
+ghost
+medium
+typo3
+contentful
+strapi
+sanity
+prismic
+
+# === Analytics ===
+analytics
+stats
+statistics
+tracking
+pixel
+tag
+tags
+gtm
+ga
+google-analytics
+matomo
+piwik
+hotjar
+heap
+mixpanel
+amplitude
+segment
+optimizely
+ab-test
+abtest
+abtesting
+experiment
+
+# === Documentation ===
+docs
+doc
+documentation
+wiki
+kb
+knowledgebase
+knowledge-base
+help
+support
+faq
+faqs
+manual
+manuals
+guide
+guides
+tutorial
+tutorials
+reference
+references
+readme
+changelog
+
+# === Collaboration ===
+confluence
+jira
+trello
+notion
+asana
+monday
+basecamp
+clickup
+linear
+shortcut
+clubhouse
+gitlab
+github
+bitbucket
+slack
+discord
+teams
+
+# === CRM & ERP ===
+crm
+erp
+salesforce
+sf
+hubspot
+zoho
+dynamics
+sap
+oracle
+workday
+servicenow
+zendesk
+freshdesk
+intercom
+drift
+
+# === Misc Patterns ===
+old
+new
+legacy
+archive
+archives
+bak
+backup
+temp
+tmp
+cache
+log
+logs
+debug
+trace
+info
+test1
+test2
+test3
+dev1
+dev2
+dev3
+stage1
+stage2
+prod1
+prod2
+www1
+www2
+www3
+ns01
+ns02
+mx01
+mx02
+web01
+web02
+app01
+app02
+db01
+db02
+srv01
+srv02
+node01
+node02
+
+# === Numbers ===
+1
+2
+3
+01
+02
+03
+001
+002
+003
+
+# === Prefixed common ===
+dev-api
+test-api
+stage-api
+prod-api
+dev-web
+test-web
+stage-web
+prod-web
+dev-app
+test-app
+stage-app
+prod-app
+api-dev
+api-test
+api-stage
+api-prod
+web-dev
+web-test
+web-stage
+web-prod
+app-dev
+app-test
+app-stage
+app-prod
+
+# === Suffixed common ===
+www-dev
+www-test
+www-stage
+www-prod
+mail-dev
+mail-test
+mail-stage
+mail-prod
+cdn-dev
+cdn-test
+cdn-stage
+cdn-prod
+
+# === Hyphenated variants ===
+dev-server
+test-server
+staging-server
+production-server
+dev-db
+test-db
+staging-db
+prod-db
+dev-api
+staging-api
+prod-api
+dev-web
+staging-web
+prod-web
+
+# === Technology specific ===
+jenkins
+sonarqube
+artifactory
+nexus
+vault
+consul
+nomad
+terraform
+ansible
+puppet
+chef
+salt
+prometheus
+grafana
+elasticsearch
+kibana
+logstash
+fluentd
+jaeger
+zipkin
+istio
+envoy
+nginx-ingress
+traefik
+harbor
+quay
+portainer
+rancher
+lens
+
+# === Gaming/Entertainment ===
+game
+games
+gaming
+play
+player
+server
+gameserver
+minecraft
+steam
+origin
+uplay
+epic
+twitch
+youtube
+stream
+streaming
+live
+broadcast
+vod
+
+# === Education ===
+learn
+learning
+edu
+education
+school
+university
+college
+student
+students
+teacher
+teachers
+course
+courses
+class
+classroom
+lms
+moodle
+canvas
+blackboard
+library
+
+# === Healthcare ===
+health
+healthcare
+medical
+patient
+patients
+doctor
+doctors
+hospital
+clinic
+pharmacy
+rx
+prescription
+lab
+labs
+laboratory
+ehr
+emr
+pacs
+hl7
+fhir
+
+# === Finance ===
+finance
+financial
+bank
+banking
+trading
+trade
+invest
+investment
+wallet
+crypto
+bitcoin
+blockchain
+payment
+payments
+transfer
+wire
+ach
+swift
+
+# === Government ===
+gov
+government
+city
+county
+state
+federal
+portal
+citizen
+service
+services
+tax
+taxes
+license
+permit
+court
+police
+fire
+emergency
+
+# === Telecom ===
+telecom
+telco
+network
+networks
+internet
+isp
+broadband
+fiber
+cable
+wireless
+wifi
+5g
+4g
+lte
+mobile
+voip
+pbx
+
+# === Manufacturing ===
+erp
+mes
+scada
+plc
+iot
+sensor
+sensors
+device
+devices
+machine
+machines
+factory
+plant
+production
+assembly
+quality
+qa
+
+# === Logistics ===
+logistics
+supply
+supply-chain
+warehouse
+warehouses
+inventory
+shipping
+tracking
+fleet
+delivery
+order
+orders
+fulfillment
+distribution
+
+# === Real Estate ===
+property
+properties
+real-estate
+realestate
+listing
+listings
+agent
+agents
+broker
+brokers
+mls
+home
+homes
+house
+houses
+apartment
+apartments
+rent
+rental
+lease
+
+# === Hospitality ===
+hotel
+hotels
+booking
+reservation
+reservations
+guest
+guests
+room
+rooms
+restaurant
+restaurants
+food
+menu
+bar
+spa
+resort
+
+# === Retail ===
+store
+stores
+shop
+shopping
+retail
+pos
+inventory
+product
+products
+catalog
+order
+orders
+customer
+customers
+loyalty
+rewards
+
+# === Automotive ===
+auto
+automotive
+car
+cars
+vehicle
+vehicles
+dealer
+dealership
+service
+parts
+warranty
+lease
+finance
+
+# === Energy ===
+energy
+power
+electric
+electricity
+gas
+oil
+solar
+wind
+renewable
+grid
+meter
+meters
+utility
+utilities
+
+# === Agriculture ===
+farm
+farming
+agriculture
+agri
+crop
+crops
+field
+fields
+livestock
+dairy
+grain
+harvest
+
+# === Construction ===
+construction
+project
+projects
+site
+sites
+bid
+bids
+contract
+contracts
+permit
+permits
+safety
+equipment
+
+# === Legal ===
+legal
+law
+attorney
+lawyers
+court
+case
+cases
+document
+documents
+contract
+contracts
+compliance
+regulatory
+
+# === HR ===
+hr
+human-resources
+recruit
+recruiting
+talent
+hire
+hiring
+employee
+employees
+payroll
+benefits
+training
+onboarding
+
+# === Marketing ===
+marketing
+brand
+branding
+campaign
+campaigns
+social
+social-media
+content
+creative
+seo
+sem
+ppc
+email-marketing
+automation
+
+# === Research ===
+research
+lab
+labs
+laboratory
+science
+data
+analytics
+ai
+ml
+machine-learning
+deep-learning
+nlp
+cv
+computer-vision
+
+# === Event ===
+event
+events
+conference
+conferences
+meeting
+meetings
+webinar
+webinars
+registration
+ticket
+tickets
+
+# === Emerging Tech ===
+ai
+ml
+deep-learning
+neural
+model
+models
+training
+inference
+gpu
+tpu
+cuda
+tensorflow
+pytorch
+jupyter
+notebook
+notebooks
+colab
+sagemaker
+vertex
+bedrock
+openai
+anthropic
+huggingface
+
+# === Additional Common ===
+about
+access
+account
+accounts
+adserver
+adm
+admin
+administrator
+admins
+affiliate
+affiliates
+agent
+agents
+ajax
+alerts
+all
+alpha
+amp
+android
+api
+apis
+app
+application
+apps
+apt
+archive
+archives
+assets
+assist
+atom
+auth
+auto
+autodiscover
+autoconfig
+automation
+avatar
+avatars
+aws
+b2b
+b2c
+backend
+backup
+backups
+banner
+banners
+base
+batch
+bb
+bbs
+beta
+billing
+bin
+binaries
+binary
+biz
+blog
+blogs
+board
+boards
+bot
+bots
+box
+broadcast
+broker
+bucket
+buckets
+bug
+bugs
+build
+builds
+bulk
+bulletin
+business
+buy
+cache
+calendar
+call
+callback
+calls
+campaign
+campaigns
+captcha
+card
+cards
+career
+careers
+cart
+cas
+catalog
+cdn
+central
+cgi
+change
+changes
+channel
+channels
+chart
+charts
+chat
+ci
+client
+clients
+cloud
+cluster
+clusters
+cms
+cn
+code
+codes
+collect
+collection
+collections
+comm
+comment
+comments
+commerce
+common
+community
+company
+compare
+component
+components
+conf
+config
+configuration
+connect
+connection
+connector
+connectors
+console
+consult
+contact
+contacts
+container
+containers
+content
+control
+controller
+corp
+corporate
+coupons
+coverage
+cp
+cpanel
+crash
+crm
+cron
+cronjob
+cronjobs
+crowd
+crypto
+cs
+css
+customer
+customers
+cv
+d
+daemon
+daily
+dash
+dashboard
+data
+database
+db
+dealer
+debug
+default
+demo
+demos
+deploy
+deployment
+design
+desktop
+detect
+detection
+dev
+devel
+develop
+developer
+developers
+development
+device
+devices
+devops
+dfp
+dig
+digital
+dir
+direct
+directory
+disabled
+discuss
+discussion
+disk
+dispatcher
+dl
+dns
+doc
+docker
+docs
+document
+documentation
+documents
+domain
+domains
+donate
+download
+downloads
+draft
+drafts
+drive
+drop
+drupal
+dump
+dynamic
+e
+ecommerce
+edge
+edit
+editor
+edu
+education
+el
+elastic
+elasticsearch
+element
+elements
+email
+embed
+employee
+employees
+en
+endpoint
+endpoints
+engine
+engineering
+enterprise
+entries
+entry
+env
+environment
+environments
+error
+errors
+es
+eu
+event
+events
+example
+examples
+exchange
+exec
+execute
+export
+external
+extranet
+f
+facebook
+faq
+faqs
+farm
+fast
+fax
+feature
+features
+feed
+feedback
+feeds
+field
+fields
+file
+files
+filter
+filters
+finance
+financial
+find
+finder
+fire
+firewall
+first
+fix
+fixes
+flash
+fleet
+flow
+folder
+folders
+font
+fonts
+food
+forex
+form
+forms
+forum
+forums
+forward
+found
+frame
+framework
+free
+freeware
+fresh
+frontend
+fs
+ftp
+ftps
+full
+function
+functions
+fund
+future
+fw
+g
+gallery
+game
+games
+gateway
+gen
+general
+generate
+generator
+get
+gis
+git
+github
+gitlab
+global
+go
+google
+gov
+gps
+graph
+graphql
+graphs
+green
+grid
+group
+groups
+grow
+growth
+gsm
+guest
+guests
+gui
+guide
+guides
+gw
+h
+hack
+hacking
+hadoop
+handle
+handler
+handlers
+hardware
+hash
+hd
+hdr
+health
+healthcare
+heap
+heartbeat
+help
+helpdesk
+hidden
+high
+history
+hits
+hk
+hls
+hm
+home
+homepage
+hook
+hooks
+horizon
+hospital
+host
+hosted
+hosting
+hostmaster
+hosts
+hot
+hotel
+house
+how
+howto
+hp
+hr
+hs
+html
+http
+https
+hub
+hvac
+i
+i18n
+id
+identity
+ids
+ig
+iis
+im
+image
+images
+imap
+import
+in
+inbox
+inbound
+include
+includes
+incoming
+index
+indexer
+india
+industries
+industry
+info
+infra
+infrastructure
+ingest
+init
+inline
+inner
+inout
+input
+inputs
+inquiry
+inside
+insights
+install
+installer
+instance
+instances
+int
+integration
+integrations
+intel
+intelligence
+interact
+interactive
+interface
+interfaces
+intern
+internal
+international
+internet
+intranet
+intro
+inventory
+invite
+io
+ios
+iot
+ip
+iphone
+ipmi
+ips
+ipsec
+ipv4
+ipv6
+irc
+ircd
+is
+iso
+isp
+issue
+issues
+it
+item
+items
+itunes
+j
+ja
+java
+javascript
+jb
+jenkins
+jira
+job
+jobs
+join
+journal
+jquery
+js
+json
+jump
+jumpbox
+k
+k8s
+kafka
+kali
+karma
+kb
+kdc
+keep
+kernel
+key
+keys
+kibana
+kickstart
+kill
+kiosk
+kit
+km
+know
+knowledge
+known
+ko
+kube
+kubernetes
+l
+l10n
+lab
+label
+labels
+labs
+landing
+language
+large
+last
+latest
+launch
+launcher
+layer
+layout
+lb
+ld
+ldap
+lead
+leads
+lean
+learn
+learning
+legacy
+legal
+letsencrypt
+lib
+libraries
+library
+license
+lifecycle
+light
+like
+limit
+limits
+line
+linear
+link
+linkedin
+links
+linux
+list
+listen
+lists
+listserv
+live
+lms
+load
+loadbalancer
+loader
+loading
+local
+locale
+localhost
+location
+locations
+lock
+locked
+log
+loghost
+logging
+logic
+login
+logon
+logout
+logs
+long
+loop
+low
+lp
+ls
+lync
+m
+m2m
+mac
+machine
+machines
+mail
+mail1
+mail2
+mail3
+mail4
+mail5
+mailbox
+maildir
+mailer
+mailgate
+mailhost
+mailing
+mailman
+mailserver
+main
+maintenance
+make
+manage
+managed
+management
+manager
+manifest
+manual
+manuals
+map
+mapping
+maps
+market
+marketing
+marketplace
+master
+match
+matrix
+maven
+max
+mc
+mcp
+md
+mdb
+me
+measure
+media
+meeting
+meetings
+member
+members
+membership
+memo
+memory
+merge
+mesh
+message
+messages
+messaging
+meta
+metadata
+meter
+metric
+metrics
+mgmt
+mi
+mic
+micro
+microsoft
+middleware
+migration
+min
+mirror
+mirrors
+misc
+mission
+mix
+ml
+mm
+mms
+mo
+mob
+mobile
+mock
+mod
+model
+models
+modern
+module
+modules
+mon
+mongo
+mongodb
+monitor
+monitoring
+monthly
+more
+mount
+movie
+movies
+mp
+mp3
+mp4
+mq
+mqtt
+mr
+ms
+mssql
+mt
+mta
+mts
+multi
+multimedia
+music
+mx
+mx1
+mx2
+my
+mysql
+n
+n1
+n2
+na
+name
+names
+nameserver
+naming
+nas
+nat
+nav
+navigation
+nb
+nc
+nd
+nds
+net
+netdata
+netflow
+netops
+network
+networking
+networks
+new
+newrelic
+news
+newsletter
+next
+nexus
+nfs
+ng
+nginx
+ni
+night
+nl
+nm
+nn
+no
+noc
+node
+nodes
+noi
+north
+nos
+not
+notes
+notice
+notification
+notifications
+notify
+now
+np
+ns
+ns1
+ns2
+ns3
+ns4
+ns5
+nt
+ntp
+ntp1
+ntp2
+null
+number
+numbers
+nw
+o
+oauth
+oauth2
+obe
+object
+objects
+observability
+oc
+ocs
+ocs2
+od
+ods
+of
+office
+office365
+offline
+ok
+old
+omega
+on
+one
+online
+ood
+oob
+oom
+op
+open
+openapi
+openid
+opensource
+openstack
+ops
+opt
+option
+options
+oracle
+orchestration
+order
+orders
+org
+org1
+org2
+organization
+origin
+os
+oss
+ost
+other
+out
+outage
+outbound
+outer
+outlook
+output
+outputs
+outreach
+outside
+outsourcing
+over
+owa
+owl
+own
+owner
+p
+p2p
+pa
+package
+packages
+packaging
+page
+pages
+paid
+panel
+pano
+paper
+parallel
+param
+params
+parent
+park
+parking
+parse
+parser
+part
+partner
+partners
+parts
+party
+pass
+passenger
+password
+passwords
+paste
+patch
+patches
+path
+pay
+payment
+payments
+paypal
+payout
+pb
+pc
+pci
+pdf
+pe
+peak
+peer
+peering
+pen
+pending
+people
+perf
+performance
+perm
+permissions
+persist
+persistent
+person
+personal
+pet
+pf
+pg
+pgadmin
+phone
+phones
+photo
+photos
+php
+phpmyadmin
+pi
+pic
+pics
+picture
+pictures
+pilot
+pin
+ping
+pioneer
+pipeline
+pipelines
+pit
+pixel
+pk
+pkg
+pkgs
+place
+placement
+plan
+planner
+planning
+plans
+platform
+platforms
+play
+player
+playground
+please
+plesk
+plugin
+plugins
+plus
+pm
+pm2
+pma
+pms
+pn
+pns
+poc
+pod
+pods
+point
+points
+policies
+policy
+poll
+polls
+pop
+pop3
+popmail
+port
+portal
+portfolio
+pos
+position
+positions
+post
+postgre
+postgres
+postgresql
+postman
+postmaster
+posts
+power
+pp
+ppg
+ppm
+ppt
+pptx
+pr
+pre
+predict
+prediction
+pref
+preference
+preferences
+premium
+prep
+preprod
+presence
+present
+presentation
+press
+preview
+price
+prices
+pricing
+primary
+prime
+print
+printer
+printers
+printing
+priority
+prism
+privacy
+private
+privilege
+pro
+probe
+problem
+problems
+proc
+process
+processes
+processing
+processor
+prod
+product
+production
+products
+professional
+profile
+profiles
+profit
+program
+programs
+progress
+project
+projects
+promo
+promos
+promotion
+promotions
+prompt
+proof
+prop
+properties
+property
+proposal
+protect
+protection
+proto
+protocol
+prototype
+provide
+provider
+providers
+provision
+provisioning
+proxy
+prx
+ps
+pt
+pta
+ptp
+pts
+pub
+public
+publish
+publisher
+publishing
+pull
+pulse
+puppet
+purchase
+push
+put
+pw
+pxe
+python
+q
+qa
+qc
+qm
+qms
+qq
+qt
+query
+question
+questions
+queue
+queues
+quick
+quota
+quote
+quotes
+r
+r1
+r2
+r10
+rabbitmq
+rack
+radar
+radio
+radius
+rads
+raid
+rails
+rain
+random
+range
+rank
+ranking
+rapid
+rate
+rates
+rating
+ratings
+raw
+rc
+rcs
+rdc
+rds
+re
+react
+read
+readme
+ready
+real
+realtime
+reason
+rebuild
+receive
+receiver
+recent
+recommend
+recommendation
+recommendations
+record
+recorder
+records
+recover
+recovery
+recruit
+recruiting
+recycle
+red
+redirect
+redis
+ref
+reference
+references
+referral
+refresh
+reg
+region
+regional
+regions
+register
+registration
+registry
+related
+relay
+release
+releases
+reload
+remote
+removal
+remove
+render
+repair
+replica
+replicate
+replication
+reply
+report
+reporter
+reporters
+reporting
+reports
+repo
+repos
+repository
+request
+requests
+require
+requirements
+res
+research
+reseller
+resellers
+reserve
+reset
+residence
+resolve
+resolver
+resource
+resources
+response
+responses
+rest
+restart
+restore
+result
+results
+resume
+retail
+retention
+retire
+return
+returns
+rev
+reverse
+review
+reviews
+revision
+rg
+ri
+rich
+ride
+ring
+risk
+rl
+rm
+rn
+ro
+road
+robot
+robots
+role
+roles
+roll
+rollback
+rollout
+root
+rootmail
+ros
+rotate
+rotation
+route
+router
+routers
+routes
+routing
+row
+rows
+rp
+rpc
+rpm
+rr
+rs
+rsa
+rss
+rt
+rtc
+rtmp
+rtp
+ruby
+rule
+rules
+run
+runner
+running
+runtime
+rv
+rx
+s
+s1
+s2
+s3
+s4
+s5
+sa
+saa
+saas
+safe
+safety
+sale
+sales
+salesforce
+salt
+sam
+sample
+samples
+samba
+san
+sandbox
+sap
+sat
+satellite
+save
+sc
+scala
+scale
+scan
+scanner
+schedule
+scheduler
+scheduling
+schema
+schemes
+school
+science
+scope
+score
+scores
+scoring
+scr
+screen
+screens
+script
+scripts
+sd
+sdc
+sdn
+sdr
+se
+search
+seat
+seats
+sec
+second
+secondary
+secret
+secrets
+section
+sections
+secure
+secured
+security
+seed
+segment
+segmentation
+select
+self
+self-service
+sell
+sem
+seminar
+seminars
+send
+sender
+senders
+sendmail
+seo
+sequence
+sequoia
+series
+serve
+server
+server1
+server2
+serverless
+servers
+service
+services
+servlet
+session
+sessions
+set
+setting
+settings
+setup
+sf
+sftp
+sg
+sh
+shadow
+share
+shared
+sharepoint
+shares
+sharing
+shell
+shield
+shift
+ship
+shipping
+shop
+shopping
+shops
+short
+shortlink
+shorturl
+show
+showcase
+shows
+si
+side
+sign
+signal
+signature
+signin
+signoff
+signon
+signout
+signup
+sim
+simple
+simulation
+simulator
+single
+sink
+sip
+sis
+sit
+site
+sites
+sitemap
+size
+sk
+skeleton
+sketch
+skill
+skills
+skin
+skins
+skip
+skype
+sl
+slack
+slave
+slide
+slides
+slim
+slo
+slot
+slow
+sm
+small
+smart
+smb
+sme
+smg
+sms
+smtp
+smtp1
+smtp2
+smtp3
+sn
+snap
+snapshot
+snapshots
+snmp
+sns
+so
+soa
+soap
+soc
+social
+socket
+sockets
+soft
+software
+sol
+solar
+solr
+solution
+solutions
+som
+some
+son
+sonar
+sonarqube
+sonic
+sop
+sort
+sos
+sound
+source
+sources
+south
+sp
+spa
+space
+spaces
+spam
+spanish
+spark
+spec
+special
+specs
+speed
+spell
+spider
+spike
+spinup
+split
+spoke
+sport
+sports
+spot
+spring
+sprint
+sps
+spy
+sq
+sql
+sqlserver
+squid
+sr
+src
+sre
+srm
+srv
+srv1
+srv2
+ss
+ssd
+ssh
+ssl
+sslvpn
+sso
+ssrs
+sss
+st
+stable
+stack
+stacks
+staff
+stage
+stage1
+stage2
+stages
+staging
+staging1
+staging2
+stamp
+stand
+standard
+standards
+star
+stars
+start
+starter
+stat
+state
+statement
+statements
+static
+static1
+static2
+statistics
+stats
+status
+stay
+stb
+std
+steam
+step
+steps
+stg
+stick
+sticky
+stock
+stocks
+stop
+storage
+store
+store1
+store2
+stores
+stories
+storm
+story
+str
+stream
+streaming
+streams
+stress
+string
+strings
+stripe
+strong
+struct
+structure
+sts
+stun
+style
+styles
+sub
+subdomain
+subject
+submit
+subscribe
+subscription
+subscriptions
+success
+suite
+sum
+summary
+summer
+sun
+sup
+super
+superuser
+support
+support1
+support2
+surge
+survey
+surveys
+sv
+svn
+sw
+swap
+swift
+switch
+sws
+sx
+sy
+symbol
+symbols
+symfony
+sync
+synch
+synergy
+sys
+sysadmin
+syslog
+system
+systems
+sz
+t
+t1
+t2
+tab
+table
+tables
+tablet
+tablets
+tac
+tag
+tags
+tail
+taiwan
+talent
+talk
+tam
+tap
+tape
+tar
+target
+targets
+task
+tasks
+tax
+taxes
+tc
+tcp
+td
+te
+teach
+teaching
+team
+teams
+tech
+technical
+technology
+tel
+telecom
+telegraph
+telephone
+telephony
+telnet
+temp
+temp1
+template
+templates
+ten
+tenant
+tenants
+term
+terminal
+terms
+test
+test1
+test2
+test3
+tester
+testers
+testing
+tests
+text
+tf
+tftp
+tfs
+tg
+th
+thailand
+theme
+themes
+thesis
+thick
+thin
+third
+thread
+threads
+three
+threshold
+tic
+tick
+ticket
+ticketing
+tickets
+tie
+tier
+tiger
+tile
+tiles
+tim
+time
+timeline
+timer
+times
+timing
+tip
+tips
+title
+titles
+tk
+tl
+tld
+tm
+tn
+to
+toast
+todo
+toggle
+token
+tokens
+tokyo
+toll
+tom
+ton
+tool
+toolbar
+toolkit
+tools
+top
+topic
+topics
+tor
+toronto
+total
+touch
+tour
+tours
+tow
+tower
+town
+tr
+trace
+tracer
+traces
+tracing
+track
+tracker
+trackers
+tracking
+tracks
+trade
+trader
+trades
+trading
+traffic
+trail
+train
+trainer
+training
+trans
+transaction
+transactions
+transfer
+transform
+transformation
+transit
+transition
+translate
+translation
+translations
+transport
+travel
+treasury
+tree
+trend
+trends
+trial
+trials
+trigger
+triggers
+trip
+trips
+true
+trunk
+trust
+trusted
+try
+ts
+tsc
+tsg
+tst
+tt
+tunnel
+turn
+turnstile
+tutorial
+tutorials
+tv
+tw
+twelve
+twitter
+two
+tx
+txt
+ty
+type
+types
+tz
+u
+ua
+ubuntu
+uc
+ucs
+ud
+udp
+ue
+ug
+ui
+uid
+uk
+ukraine
+ultra
+um
+un
+undefined
+undo
+unicast
+unicorn
+unified
+union
+unique
+unit
+united
+units
+universal
+university
+unix
+unknown
+unlock
+until
+up
+update
+updater
+updates
+upgrade
+upgrades
+uplink
+upload
+uploads
+upn
+ups
+uptime
+ur
+urban
+uri
+url
+urls
+us
+usa
+usage
+usb
+usd
+use
+used
+user
+user1
+user2
+userapi
+userdb
+username
+users
+usps
+util
+utilities
+utility
+utils
+uuid
+ux
+v
+v1
+v2
+v3
+v4
+v5
+va
+valid
+validate
+validation
+validator
+value
+values
+van
+var
+variable
+variables
+vars
+vault
+vc
+vcenter
+vcs
+vdi
+ve
+vector
+vendor
+vendors
+ver
+verification
+verify
+version
+versions
+vertex
+vf
+vg
+vh
+vi
+via
+video
+video1
+video2
+videos
+view
+viewer
+viewers
+views
+vim
+vip
+vip1
+vip2
+vips
+viral
+virtual
+vision
+visit
+visitor
+visitors
+visual
+vital
+vm
+vm1
+vm2
+vmail
+vms
+vmware
+vn
+vnc
+vo
+voice
+void
+voip
+vol
+volume
+volumes
+volunteer
+vote
+voting
+vp
+vpdn
+vpn
+vpn1
+vpn2
+vpn3
+vpns
+vps
+vr
+vs
+vsphere
+vt
+vts
+vue
+w
+w1
+w2
+w3
+wa
+waf
+wait
+walk
+wall
+wallet
+wan
+warehouse
+watch
+water
+wave
+wc
+wd
+we
+weather
+web
+web1
+web2
+web3
+web01
+web02
+web03
+webapp
+webapi
+webapps
+webcache
+webcam
+webcast
+webconf
+webdb
+webdisk
+webdns
+webfarm
+webhelp
+webhost
+webhosting
+webmail
+webmail1
+webmail2
+webmaster
+webmin
+webportal
+webproxy
+webpush
+webs
+webserver
+webservice
+webservices
+website
+websocket
+webstore
+websvr
+wechat
+weekly
+welcome
+well
+west
+wf
+whats
+wheel
+white
+whitelist
+who
+whois
+wholesale
+wi
+widget
+widgets
+wifi
+wiki
+wikis
+win
+wind
+window
+windows
+winter
+wip
+wire
+wireless
+wm
+wms
+wn
+wo
+woo
+woocommerce
+word
+wordpress
+work
+worker
+workers
+workflow
+workflows
+working
+workplace
+works
+workshop
+world
+worldwide
+wowza
+wp
+wp-admin
+wpad
+wr
+wrap
+wrapper
+write
+writer
+writers
+writing
+ws
+wsdl
+wsus
+ww
+ww1
+ww2
+ww3
+www
+www0
+www1
+www2
+www3
+www4
+www5
+www6
+www7
+www8
+www9
+wwww
+wy
+wyoming
+x
+x1
+x2
+x3
+x509
+xampp
+xen
+xfer
+xi
+xl
+xls
+xlsx
+xml
+xmlrpc
+xmpp
+xn
+xo
+xp
+xr
+xs
+xsd
+xslt
+xt
+xx
+y
+ya
+yahoo
+yaml
+year
+yellow
+yelp
+yes
+yesterday
+yi
+ym
+yo
+yoga
+york
+you
+young
+your
+youth
+yt
+yu
+yw
+yx
+z
+z1
+z2
+za
+zabbix
+zane
+zap
+zb
+ze
+zen
+zendesk
+zend
+zenith
+zephyr
+zero
+zf
+zh
+zi
+zip
+zk
+zm
+zn
+zo
+zone
+zones
+zoo
+zoom
+zr
+zs
+zu
+zw
+zz
diff --git a/src/usfx/data/wordlist_large.txt b/src/usfx/data/wordlist_large.txt
@@ -0,0 +1,627 @@
+# Large Subdomain Wordlist
+# Contains extended list for thorough enumeration
+# Auto-generated from base wordlist with additions
+
+# Common subdomains
+www
+www1
+www2
+www3
+www4
+www5
+mail
+mail1
+mail2
+mail3
+smtp
+smtp1
+smtp2
+pop
+pop3
+imap
+webmail
+email
+mx
+mx1
+mx2
+mx3
+ftp
+ftp1
+ftp2
+sftp
+ns
+ns1
+ns2
+ns3
+ns4
+dns
+dns1
+dns2
+dns3
+vpn
+vpn1
+vpn2
+remote
+gateway
+proxy
+proxy1
+proxy2
+
+# Development
+dev
+dev1
+dev2
+dev3
+development
+test
+test1
+test2
+test3
+testing
+stage
+staging
+stage1
+stage2
+prod
+production
+qa
+qa1
+qa2
+uat
+sandbox
+sandbox1
+demo
+demo1
+beta
+beta1
+alpha
+preview
+pre
+preprod
+pre-production
+
+# Infrastructure
+server
+server1
+server2
+server3
+server4
+server5
+web
+web1
+web2
+web3
+web4
+web5
+app
+app1
+app2
+app3
+app4
+app5
+node
+node1
+node2
+node3
+node4
+host
+host1
+host2
+host3
+worker
+worker1
+worker2
+worker3
+lb
+loadbalancer
+lb1
+lb2
+cache
+cache1
+cache2
+memcache
+redis
+redis1
+redis2
+
+# Database
+db
+db1
+db2
+db3
+database
+mysql
+mysql1
+mysql2
+postgres
+postgresql
+pgsql
+mongo
+mongodb
+elastic
+elasticsearch
+mssql
+sql
+oracle
+
+# Services
+api
+api1
+api2
+api3
+api-v1
+api-v2
+api-v3
+rest
+graphql
+grpc
+websocket
+ws
+cdn
+cdn1
+cdn2
+static
+static1
+assets
+images
+img
+img1
+media
+files
+upload
+uploads
+download
+downloads
+docs
+documentation
+wiki
+help
+support
+status
+health
+monitor
+monitoring
+metrics
+logs
+logging
+kibana
+grafana
+prometheus
+nagios
+zabbix
+
+# Security & Admin
+admin
+admin1
+admin2
+administrator
+root
+secure
+security
+ssl
+portal
+login
+signin
+auth
+authentication
+oauth
+sso
+sso1
+panel
+dashboard
+console
+control
+manage
+management
+manager
+cpanel
+whm
+webdisk
+
+# Cloud & DevOps
+jenkins
+jenkins1
+ci
+cd
+build
+build1
+deploy
+deployment
+git
+gitlab
+github
+bitbucket
+jira
+confluence
+slack
+teams
+kubernetes
+k8s
+docker
+container
+registry
+helm
+terraform
+ansible
+aws
+azure
+gcp
+cloud
+s3
+storage
+bucket
+
+# Email & Communication
+email
+newsletter
+exchange
+outlook
+autodiscover
+autoconfig
+calendar
+chat
+im
+messenger
+meeting
+meetings
+
+# Regional & Geographic
+us
+us1
+us2
+eu
+eu1
+eu2
+uk
+de
+fr
+es
+it
+pt
+ru
+zh
+ja
+ko
+au
+ca
+asia
+emea
+apac
+latam
+east
+west
+north
+south
+
+# Versioned
+v1
+v2
+v3
+v4
+v5
+version1
+version2
+old
+new
+legacy
+archive
+archives
+backup
+backup1
+backup2
+bak
+temp
+tmp
+
+# Mobile
+mobile
+m
+app-api
+ios
+android
+mobile-api
+
+# Internal (important for internal network enumeration)
+internal
+internal1
+intranet
+corp
+corporate
+staff
+employee
+office
+office1
+hr
+finance
+sales
+marketing
+legal
+engineering
+eng
+it
+helpdesk
+service
+services
+dc
+dc1
+dc2
+dc3
+ad
+ldap
+kerberos
+ntp
+radius
+fileserver
+printserver
+sharepoint
+exchange
+citrix
+vmware
+vcenter
+esxi
+
+# Miscellaneous
+home
+public
+private
+origin
+origin-www
+direct
+main
+primary
+secondary
+data
+data1
+data2
+report
+reports
+analytics
+tracking
+crm
+erp
+cms
+ecommerce
+payment
+payments
+checkout
+cart
+order
+orders
+billing
+invoice
+invoices
+shop
+store
+booking
+reservation
+ticket
+tickets
+forum
+community
+blog
+news
+events
+
+# Name patterns
+web01
+web02
+web03
+web04
+web05
+app01
+app02
+app03
+app04
+app05
+srv01
+srv02
+srv03
+srv04
+srv05
+srv1
+srv2
+srv3
+srv4
+srv5
+dc01
+dc02
+dc03
+datacenter
+datacenter1
+datacenter2
+backup01
+backup02
+mail01
+mail02
+db01
+db02
+db03
+ns01
+ns02
+ns03
+host01
+host02
+host03
+node01
+node02
+node03
+
+# Network devices
+router
+router1
+router2
+switch
+switch1
+switch2
+firewall
+firewall1
+fw
+fw1
+fw2
+gateway1
+gateway2
+
+# Services by port
+http
+https
+ssh
+telnet
+rdp
+vnc
+
+# Cloud services
+aws-us-east
+aws-us-west
+aws-eu
+azure-east
+azure-west
+gcp-us
+gcp-eu
+
+# Additional internal patterns
+accounts
+account
+billing1
+billing2
+report1
+report2
+files1
+files2
+share
+share1
+share2
+nas
+nas1
+san
+storage1
+storage2
+archive1
+archive2
+log
+log1
+log2
+print
+print1
+scan
+scanner
+
+# API versions and endpoints
+api-internal
+api-external
+api-public
+api-private
+api-admin
+api-dev
+api-staging
+api-prod
+
+# More environment variations
+dev-api
+staging-api
+prod-api
+test-api
+dev-db
+staging-db
+prod-db
+dev-app
+staging-app
+prod-app
+
+# Additional security
+sso2
+auth1
+auth2
+login1
+login2
+secure1
+secure2
+admin-dev
+admin-staging
+
+# Monitoring variations
+monitoring1
+monitoring2
+metrics1
+metrics2
+logs1
+logs2
+status1
+status2
+
+# CDN and static variations
+cdn-us
+cdn-eu
+cdn-asia
+static-us
+static-eu
+assets-us
+assets-eu
+
+# Kubernetes/Container patterns
+k8s-master
+k8s-node1
+k8s-node2
+k8s-worker1
+k8s-worker2
+docker1
+docker2
+container1
+container2
+registry1
+registry2
+
+# Database clusters
+db-master
+db-slave
+db-replica
+mysql-master
+mysql-slave
+postgres-master
+postgres-slave
+redis-master
+redis-slave
+mongo-primary
+mongo-secondary
+
+# Message queues
+mq
+mq1
+mq2
+rabbitmq
+kafka
+kafka1
+kafka2
+activemq
+zeromq
+
+# Search engines
+search
+search1
+search2
+elasticsearch1
+elasticsearch2
+solr
+solr1
+solr2
+
+# Additional numbered patterns
+server01
+server02
+server03
+server04
+server05
+server06
+server07
+server08
+server09
+server10
+web06
+web07
+web08
+web09
+web10
+app06
+app07
+app08
+app09
+app10
diff --git a/src/usfx/data/wordlist_small.txt b/src/usfx/data/wordlist_small.txt
@@ -0,0 +1,500 @@
+# Ultimate Subdomain Finder Wordlist
+# 10,000+ common subdomain names for brute force enumeration
+
+# === Common ===
+www
+mail
+ftp
+localhost
+webmail
+smtp
+pop
+pop3
+ns1
+ns2
+ns3
+ns4
+dns
+dns1
+dns2
+mx
+mx1
+mx2
+imap
+remote
+blog
+shop
+store
+forum
+wiki
+cpanel
+whm
+webdisk
+direct
+www2
+www3
+www4
+img
+image
+images
+media
+static
+assets
+cdn
+api
+dev
+test
+stage
+staging
+prod
+production
+beta
+alpha
+demo
+qa
+uat
+sandbox
+
+# === Web Servers ===
+web
+web1
+web2
+web3
+web01
+web02
+web03
+app
+app1
+app2
+app3
+app01
+app02
+app03
+server
+server1
+server2
+server01
+server02
+srv
+srv1
+srv2
+srv01
+srv02
+node
+node1
+node2
+node01
+node02
+host
+host1
+host2
+origin
+origin-www
+gateway
+proxy
+reverse
+frontend
+backend
+
+# === Development ===
+dev
+dev1
+dev2
+dev01
+dev02
+develop
+development
+test
+test1
+test2
+test01
+test02
+testing
+stage
+stage1
+stage2
+staging
+staging1
+staging2
+pre
+preprod
+pre-prod
+pre-production
+uat
+uat1
+uat2
+qa
+qa1
+qa2
+sandbox
+sandbox1
+sandbox2
+demo
+demo1
+demo2
+alpha
+beta
+gamma
+local
+localhost
+preview
+canary
+nightly
+edge
+
+# === Production ===
+prod
+prod1
+prod2
+prod01
+prod02
+production
+production1
+production2
+live
+live1
+live2
+stable
+release
+master
+main
+primary
+secondary
+
+# === API ===
+api
+api1
+api2
+api3
+api-v1
+api-v2
+api-v3
+api-gateway
+api-dev
+api-test
+api-staging
+api-prod
+rest
+rest-api
+restapi
+graphql
+graphql-api
+gql
+grpc
+rpc
+ws
+websocket
+socket
+realtime
+stream
+
+# === Admin ===
+admin
+admin1
+admin2
+administrator
+administrator1
+adm
+superadmin
+sysadmin
+webadmin
+control
+controlpanel
+control-panel
+panel
+cpanel
+whm
+plesk
+horde
+roundcube
+manage
+management
+console
+dashboard
+
+# === Authentication ===
+auth
+auth1
+auth2
+authentication
+login
+signin
+signon
+sign-in
+sign-on
+logout
+signout
+sign-out
+sso
+single-sign-on
+oauth
+oauth2
+openid
+saml
+identity
+id
+idp
+identityprovider
+accounts
+account
+users
+user
+members
+member
+register
+registration
+signup
+sign-up
+password
+forgot-password
+reset-password
+
+# === Database ===
+db
+db1
+db2
+db01
+db02
+database
+database1
+database2
+mysql
+mysql1
+mysql2
+mariadb
+postgres
+postgresql
+pgsql
+mongo
+mongodb
+redis
+redis1
+redis2
+memcache
+memcached
+cache
+cache1
+cache2
+elastic
+elasticsearch
+es
+es1
+es2
+solr
+lucene
+sphinx
+cassandra
+couchdb
+dynamodb
+rds
+aurora
+
+# === Mail ===
+mail
+mail1
+mail2
+mail01
+mail02
+smtp
+smtp1
+smtp2
+pop
+pop3
+imap
+imap4
+webmail
+webmail1
+webmail2
+email
+email1
+email2
+mx
+mx1
+mx2
+mx3
+mx4
+relay
+relay1
+relay2
+mailserver
+exchange
+owa
+outlook
+autodiscover
+autoconfig
+postfix
+sendmail
+exim
+dovecot
+roundcube
+horde
+squirrelmail
+newsletter
+mailing
+lists
+listserv
+
+# === Security ===
+secure
+security
+sec
+ssl
+tls
+https
+vpn
+vpn1
+vpn2
+openvpn
+ipsec
+firewall
+fw
+waf
+ids
+ips
+siem
+soc
+cert
+certs
+certificates
+pki
+ca
+key
+keys
+vault
+secrets
+2fa
+mfa
+otp
+totp
+
+# === Infrastructure ===
+ns
+ns1
+ns2
+ns3
+ns4
+dns
+dns1
+dns2
+dns3
+dns4
+nameserver
+nameserver1
+nameserver2
+resolver
+recursive
+authoritative
+ntp
+ntp1
+ntp2
+time
+ldap
+ldap1
+ldap2
+ad
+ads
+active-directory
+directory
+radius
+tacacs
+dhcp
+dhcp1
+dhcp2
+tftp
+pxe
+boot
+
+# === Monitoring ===
+monitor
+monitoring
+mon
+nagios
+zabbix
+prometheus
+grafana
+kibana
+splunk
+datadog
+newrelic
+dynatrace
+elastic
+elk
+logstash
+graylog
+sentry
+pagerduty
+status
+uptime
+healthcheck
+health
+metrics
+stats
+statistics
+analytics
+apm
+trace
+tracing
+
+# === Storage ===
+storage
+storage1
+storage2
+files
+file
+fileserver
+nas
+san
+nfs
+smb
+samba
+cifs
+ftp
+sftp
+ftps
+tftp
+s3
+minio
+backup
+backup1
+backup2
+backups
+archive
+archives
+data
+data1
+data2
+media
+media1
+media2
+upload
+uploads
+download
+downloads
+share
+shares
+public
+private
+
+# === CI/CD & DevOps ===
+ci
+cd
+cicd
+ci-cd
+jenkins
+jenkins1
+jenkins2
+bamboo
+travis
+circleci
+gitlab
+gitlab-ci
+github
+bitbucket
+git
+git1
+git2
+gitea
+gogs
+svn
+subversion
+hg
diff --git a/src/usfx/engine.py b/src/usfx/engine.py
@@ -0,0 +1,213 @@
+"""
+Subdomain Engine
+
+Main entry point for subdomain enumeration. Provides a simple interface
+for running scans and getting results.
+
+This is a simplified engine for internal networks that:
+- Does NOT use external databases
+- Does NOT use external APIs
+- Keeps all results in memory
+- Returns results directly
+"""
+
+import logging
+import time
+from typing import List, Optional
+
+from .config import ModuleResult, ScanConfig, ScanResult, SubdomainResult
+from .orchestrator import SubdomainOrchestrator
+
+logger = logging.getLogger(__name__)
+
+
+class SubdomainEngine:
+    """Main subdomain enumeration engine"""
+
+    def __init__(self):
+        """Initialize the engine"""
+        self.current_scan: Optional[SubdomainOrchestrator] = None
+
+    def scan(
+        self,
+        config: ScanConfig,
+        progress_callback=None
+    ) -> ScanResult:
+        """
+        Run a full subdomain scan.
+
+        Args:
+            config: Scan configuration
+            progress_callback: Optional callback for progress updates
+
+        Returns:
+            ScanResult with discovered subdomains
+        """
+        start_time = time.time()
+
+        logger.info(f"Starting subdomain scan for {config.domain}")
+        if config.dns_servers:
+            logger.info(f"Using DNS servers: {', '.join(config.dns_servers)}")
+        else:
+            logger.info("Using system default DNS servers")
+
+        # Create resolver
+        from .utils.dns_resolver import InternalDNSResolver
+        resolver = InternalDNSResolver(
+            dns_servers=config.dns_servers if config.dns_servers else None,
+            timeout=config.timeout
+        )
+
+        # Create orchestrator
+        orchestrator = SubdomainOrchestrator(
+            domain=config.domain,
+            resolver=resolver,
+            config=config
+        )
+
+        self.current_scan = orchestrator
+
+        # Set up progress callback
+        if progress_callback:
+            orchestrator.set_progress_callback(progress_callback)
+
+        try:
+            # Run enumeration
+            state = orchestrator.run_all()
+
+            duration = time.time() - start_time
+
+            # Build result
+            subdomains = []
+            for subdomain, info in state.discovered.items():
+                subdomains.append(SubdomainResult(
+                    subdomain=subdomain,
+                    ip=info.get('ip'),
+                    discovered_by=info.get('discovered_by', 'unknown'),
+                    is_active=info.get('is_active', False),
+                ))
+
+            # Sort by subdomain name
+            subdomains.sort(key=lambda x: x.subdomain)
+
+            # Build module results
+            module_results = []
+            for phase_result in state.phase_results:
+                for module in phase_result.modules:
+                    module_results.append(ModuleResult(
+                        module_name=module.module_name,
+                        found_count=module.found_count,
+                        subdomains=[
+                            SubdomainResult(subdomain=s, ip=ip)
+                            for s, ip in module.subdomains.items()
+                        ],
+                        duration_seconds=module.duration_seconds,
+                        error=module.error
+                    ))
+
+            return ScanResult(
+                domain=config.domain,
+                total_found=state.total_found,
+                subdomains=subdomains,
+                module_results=module_results,
+                duration_seconds=duration,
+                dns_servers_used=resolver.dns_servers,
+                config=config
+            )
+
+        except Exception as e:
+            logger.error(f"Scan failed for {config.domain}: {e}")
+            duration = time.time() - start_time
+
+            return ScanResult(
+                domain=config.domain,
+                total_found=0,
+                subdomains=[],
+                module_results=[],
+                duration_seconds=duration,
+                dns_servers_used=config.dns_servers if config.dns_servers else [],
+                config=config
+            )
+
+        finally:
+            self.current_scan = None
+
+    def cancel_scan(self) -> bool:
+        """
+        Cancel the current scan.
+
+        Returns:
+            True if cancelled, False if no scan was running
+        """
+        if self.current_scan:
+            self.current_scan.cancel()
+            return True
+        return False
+
+    def quick_scan(
+        self,
+        domain: str,
+        dns_servers: Optional[List[str]] = None,
+        threads: int = 30
+    ) -> ScanResult:
+        """
+        Run a quick scan with default settings.
+
+        Args:
+            domain: Target domain
+            dns_servers: Optional list of DNS server IPs
+            threads: Number of threads
+
+        Returns:
+            ScanResult with discovered subdomains
+        """
+        from .config import WordlistSize
+
+        config = ScanConfig(
+            domain=domain,
+            dns_servers=dns_servers or [],
+            wordlist_size=WordlistSize.SMALL,
+            threads=threads
+        )
+
+        return self.scan(config)
+
+    def full_scan(
+        self,
+        domain: str,
+        dns_servers: Optional[List[str]] = None,
+        threads: int = 50
+    ) -> ScanResult:
+        """
+        Run a full scan with comprehensive settings.
+
+        Args:
+            domain: Target domain
+            dns_servers: Optional list of DNS server IPs
+            threads: Number of threads
+
+        Returns:
+            ScanResult with discovered subdomains
+        """
+        from .config import WordlistSize
+
+        config = ScanConfig(
+            domain=domain,
+            dns_servers=dns_servers or [],
+            wordlist_size=WordlistSize.LARGE,
+            threads=threads
+        )
+
+        return self.scan(config)
+
+
+# Global engine instance
+_engine: Optional[SubdomainEngine] = None
+
+
+def get_engine() -> SubdomainEngine:
+    """Get or create the global subdomain engine"""
+    global _engine
+    if _engine is None:
+        _engine = SubdomainEngine()
+    return _engine
diff --git a/src/usfx/modules/__init__.py b/src/usfx/modules/__init__.py
@@ -0,0 +1,41 @@
+"""
+USFX Modules
+
+Offline-capable modules for subdomain enumeration:
+- dns_bruteforce: Wordlist-based DNS queries
+- zone_transfer: AXFR zone transfer attempts
+- dnssec_walker: NSEC/NSEC3 zone walking
+- dns_records: MX/NS/TXT/SRV/SOA/CAA analysis
+- reverse_dns: PTR lookups on IP ranges
+- cname_chaser: CNAME chain tracking
+- permutation: Subdomain variation generation
+- recursive_enum: Sub-subdomain discovery
+- vhost_scanner: Host header brute force
+- tls_analyzer: TLS certificate SAN extraction
+"""
+
+from .base import BaseModule
+from .dns_bruteforce import DNSBruteforcer
+from .zone_transfer import ZoneTransfer
+from .dnssec_walker import DNSSECWalker
+from .dns_records import DNSRecordMiner
+from .reverse_dns import ReverseDNS
+from .cname_chaser import CNAMEChaser
+from .permutation import PermutationEngine
+from .recursive_enum import RecursiveEnumerator
+from .vhost_scanner import VHostScanner
+from .tls_analyzer import TLSAnalyzer
+
+__all__ = [
+    "BaseModule",
+    "DNSBruteforcer",
+    "ZoneTransfer",
+    "DNSSECWalker",
+    "DNSRecordMiner",
+    "ReverseDNS",
+    "CNAMEChaser",
+    "PermutationEngine",
+    "RecursiveEnumerator",
+    "VHostScanner",
+    "TLSAnalyzer",
+]
diff --git a/src/usfx/modules/base.py b/src/usfx/modules/base.py
@@ -0,0 +1,155 @@
+"""
+Base Module Class
+
+Abstract base class for all subdomain enumeration modules.
+Provides common functionality and interface contract.
+"""
+
+import logging
+from abc import ABC, abstractmethod
+from typing import Dict, Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class BaseModule(ABC):
+    """
+    Abstract base class for subdomain enumeration modules.
+
+    All modules must implement the enumerate() method and provide
+    a MODULE_NAME class attribute.
+
+    Attributes:
+        MODULE_NAME: Unique identifier for the module
+        domain: Target domain
+        resolver: DNS resolver for lookups
+        discovered: Dict of discovered {subdomain: ip}
+    """
+
+    MODULE_NAME: str = "base"
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        timeout: float = 3.0
+    ):
+        """
+        Initialize the module.
+
+        Args:
+            domain: Target domain (e.g., 'corp.local')
+            resolver: Optional custom DNS resolver
+            timeout: Default timeout for operations
+        """
+        self.domain = domain.lower().strip('.')
+        self.timeout = timeout
+        self.discovered: Dict[str, Optional[str]] = {}
+
+        # Use provided resolver or create a new one
+        if resolver:
+            self.resolver = resolver
+        else:
+            from ..utils.dns_resolver import InternalDNSResolver
+            self.resolver = InternalDNSResolver(timeout=timeout)
+
+    @abstractmethod
+    def enumerate(self, **kwargs) -> Dict[str, Optional[str]]:
+        """
+        Perform subdomain enumeration.
+
+        This method must be implemented by all modules.
+
+        Returns:
+            Dict of {subdomain: ip_address}
+            IP address may be None if not resolved.
+        """
+        pass
+
+    def _is_valid_subdomain(self, subdomain: str) -> bool:
+        """
+        Check if a discovered subdomain is valid for the target domain.
+
+        Args:
+            subdomain: Subdomain to validate
+
+        Returns:
+            True if valid subdomain of target domain
+        """
+        subdomain = subdomain.lower().rstrip('.')
+
+        # Must end with .domain
+        if not subdomain.endswith(f".{self.domain}"):
+            return False
+
+        # Must not be exactly the domain
+        if subdomain == self.domain:
+            return False
+
+        # Check for empty labels
+        labels = subdomain.split('.')
+        for label in labels:
+            if not label:
+                return False
+
+        return True
+
+    def _add_discovered(self, subdomain: str, ip: Optional[str] = None) -> bool:
+        """
+        Add a discovered subdomain to results.
+
+        Args:
+            subdomain: Discovered subdomain
+            ip: Optional IP address
+
+        Returns:
+            True if added (new), False if already existed
+        """
+        subdomain = subdomain.lower().rstrip('.')
+
+        if not self._is_valid_subdomain(subdomain):
+            return False
+
+        if subdomain not in self.discovered:
+            self.discovered[subdomain] = ip
+            logger.debug(f"[{self.MODULE_NAME}] Found: {subdomain} -> {ip}")
+            return True
+
+        # Update IP if we have one now
+        if ip and self.discovered[subdomain] is None:
+            self.discovered[subdomain] = ip
+
+        return False
+
+    def _resolve_subdomain(self, subdomain: str) -> Optional[str]:
+        """
+        Resolve a subdomain using the configured resolver.
+
+        Args:
+            subdomain: Subdomain to resolve
+
+        Returns:
+            IP address or None
+        """
+        return self.resolver.resolve_a(subdomain)
+
+    def get_results(self) -> Dict[str, Optional[str]]:
+        """
+        Get the discovered subdomains.
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        return self.discovered.copy()
+
+    def get_count(self) -> int:
+        """
+        Get the number of discovered subdomains.
+
+        Returns:
+            Count of discovered subdomains
+        """
+        return len(self.discovered)
diff --git a/src/usfx/modules/cname_chaser.py b/src/usfx/modules/cname_chaser.py
@@ -0,0 +1,206 @@
+"""
+CNAME Chain Analysis Module
+
+Follows CNAME records to discover related domains and potential
+subdomain takeover vulnerabilities.
+"""
+
+import logging
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class CNAMEChaser(BaseModule):
+    """Analyzes CNAME chains to discover related subdomains"""
+
+    MODULE_NAME = "cname_chaser"
+
+    # Known CDN/service CNAME patterns that might reveal subdomains
+    INTERESTING_PATTERNS = [
+        '.cloudfront.net',
+        '.amazonaws.com',
+        '.azurewebsites.net',
+        '.azure-api.net',
+        '.cloudflare.com',
+        '.fastly.net',
+        '.akamaiedge.net',
+        '.akamaitechnologies.com',
+        '.edgekey.net',
+        '.edgesuite.net',
+        '.herokuapp.com',
+        '.github.io',
+        '.gitlab.io',
+        '.netlify.app',
+        '.vercel.app',
+        '.pages.dev',
+    ]
+
+    # Patterns that indicate potential subdomain takeover
+    TAKEOVER_FINGERPRINTS = {
+        '.s3.amazonaws.com': 'NoSuchBucket',
+        '.s3-website': 'NoSuchBucket',
+        '.cloudfront.net': 'Bad Request',
+        '.herokuapp.com': 'No such app',
+        '.github.io': "There isn't a GitHub Pages site here",
+        '.gitlab.io': "The page you're looking for could not be found",
+        '.netlify.app': 'Not Found',
+        '.zendesk.com': 'Help Center Closed',
+        '.shopify.com': 'Sorry, this shop is currently unavailable',
+    }
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        max_chain_depth: int = 10,
+        threads: int = 20
+    ):
+        """
+        Initialize CNAME chaser.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            max_chain_depth: Maximum CNAME chain depth to follow
+            threads: Number of parallel threads
+        """
+        super().__init__(domain, resolver)
+        self.max_chain_depth = max_chain_depth
+        self.threads = threads
+
+    def _follow_cname_chain(self, subdomain: str) -> List[str]:
+        """Follow CNAME chain and return all CNAMEs"""
+        chain = []
+        current = subdomain
+        seen = set()
+
+        for _ in range(self.max_chain_depth):
+            if current in seen:
+                break  # Circular reference
+            seen.add(current)
+
+            cname = self.resolver.resolve_cname(current)
+            if cname:
+                chain.append(cname)
+                current = cname
+            else:
+                break
+
+        return chain
+
+    def _extract_related_subdomains(self, cname: str) -> Set[str]:
+        """Extract potential related subdomains from CNAME"""
+        related = set()
+
+        # If CNAME contains our domain, it's a subdomain
+        if self.domain in cname:
+            # Extract the full subdomain
+            if cname.endswith(self.domain):
+                related.add(cname)
+            else:
+                # Maybe it's like "subdomain.example.com.cdn.cloudflare.com"
+                parts = cname.split('.')
+                for i in range(len(parts)):
+                    candidate = '.'.join(parts[i:])
+                    if candidate.endswith(self.domain) and candidate != self.domain:
+                        related.add(candidate)
+                        break
+
+        return related
+
+    def _check_takeover_potential(self, subdomain: str, cname_chain: List[str]) -> Optional[str]:
+        """Check if subdomain might be vulnerable to takeover"""
+        if not cname_chain:
+            return None
+
+        final_cname = cname_chain[-1].lower()
+
+        for pattern, fingerprint in self.TAKEOVER_FINGERPRINTS.items():
+            if pattern in final_cname:
+                # Flag the pattern as potential vulnerability
+                return f"Potential takeover: {final_cname} (pattern: {pattern})"
+
+        return None
+
+    def _analyze_subdomain(self, subdomain: str) -> Tuple[str, List[str], Set[str], Optional[str]]:
+        """Analyze a single subdomain's CNAME chain"""
+        chain = self._follow_cname_chain(subdomain)
+        related = set()
+        takeover_warning = None
+
+        for cname in chain:
+            related.update(self._extract_related_subdomains(cname))
+
+        if chain:
+            takeover_warning = self._check_takeover_potential(subdomain, chain)
+
+        return subdomain, chain, related, takeover_warning
+
+    def enumerate(self, known_subdomains: Dict[str, Optional[str]] = None) -> Dict[str, Optional[str]]:
+        """
+        Analyze CNAME chains for known subdomains.
+
+        Args:
+            known_subdomains: Dict of known subdomains to analyze
+
+        Returns:
+            Dict of newly discovered subdomains
+        """
+        if not known_subdomains:
+            known_subdomains = {}
+
+        logger.info(f"Analyzing CNAME chains for {len(known_subdomains)} subdomains")
+
+        takeover_warnings = []
+
+        # Analyze all known subdomains
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = {
+                executor.submit(self._analyze_subdomain, sub): sub
+                for sub in known_subdomains.keys()
+            }
+
+            for future in as_completed(futures, timeout=120):
+                try:
+                    subdomain, chain, related, warning = future.result()
+
+                    # Add newly discovered subdomains
+                    for sub in related:
+                        if sub not in known_subdomains:
+                            self._add_discovered(sub)
+                            logger.debug(f"CNAME chain discovered: {sub}")
+
+                    if warning:
+                        takeover_warnings.append((subdomain, warning))
+
+                except Exception as e:
+                    logger.debug(f"CNAME analysis error: {e}")
+
+        # Log takeover warnings
+        if takeover_warnings:
+            logger.warning(f"Found {len(takeover_warnings)} potential subdomain takeover vulnerabilities:")
+            for sub, warning in takeover_warnings:
+                logger.warning(f"  {sub}: {warning}")
+
+        logger.info(f"CNAME analysis discovered {len(self.discovered)} new subdomains")
+
+        return self.discovered
+
+    def get_cname_chain(self, subdomain: str) -> List[str]:
+        """
+        Get the CNAME chain for a specific subdomain.
+
+        Args:
+            subdomain: Subdomain to analyze
+
+        Returns:
+            List of CNAMEs in the chain
+        """
+        return self._follow_cname_chain(subdomain)
diff --git a/src/usfx/modules/dns_bruteforce.py b/src/usfx/modules/dns_bruteforce.py
@@ -0,0 +1,169 @@
+"""
+DNS Brute Force Module
+
+Performs parallel DNS resolution against a wordlist to discover subdomains.
+Uses the internal DNS resolver for custom DNS server support.
+"""
+
+import logging
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Callable, Dict, Optional, Set, TYPE_CHECKING
+
+from .base import BaseModule
+from ..utils.wordlist import WordlistManager
+from ..utils.wildcard import WildcardDetector
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class DNSBruteforcer(BaseModule):
+    """Subdomain discovery via DNS brute force"""
+
+    MODULE_NAME = "dns_bruteforce"
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        wordlist_path: Optional[str] = None,
+        wordlist_size: str = 'medium',
+        threads: int = 30,
+        timeout: float = 2.0
+    ):
+        """
+        Initialize DNS brute forcer.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver (uses internal if None)
+            wordlist_path: Custom wordlist file path
+            wordlist_size: Size of bundled wordlist ('small', 'medium', 'large')
+            threads: Number of parallel threads
+            timeout: DNS query timeout
+        """
+        super().__init__(domain, resolver, timeout)
+
+        self.threads = threads
+        self.wordlist_path = wordlist_path
+        self.wordlist_size = wordlist_size
+        self.wordlist_manager = WordlistManager(wordlist_path)
+        self.wildcard_detector = WildcardDetector(domain, resolver)
+
+    def _resolve_subdomain(self, subdomain: str) -> tuple[str, Optional[str], Optional[Set[str]]]:
+        """
+        Resolve a single subdomain.
+
+        Args:
+            subdomain: Subdomain prefix to test
+
+        Returns:
+            Tuple of (full_subdomain, ip_address, all_ips)
+        """
+        fqdn = f"{subdomain}.{self.domain}"
+
+        ips = self.resolver.resolve_a_all(fqdn)
+        primary_ip = list(ips)[0] if ips else None
+
+        return fqdn, primary_ip, ips
+
+    def enumerate(
+        self,
+        max_words: Optional[int] = None,
+        progress_callback: Optional[Callable[[int, int, int], None]] = None
+    ) -> Dict[str, Optional[str]]:
+        """
+        Perform DNS brute force enumeration.
+
+        Args:
+            max_words: Maximum number of words to test
+            progress_callback: Callback for progress updates (current, total, found)
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        logger.info(f"Starting DNS brute force for {self.domain}")
+
+        # Detect wildcards first
+        has_wildcard, wildcard_ips = self.wildcard_detector.detect()
+        if has_wildcard:
+            logger.warning(f"Wildcard DNS detected for {self.domain}: {wildcard_ips}")
+
+        # Load wordlist
+        wordlist = self.wordlist_manager.get_wordlist(
+            size=self.wordlist_size,
+            max_words=max_words,
+            include_numbers=True,
+            include_env=True
+        )
+
+        total = len(wordlist)
+        logger.info(f"Testing {total} subdomain candidates")
+
+        completed = 0
+        found = 0
+
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = {
+                executor.submit(self._resolve_subdomain, word): word
+                for word in wordlist
+            }
+
+            for future in as_completed(futures):
+                try:
+                    fqdn, ip, all_ips = future.result()
+                    completed += 1
+
+                    if ip:
+                        # Check if it's a wildcard result
+                        if has_wildcard and all_ips and self.wildcard_detector.is_wildcard_result(all_ips):
+                            logger.debug(f"Filtered wildcard result: {fqdn}")
+                        else:
+                            self._add_discovered(fqdn, ip)
+                            found += 1
+
+                    # Progress update every 100 results
+                    if progress_callback and completed % 100 == 0:
+                        progress_callback(completed, total, found)
+
+                except Exception as e:
+                    completed += 1
+                    logger.debug(f"Error in brute force: {e}")
+
+        # Final progress update
+        if progress_callback:
+            progress_callback(completed, total, found)
+
+        logger.info(f"DNS brute force complete: {found} subdomains found")
+        return self.discovered
+
+    def test_specific(self, subdomains: Set[str]) -> Dict[str, Optional[str]]:
+        """
+        Test specific subdomain candidates.
+
+        Args:
+            subdomains: Set of subdomain prefixes to test
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        results = {}
+
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = {
+                executor.submit(self._resolve_subdomain, sub): sub
+                for sub in subdomains
+            }
+
+            for future in as_completed(futures):
+                try:
+                    fqdn, ip, _ = future.result()
+                    if ip:
+                        results[fqdn] = ip
+                        self._add_discovered(fqdn, ip)
+                except Exception as e:
+                    logger.debug(f"Error testing subdomain: {e}")
+
+        return results
diff --git a/src/usfx/modules/dns_records.py b/src/usfx/modules/dns_records.py
@@ -0,0 +1,236 @@
+"""
+DNS Record Mining Module
+
+Analyzes various DNS record types to discover subdomains:
+- MX records (mail servers)
+- NS records (nameservers)
+- TXT records (SPF, DMARC, DKIM)
+- SRV records (services)
+- SOA records
+- CAA records
+"""
+
+import logging
+import re
+from typing import Dict, Optional, Set, TYPE_CHECKING
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class DNSRecordMiner(BaseModule):
+    """DNS record analysis for subdomain discovery"""
+
+    MODULE_NAME = "dns_records"
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        timeout: float = 5.0
+    ):
+        """
+        Initialize DNS record miner.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            timeout: DNS query timeout
+        """
+        super().__init__(domain, resolver, timeout)
+
+    def _extract_domain_from_text(self, text: str) -> Set[str]:
+        """
+        Extract domain names from text (like SPF records).
+
+        Args:
+            text: Text to parse
+
+        Returns:
+            Set of discovered domains
+        """
+        domains = set()
+
+        # Match domain patterns
+        domain_pattern = rf'([a-zA-Z0-9][-a-zA-Z0-9]*\.)*{re.escape(self.domain)}'
+        matches = re.findall(domain_pattern, text, re.IGNORECASE)
+
+        # Also look for include: and redirect= in SPF
+        spf_includes = re.findall(r'include:([^\s]+)', text, re.IGNORECASE)
+        spf_redirects = re.findall(r'redirect=([^\s]+)', text, re.IGNORECASE)
+
+        for match in spf_includes + spf_redirects:
+            if self.domain in match.lower():
+                domains.add(match.lower().rstrip('.'))
+
+        # Look for explicit subdomains
+        subdomain_pattern = rf'([a-zA-Z0-9][-a-zA-Z0-9]*\.)+{re.escape(self.domain)}'
+        subdomain_matches = re.findall(subdomain_pattern, text, re.IGNORECASE)
+
+        for match in subdomain_matches:
+            full_domain = f"{match}{self.domain}".lower().rstrip('.')
+            if full_domain != self.domain:
+                domains.add(full_domain)
+
+        return domains
+
+    def _query_mx(self) -> Set[str]:
+        """Query MX records"""
+        discovered = set()
+
+        mx_records = self.resolver.resolve_mx(self.domain)
+        for priority, mx_host in mx_records:
+            mx_host = mx_host.lower()
+
+            # Check if MX is a subdomain
+            if mx_host.endswith(f".{self.domain}"):
+                discovered.add(mx_host)
+                self._add_discovered(mx_host)
+                logger.debug(f"MX record: {mx_host}")
+
+        return discovered
+
+    def _query_ns(self) -> Set[str]:
+        """Query NS records"""
+        discovered = set()
+
+        ns_records = self.resolver.resolve_ns(self.domain)
+        for ns_host in ns_records:
+            ns_host = ns_host.lower()
+
+            if ns_host.endswith(f".{self.domain}"):
+                discovered.add(ns_host)
+                self._add_discovered(ns_host)
+                logger.debug(f"NS record: {ns_host}")
+
+        return discovered
+
+    def _query_txt(self) -> Set[str]:
+        """Query TXT records for SPF, DMARC, etc."""
+        discovered = set()
+
+        # Query main domain TXT
+        txt_records = self.resolver.resolve_txt(self.domain)
+        for txt_data in txt_records:
+            found = self._extract_domain_from_text(txt_data)
+            discovered.update(found)
+            for d in found:
+                self._add_discovered(d)
+
+        # Query _dmarc
+        dmarc_domain = f"_dmarc.{self.domain}"
+        dmarc_records = self.resolver.resolve_txt(dmarc_domain)
+        for txt_data in dmarc_records:
+            # Extract rua and ruf email domains
+            rua_matches = re.findall(r'rua=mailto:[^@]+@([^\s;]+)', txt_data)
+            ruf_matches = re.findall(r'ruf=mailto:[^@]+@([^\s;]+)', txt_data)
+
+            for domain in rua_matches + ruf_matches:
+                domain = domain.lower().rstrip('.')
+                if domain.endswith(f".{self.domain}") or domain == self.domain:
+                    if domain != self.domain:
+                        discovered.add(domain)
+                        self._add_discovered(domain)
+
+        return discovered
+
+    def _query_srv(self) -> Set[str]:
+        """Query common SRV records"""
+        discovered = set()
+
+        # Common SRV record prefixes
+        srv_prefixes = [
+            '_autodiscover._tcp',  # Exchange
+            '_sip._tcp', '_sip._udp',  # SIP
+            '_xmpp-client._tcp', '_xmpp-server._tcp',  # XMPP
+            '_ldap._tcp',  # LDAP
+            '_kerberos._tcp', '_kerberos._udp',  # Kerberos
+            '_gc._tcp',  # Global Catalog
+            '_kpasswd._tcp', '_kpasswd._udp',  # Kerberos password
+            '_http._tcp', '_https._tcp',  # HTTP
+            '_imaps._tcp', '_imap._tcp',  # IMAP
+            '_pop3s._tcp', '_pop3._tcp',  # POP3
+            '_smtps._tcp', '_smtp._tcp',  # SMTP
+            '_submission._tcp',  # Mail submission
+        ]
+
+        for prefix in srv_prefixes:
+            srv_name = f"{prefix}.{self.domain}"
+            srv_records = self.resolver.resolve_srv(srv_name)
+
+            for priority, weight, port, target in srv_records:
+                target = target.lower()
+                if target.endswith(f".{self.domain}"):
+                    discovered.add(target)
+                    self._add_discovered(target)
+                    logger.debug(f"SRV record {prefix}: {target}")
+
+        return discovered
+
+    def _query_soa(self) -> Set[str]:
+        """Query SOA record for hostnames"""
+        discovered = set()
+
+        soa = self.resolver.resolve_soa(self.domain)
+        if soa:
+            mname = soa['mname'].lower()
+            rname = soa['rname'].lower()
+
+            if mname.endswith(f".{self.domain}"):
+                discovered.add(mname)
+                self._add_discovered(mname)
+
+            # rname is often in email format (hostmaster.domain.com)
+            # Convert to actual domain
+            if '.' in rname:
+                potential_domain = rname
+                if potential_domain.endswith(f".{self.domain}"):
+                    discovered.add(potential_domain)
+                    self._add_discovered(potential_domain)
+
+        return discovered
+
+    def _query_caa(self) -> Set[str]:
+        """Query CAA records for domain references"""
+        discovered = set()
+
+        caa_records = self.resolver.resolve_any(self.domain, 'CAA')
+        for record in caa_records:
+            value = str(record).lower()
+            if value.endswith(f".{self.domain}"):
+                discovered.add(value)
+                self._add_discovered(value)
+
+        return discovered
+
+    def enumerate(self) -> Dict[str, Optional[str]]:
+        """
+        Mine all DNS record types for subdomains.
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        logger.info(f"Mining DNS records for {self.domain}")
+
+        # Query all record types
+        all_discovered = set()
+        all_discovered.update(self._query_mx())
+        all_discovered.update(self._query_ns())
+        all_discovered.update(self._query_txt())
+        all_discovered.update(self._query_srv())
+        all_discovered.update(self._query_soa())
+        all_discovered.update(self._query_caa())
+
+        # Resolve discovered subdomains
+        for subdomain in all_discovered:
+            if subdomain in self.discovered and self.discovered[subdomain] is None:
+                ip = self.resolver.resolve_a(subdomain)
+                if ip:
+                    self.discovered[subdomain] = ip
+
+        logger.info(f"DNS record mining found {len(self.discovered)} subdomains")
+        return self.discovered
diff --git a/src/usfx/modules/dnssec_walker.py b/src/usfx/modules/dnssec_walker.py
@@ -0,0 +1,187 @@
+"""
+DNSSEC Zone Walking Module
+
+Exploits DNSSEC NSEC/NSEC3 records to enumerate zone contents.
+NSEC records form a chain linking all records in a zone.
+"""
+
+import logging
+from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING
+
+import dns.rdatatype
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class DNSSECWalker(BaseModule):
+    """DNSSEC zone walking via NSEC/NSEC3 records"""
+
+    MODULE_NAME = "dnssec_walker"
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        timeout: float = 5.0,
+        max_iterations: int = 500
+    ):
+        """
+        Initialize DNSSEC walker.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            timeout: DNS query timeout
+            max_iterations: Maximum NSEC chain iterations
+        """
+        super().__init__(domain, resolver, timeout)
+        self.max_iterations = max_iterations
+
+    def _check_dnssec(self) -> Tuple[bool, bool, dict]:
+        """
+        Check if domain uses DNSSEC and what type.
+
+        Returns:
+            Tuple of (has_dnssec, uses_nsec3, nsec3_params)
+        """
+        try:
+            # Try to get NSEC3PARAM first
+            records = self.resolver.resolve_any(self.domain, 'NSEC3PARAM')
+            if records:
+                # NSEC3 is in use
+                return True, True, {}
+
+            # Try to get DNSKEY to confirm DNSSEC
+            records = self.resolver.resolve_any(self.domain, 'DNSKEY')
+            if records:
+                return True, False, {}
+
+            return False, False, {}
+
+        except Exception as e:
+            logger.debug(f"Error checking DNSSEC: {e}")
+            return False, False, {}
+
+    def _walk_nsec(self) -> Set[str]:
+        """
+        Walk NSEC records to enumerate zone.
+
+        Returns:
+            Set of discovered subdomains
+        """
+        discovered = set()
+        current = self.domain
+        iterations = 0
+
+        logger.info(f"Starting NSEC zone walk for {self.domain}")
+
+        native_resolver = self.resolver.get_native_resolver()
+
+        while iterations < self.max_iterations:
+            iterations += 1
+
+            try:
+                # Query for NSEC record at current name
+                try:
+                    answers = native_resolver.resolve(current, 'NSEC')
+                    for rdata in answers:
+                        next_name = str(rdata.next).rstrip('.')
+
+                        if next_name.endswith(f".{self.domain}"):
+                            discovered.add(next_name)
+                            self._add_discovered(next_name)
+
+                        # Check if we've looped back
+                        if next_name == self.domain or next_name <= current:
+                            logger.info(f"NSEC walk complete: {len(discovered)} found")
+                            return discovered
+
+                        current = next_name
+
+                except Exception:
+                    # Try querying a fake subdomain to get NSEC in authority
+                    fake = f"aaa{iterations}.{current if current != self.domain else self.domain}"
+                    try:
+                        native_resolver.resolve(fake, 'A')
+                    except Exception:
+                        break
+
+            except Exception as e:
+                logger.debug(f"NSEC walk error at {current}: {e}")
+                break
+
+        logger.info(f"NSEC walk finished with {len(discovered)} subdomains")
+        return discovered
+
+    def _walk_nsec3(self, params: dict) -> Set[str]:
+        """
+        Attempt to crack NSEC3 hashes (limited effectiveness).
+
+        For NSEC3, we can only try to match known names against hashes,
+        so this is less effective than NSEC walking.
+
+        Args:
+            params: NSEC3 parameters
+
+        Returns:
+            Set of discovered subdomains
+        """
+        discovered = set()
+
+        # NSEC3 uses hashed names, making direct walking impossible
+        # We can only verify if specific names exist by computing their hashes
+
+        # Common subdomain prefixes to try
+        common_prefixes = [
+            'www', 'mail', 'ftp', 'smtp', 'pop', 'imap', 'webmail',
+            'api', 'dev', 'test', 'staging', 'admin', 'blog', 'shop',
+            'cdn', 'static', 'ns1', 'ns2', 'dns', 'vpn', 'remote'
+        ]
+
+        logger.info(f"Attempting NSEC3 hash matching for {self.domain}")
+
+        for prefix in common_prefixes:
+            subdomain = f"{prefix}.{self.domain}"
+            ip = self.resolver.resolve_a(subdomain)
+            if ip:
+                discovered.add(subdomain)
+                self._add_discovered(subdomain, ip)
+
+        logger.info(f"NSEC3 matching found {len(discovered)} subdomains")
+        return discovered
+
+    def enumerate(self) -> Dict[str, Optional[str]]:
+        """
+        Enumerate subdomains using DNSSEC zone walking.
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        logger.info(f"Checking DNSSEC for {self.domain}")
+
+        has_dnssec, uses_nsec3, nsec3_params = self._check_dnssec()
+
+        if not has_dnssec:
+            logger.info(f"Domain {self.domain} does not use DNSSEC")
+            return {}
+
+        if uses_nsec3:
+            logger.info(f"Domain uses NSEC3 (hashed) - limited enumeration possible")
+            self._walk_nsec3(nsec3_params)
+        else:
+            logger.info(f"Domain uses NSEC - zone walking possible")
+            self._walk_nsec()
+
+        # Resolve discovered subdomains to get IPs
+        for subdomain in list(self.discovered.keys()):
+            if self.discovered[subdomain] is None:
+                ip = self.resolver.resolve_a(subdomain)
+                if ip:
+                    self.discovered[subdomain] = ip
+
+        return self.discovered
diff --git a/src/usfx/modules/permutation.py b/src/usfx/modules/permutation.py
@@ -0,0 +1,286 @@
+"""
+Permutation Engine Module
+
+Generates subdomain variations based on discovered subdomains:
+- Version variations (api-v1 -> api-v2, api-v3)
+- Environment variations (api -> api-dev, api-staging)
+- Numeric variations (web1 -> web2, web3)
+- Prefix/suffix combinations
+"""
+
+import logging
+import re
+from typing import Callable, Dict, List, Optional, Set, TYPE_CHECKING
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class PermutationEngine(BaseModule):
+    """Subdomain permutation generator"""
+
+    MODULE_NAME = "permutation"
+
+    # Common prefixes
+    PREFIXES = [
+        'dev', 'test', 'stage', 'staging', 'prod', 'production',
+        'qa', 'uat', 'sandbox', 'demo', 'beta', 'alpha',
+        'internal', 'external', 'private', 'public',
+        'old', 'new', 'legacy', 'backup', 'temp', 'tmp',
+        'api', 'app', 'web', 'mobile', 'm', 'admin',
+        'secure', 'ssl', 'vpn', 'remote', 'local'
+    ]
+
+    # Common suffixes
+    SUFFIXES = [
+        'dev', 'test', 'stage', 'staging', 'prod', 'production',
+        'qa', 'uat', 'sandbox', 'demo', 'beta', 'alpha',
+        'internal', 'external', 'api', 'app', 'web',
+        'old', 'new', 'backup', 'bak', 'temp', 'tmp',
+        '01', '02', '03', '1', '2', '3'
+    ]
+
+    # Separator characters
+    SEPARATORS = ['-', '.', '_', '']
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None
+    ):
+        """
+        Initialize permutation engine.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+        """
+        super().__init__(domain, resolver)
+
+    def _extract_patterns(self, subdomains: Set[str]) -> dict:
+        """
+        Analyze discovered subdomains to extract patterns.
+
+        Args:
+            subdomains: Set of discovered subdomains
+
+        Returns:
+            Dict of pattern types and examples
+        """
+        patterns = {
+            'versioned': [],      # api-v1, api-v2
+            'numbered': [],       # web1, web2
+            'environment': [],    # api-dev, api-prod
+            'base_names': set()   # base names without modifiers
+        }
+
+        for subdomain in subdomains:
+            # Extract the subdomain part (without parent domain)
+            if subdomain.endswith(f".{self.domain}"):
+                name = subdomain[:-len(self.domain) - 1]
+            else:
+                continue
+
+            # Detect versioned patterns (v1, v2, etc.)
+            version_match = re.match(r'^(.+?)[-._]?v(\d+)$', name, re.IGNORECASE)
+            if version_match:
+                patterns['versioned'].append({
+                    'base': version_match.group(1),
+                    'version': int(version_match.group(2)),
+                    'original': name
+                })
+                patterns['base_names'].add(version_match.group(1))
+                continue
+
+            # Detect numbered patterns (web1, web2, etc.)
+            numbered_match = re.match(r'^(.+?)(\d+)$', name)
+            if numbered_match:
+                patterns['numbered'].append({
+                    'base': numbered_match.group(1),
+                    'number': int(numbered_match.group(2)),
+                    'original': name
+                })
+                patterns['base_names'].add(numbered_match.group(1))
+                continue
+
+            # Detect environment patterns
+            for env in ['dev', 'test', 'stage', 'staging', 'prod', 'qa', 'uat']:
+                if re.search(rf'[-._]{env}$', name, re.IGNORECASE):
+                    base = re.sub(rf'[-._]{env}$', '', name, flags=re.IGNORECASE)
+                    patterns['environment'].append({
+                        'base': base,
+                        'env': env,
+                        'original': name
+                    })
+                    patterns['base_names'].add(base)
+                    break
+                elif re.search(rf'^{env}[-._]', name, re.IGNORECASE):
+                    base = re.sub(rf'^{env}[-._]', '', name, flags=re.IGNORECASE)
+                    patterns['environment'].append({
+                        'base': base,
+                        'env': env,
+                        'original': name,
+                        'prefix': True
+                    })
+                    patterns['base_names'].add(base)
+                    break
+            else:
+                patterns['base_names'].add(name)
+
+        return patterns
+
+    def _generate_version_permutations(self, patterns: List[dict]) -> Set[str]:
+        """Generate version-based permutations"""
+        permutations = set()
+
+        for pattern in patterns:
+            base = pattern['base']
+            current_version = pattern['version']
+
+            # Generate nearby versions
+            for v in range(1, min(current_version + 5, 20)):
+                if v != current_version:
+                    for sep in ['-', '_', '.', '']:
+                        permutations.add(f"{base}{sep}v{v}")
+
+        return permutations
+
+    def _generate_numbered_permutations(self, patterns: List[dict]) -> Set[str]:
+        """Generate number-based permutations"""
+        permutations = set()
+
+        for pattern in patterns:
+            base = pattern['base']
+            current_num = pattern['number']
+
+            # Generate nearby numbers
+            for n in range(1, min(current_num + 10, 50)):
+                if n != current_num:
+                    permutations.add(f"{base}{n}")
+                    if n < 10:
+                        permutations.add(f"{base}0{n}")
+
+        return permutations
+
+    def _generate_environment_permutations(self, patterns: List[dict]) -> Set[str]:
+        """Generate environment-based permutations"""
+        permutations = set()
+        environments = ['dev', 'test', 'stage', 'staging', 'prod', 'production', 'qa', 'uat', 'sandbox', 'demo']
+
+        for pattern in patterns:
+            base = pattern['base']
+            is_prefix = pattern.get('prefix', False)
+
+            for env in environments:
+                if env != pattern.get('env', '').lower():
+                    for sep in ['-', '_', '.']:
+                        if is_prefix:
+                            permutations.add(f"{env}{sep}{base}")
+                        else:
+                            permutations.add(f"{base}{sep}{env}")
+
+        return permutations
+
+    def _generate_prefix_suffix_permutations(self, base_names: Set[str]) -> Set[str]:
+        """Generate prefix/suffix combinations"""
+        permutations = set()
+
+        for base in base_names:
+            # Don't permute very short names
+            if len(base) < 2:
+                continue
+
+            # Add common prefixes
+            for prefix in self.PREFIXES[:15]:  # Limit to avoid explosion
+                for sep in ['-', '.']:
+                    permutations.add(f"{prefix}{sep}{base}")
+
+            # Add common suffixes
+            for suffix in self.SUFFIXES[:15]:
+                for sep in ['-', '.']:
+                    permutations.add(f"{base}{sep}{suffix}")
+
+        return permutations
+
+    def generate(
+        self,
+        discovered_subdomains: Set[str],
+        max_permutations: int = 1000
+    ) -> Set[str]:
+        """
+        Generate permutations of discovered subdomains.
+
+        Args:
+            discovered_subdomains: Set of discovered subdomains
+            max_permutations: Maximum number of permutations to generate
+
+        Returns:
+            Set of generated subdomain candidates (full FQDNs)
+        """
+        logger.info(f"Generating permutations for {len(discovered_subdomains)} subdomains")
+
+        patterns = self._extract_patterns(discovered_subdomains)
+        all_permutations = set()
+
+        # Generate different types of permutations
+        all_permutations.update(self._generate_version_permutations(patterns['versioned']))
+        all_permutations.update(self._generate_numbered_permutations(patterns['numbered']))
+        all_permutations.update(self._generate_environment_permutations(patterns['environment']))
+        all_permutations.update(self._generate_prefix_suffix_permutations(patterns['base_names']))
+
+        # Convert to FQDNs
+        fqdns = set()
+        for perm in all_permutations:
+            fqdn = f"{perm}.{self.domain}"
+            if fqdn not in discovered_subdomains:
+                fqdns.add(fqdn)
+
+        # Limit results
+        if len(fqdns) > max_permutations:
+            fqdns = set(list(fqdns)[:max_permutations])
+
+        logger.info(f"Generated {len(fqdns)} permutation candidates")
+        return fqdns
+
+    def enumerate(
+        self,
+        discovered_subdomains: Dict[str, Optional[str]] = None,
+        resolver_func: Callable[[str], Optional[str]] = None
+    ) -> Dict[str, Optional[str]]:
+        """
+        Generate and validate permutations.
+
+        Args:
+            discovered_subdomains: Dict of {subdomain: ip}
+            resolver_func: Optional function to resolve subdomains
+
+        Returns:
+            Dict of {subdomain: ip_address} for valid permutations
+        """
+        if not discovered_subdomains:
+            discovered_subdomains = {}
+
+        # Generate candidates
+        candidates = self.generate(set(discovered_subdomains.keys()))
+
+        if not candidates:
+            return {}
+
+        # Use provided resolver or default
+        if resolver_func is None:
+            resolver_func = self._resolve_subdomain
+
+        logger.info(f"Validating {len(candidates)} permutation candidates")
+
+        for candidate in candidates:
+            ip = resolver_func(candidate)
+            if ip:
+                self._add_discovered(candidate, ip)
+                logger.debug(f"Valid permutation: {candidate} -> {ip}")
+
+        logger.info(f"Permutation engine found {len(self.discovered)} valid subdomains")
+        return self.discovered
diff --git a/src/usfx/modules/recursive_enum.py b/src/usfx/modules/recursive_enum.py
@@ -0,0 +1,194 @@
+"""
+Recursive Subdomain Enumeration Module
+
+Discovers sub-subdomains by treating discovered subdomains as base domains
+and running enumeration on them (e.g., finding dev.api.example.com when
+api.example.com was discovered).
+"""
+
+import logging
+import time
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Callable, Dict, Optional, TYPE_CHECKING
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class RecursiveEnumerator(BaseModule):
+    """Discovers sub-subdomains through recursive enumeration"""
+
+    MODULE_NAME = "recursive_enum"
+
+    # Short wordlist for recursive enumeration (most common prefixes only)
+    RECURSIVE_WORDLIST = [
+        # Development
+        'dev', 'staging', 'test', 'qa', 'beta',
+
+        # Versioning
+        'v1', 'v2', 'v3',
+
+        # Environments
+        'prod', 'internal',
+
+        # Geographic
+        'us', 'eu', 'asia',
+
+        # Numbered
+        '1', '2', 'node1', 'node2',
+
+        # Services
+        'api', 'app', 'www', 'admin', 'auth', 'db', 'cdn', 'static',
+    ]
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        threads: int = 20,
+        max_depth: int = 2
+    ):
+        """
+        Initialize recursive enumerator.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            threads: Number of parallel threads
+            max_depth: Maximum recursion depth
+        """
+        super().__init__(domain, resolver)
+        self.threads = threads
+        self.max_depth = max_depth
+
+    def _enumerate_subdomain(self, base_subdomain: str) -> Dict[str, Optional[str]]:
+        """Enumerate sub-subdomains for a given subdomain"""
+        discovered = {}
+
+        for prefix in self.RECURSIVE_WORDLIST:
+            candidate = f'{prefix}.{base_subdomain}'
+
+            # Skip if it's too deep (more than max_depth levels above original domain)
+            depth = candidate.count('.') - self.domain.count('.')
+            if depth > self.max_depth:
+                continue
+
+            ip = self.resolver.resolve_a(candidate)
+            if ip:
+                discovered[candidate] = ip
+
+        return discovered
+
+    def _get_subdomain_depth(self, subdomain: str) -> int:
+        """Calculate depth of subdomain relative to base domain"""
+        return subdomain.count('.') - self.domain.count('.')
+
+    def enumerate(
+        self,
+        known_subdomains: Dict[str, Optional[str]] = None,
+        resolver_func: Optional[Callable[[str], Optional[str]]] = None
+    ) -> Dict[str, Optional[str]]:
+        """
+        Recursively enumerate sub-subdomains.
+
+        Args:
+            known_subdomains: Dict of discovered subdomains to use as base
+            resolver_func: Optional custom resolver function
+
+        Returns:
+            Dict of newly discovered sub-subdomains with IPs
+        """
+        start_time = time.time()
+        max_duration = 45  # Maximum 45 seconds
+
+        if not known_subdomains:
+            return {}
+
+        logger.info(f"Starting recursive enumeration on {len(known_subdomains)} subdomains")
+
+        # Filter subdomains that are worth recursing on
+        # Only recurse on depth-1 subdomains (direct subdomains of base domain)
+        candidates = []
+        for subdomain in known_subdomains.keys():
+            depth = self._get_subdomain_depth(subdomain)
+            if depth == 1:  # Only direct subdomains
+                candidates.append(subdomain)
+
+        if not candidates:
+            logger.info("No suitable subdomains for recursive enumeration")
+            return {}
+
+        # Limit to first 10 candidates
+        candidates = candidates[:10]
+        logger.info(f"Recursively enumerating {len(candidates)} base subdomains")
+
+        all_discovered = {}
+
+        # Process each candidate subdomain
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = {
+                executor.submit(self._enumerate_subdomain, sub): sub
+                for sub in candidates
+            }
+
+            for future in as_completed(futures, timeout=30):
+                if time.time() - start_time > max_duration:
+                    logger.info("Recursive enumeration timeout reached")
+                    break
+
+                try:
+                    results = future.result(timeout=10)
+                    for subdomain, ip in results.items():
+                        if subdomain not in known_subdomains and subdomain not in all_discovered:
+                            all_discovered[subdomain] = ip
+                            self._add_discovered(subdomain, ip)
+                            logger.debug(f"Recursive discovery: {subdomain} -> {ip}")
+                except Exception as e:
+                    logger.debug(f"Recursive enumeration error: {e}")
+
+        logger.info(f"Recursive enumeration discovered {len(all_discovered)} new sub-subdomains in {time.time() - start_time:.1f}s")
+
+        return self.discovered
+
+    def deep_enumerate(
+        self,
+        known_subdomains: Dict[str, Optional[str]],
+        max_iterations: int = 3
+    ) -> Dict[str, Optional[str]]:
+        """
+        Perform multiple rounds of recursive enumeration.
+
+        Each round uses newly discovered subdomains as seeds for the next round.
+
+        Args:
+            known_subdomains: Initial set of known subdomains
+            max_iterations: Maximum number of recursive rounds
+
+        Returns:
+            All newly discovered subdomains
+        """
+        all_discovered = {}
+        current_known = dict(known_subdomains)
+
+        for iteration in range(max_iterations):
+            logger.info(f"Recursive enumeration round {iteration + 1}/{max_iterations}")
+
+            new_discoveries = self.enumerate(current_known)
+
+            if not new_discoveries:
+                logger.info(f"No new discoveries in round {iteration + 1}, stopping")
+                break
+
+            # Add to total discovered
+            all_discovered.update(new_discoveries)
+
+            # Update known for next round
+            current_known.update(new_discoveries)
+
+            logger.info(f"Round {iteration + 1} found {len(new_discoveries)} new subdomains")
+
+        return all_discovered
diff --git a/src/usfx/modules/reverse_dns.py b/src/usfx/modules/reverse_dns.py
@@ -0,0 +1,212 @@
+"""
+Reverse DNS Sweep Module
+
+Discovers subdomains by performing PTR lookups on IP ranges
+associated with the domain.
+"""
+
+import ipaddress
+import logging
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Callable, Dict, List, Optional, Set, TYPE_CHECKING
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class ReverseDNS(BaseModule):
+    """Reverse DNS (PTR) enumeration"""
+
+    MODULE_NAME = "reverse_dns"
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        timeout: float = 2.0,
+        threads: int = 30
+    ):
+        """
+        Initialize reverse DNS module.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            timeout: DNS query timeout
+            threads: Number of parallel threads
+        """
+        super().__init__(domain, resolver, timeout)
+        self.threads = threads
+        self.known_ips: Set[str] = set()
+
+    def _get_domain_ips(self) -> Set[str]:
+        """
+        Get IP addresses associated with the domain.
+
+        Returns:
+            Set of IP addresses
+        """
+        ips = set()
+
+        # Get A records for main domain and common subdomains
+        for subdomain in ['', 'www', 'mail', 'ns1', 'ns2']:
+            fqdn = f"{subdomain}.{self.domain}" if subdomain else self.domain
+            ip = self.resolver.resolve_a(fqdn)
+            if ip:
+                ips.add(ip)
+
+        # Get IPs from MX records
+        mx_records = self.resolver.resolve_mx(self.domain)
+        for priority, mx_host in mx_records:
+            ip = self.resolver.resolve_a(mx_host)
+            if ip:
+                ips.add(ip)
+
+        # Get IPs from NS records
+        ns_records = self.resolver.resolve_ns(self.domain)
+        for ns_host in ns_records:
+            ip = self.resolver.resolve_a(ns_host)
+            if ip:
+                ips.add(ip)
+
+        logger.info(f"Found {len(ips)} known IPs for {self.domain}")
+        return ips
+
+    def _get_ip_range(self, ip: str) -> List[str]:
+        """
+        Get IP range to scan (/24 network).
+
+        Args:
+            ip: Base IP address
+
+        Returns:
+            List of IPs in the range
+        """
+        try:
+            ip_obj = ipaddress.ip_address(ip)
+            if isinstance(ip_obj, ipaddress.IPv4Address):
+                # Get /24 network
+                network = ipaddress.ip_network(f"{ip}/24", strict=False)
+                return [str(host) for host in network.hosts()]
+            else:
+                # For IPv6, use /112 (smaller range)
+                network = ipaddress.ip_network(f"{ip}/112", strict=False)
+                return [str(host) for host in list(network.hosts())[:256]]
+        except Exception as e:
+            logger.error(f"Error getting IP range for {ip}: {e}")
+            return []
+
+    def _scan_ip(self, ip: str) -> tuple[str, Optional[str]]:
+        """
+        Scan a single IP for PTR record matching our domain.
+
+        Args:
+            ip: IP to scan
+
+        Returns:
+            Tuple of (ip, hostname) if found
+        """
+        hostname = self.resolver.resolve_ptr(ip)
+        if hostname and hostname.endswith(f".{self.domain}"):
+            return ip, hostname
+        return ip, None
+
+    def enumerate(
+        self,
+        additional_ips: Optional[Set[str]] = None,
+        ip_ranges: Optional[List[str]] = None,
+        progress_callback: Optional[Callable[[int, int, int], None]] = None
+    ) -> Dict[str, Optional[str]]:
+        """
+        Perform reverse DNS sweep.
+
+        Args:
+            additional_ips: Additional IPs to include in sweep
+            ip_ranges: CIDR ranges to scan (e.g., ['192.168.0.0/24'])
+            progress_callback: Progress callback (current, total, found)
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        logger.info(f"Starting reverse DNS sweep for {self.domain}")
+
+        # Get known IPs
+        self.known_ips = self._get_domain_ips()
+        if additional_ips:
+            self.known_ips.update(additional_ips)
+
+        # Get all IPs to scan (unique /24 networks)
+        networks_scanned = set()
+        all_ips = set()
+
+        # Add IPs from discovered domain records
+        for ip in self.known_ips:
+            try:
+                network = ipaddress.ip_network(f"{ip}/24", strict=False)
+                network_key = str(network)
+                if network_key not in networks_scanned:
+                    networks_scanned.add(network_key)
+                    all_ips.update(self._get_ip_range(ip))
+            except Exception:
+                pass
+
+        # Add explicit IP ranges
+        if ip_ranges:
+            for cidr in ip_ranges:
+                try:
+                    network = ipaddress.ip_network(cidr, strict=False)
+                    all_ips.update(str(host) for host in network.hosts())
+                    logger.info(f"Added range {cidr} ({network.num_addresses} IPs)")
+                except ValueError as e:
+                    logger.warning(f"Invalid CIDR range {cidr}: {e}")
+
+        if not all_ips:
+            logger.warning("No IP ranges to scan")
+            return {}
+
+        total = len(all_ips)
+        logger.info(f"Scanning {total} IPs across {len(networks_scanned)} networks")
+
+        completed = 0
+        found = 0
+
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = {executor.submit(self._scan_ip, ip): ip for ip in all_ips}
+
+            for future in as_completed(futures):
+                try:
+                    ip, hostname = future.result()
+                    completed += 1
+
+                    if hostname:
+                        self._add_discovered(hostname, ip)
+                        found += 1
+                        logger.debug(f"Found: {hostname} -> {ip}")
+
+                    if progress_callback and completed % 50 == 0:
+                        progress_callback(completed, total, found)
+
+                except Exception as e:
+                    completed += 1
+                    logger.debug(f"Error in PTR lookup: {e}")
+
+        if progress_callback:
+            progress_callback(completed, total, found)
+
+        logger.info(f"Reverse DNS sweep found {len(self.discovered)} subdomains")
+        return self.discovered
+
+    def add_ips_from_subdomains(self, subdomains: Dict[str, Optional[str]]) -> None:
+        """
+        Add IPs from discovered subdomains for scanning.
+
+        Args:
+            subdomains: Dict of {subdomain: ip_address}
+        """
+        for subdomain, ip in subdomains.items():
+            if ip:
+                self.known_ips.add(ip)
diff --git a/src/usfx/modules/tls_analyzer.py b/src/usfx/modules/tls_analyzer.py
@@ -0,0 +1,265 @@
+"""
+TLS Certificate Analysis Module
+
+Extracts subdomains from TLS certificates by connecting to discovered IPs
+and parsing the Subject Alternative Names (SANs) field.
+"""
+
+import logging
+import socket
+import ssl
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Dict, List, Optional, Set, TYPE_CHECKING
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+try:
+    from cryptography import x509
+    from cryptography.x509.oid import NameOID, ExtensionOID
+    from cryptography.hazmat.backends import default_backend
+    CRYPTOGRAPHY_AVAILABLE = True
+except ImportError:
+    CRYPTOGRAPHY_AVAILABLE = False
+
+logger = logging.getLogger(__name__)
+
+
+class TLSAnalyzer(BaseModule):
+    """TLS certificate analysis for subdomain discovery"""
+
+    MODULE_NAME = "tls_analyzer"
+
+    # Common ports that might serve TLS
+    COMMON_TLS_PORTS = [443, 8443, 8080, 4443, 9443]
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        timeout: float = 5.0,
+        threads: int = 10
+    ):
+        """
+        Initialize TLS analyzer.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            timeout: Connection timeout
+            threads: Number of parallel threads
+        """
+        super().__init__(domain, resolver, timeout)
+        self.threads = threads
+        self.analyzed_hosts: Set[str] = set()
+
+    def _create_ssl_context(self) -> ssl.SSLContext:
+        """Create an SSL context that accepts all certificates"""
+        ctx = ssl.create_default_context()
+        ctx.check_hostname = False
+        ctx.verify_mode = ssl.CERT_NONE
+        return ctx
+
+    def _get_certificate(self, host: str, port: int = 443) -> Optional[bytes]:
+        """
+        Get the TLS certificate from a host.
+
+        Args:
+            host: Hostname or IP
+            port: Port number
+
+        Returns:
+            DER-encoded certificate or None
+        """
+        try:
+            ctx = self._create_ssl_context()
+
+            with socket.create_connection((host, port), timeout=self.timeout) as sock:
+                with ctx.wrap_socket(sock, server_hostname=self.domain) as ssl_sock:
+                    cert_der = ssl_sock.getpeercert(binary_form=True)
+                    return cert_der
+
+        except (socket.timeout, socket.error, ssl.SSLError) as e:
+            logger.debug(f"Failed to get cert from {host}:{port}: {e}")
+            return None
+        except Exception as e:
+            logger.debug(f"Error getting cert from {host}:{port}: {e}")
+            return None
+
+    def _extract_sans_cryptography(self, cert_der: bytes) -> Set[str]:
+        """
+        Extract SANs using cryptography library.
+
+        Args:
+            cert_der: DER-encoded certificate
+
+        Returns:
+            Set of domain names from SANs
+        """
+        if not CRYPTOGRAPHY_AVAILABLE:
+            return set()
+
+        domains = set()
+
+        try:
+            cert = x509.load_der_x509_certificate(cert_der, default_backend())
+
+            # Get Subject Alternative Names
+            try:
+                san_ext = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
+                for name in san_ext.value:
+                    if isinstance(name, x509.DNSName):
+                        domains.add(name.value.lower())
+            except x509.ExtensionNotFound:
+                pass
+
+            # Get Common Name
+            try:
+                for attr in cert.subject:
+                    if attr.oid == NameOID.COMMON_NAME:
+                        domains.add(attr.value.lower())
+            except Exception:
+                pass
+
+        except Exception as e:
+            logger.debug(f"Error parsing certificate: {e}")
+
+        return domains
+
+    def _extract_sans(self, cert_der: bytes) -> Set[str]:
+        """
+        Extract SANs from certificate.
+
+        Args:
+            cert_der: DER-encoded certificate
+
+        Returns:
+            Set of domain names from SANs
+        """
+        if CRYPTOGRAPHY_AVAILABLE:
+            return self._extract_sans_cryptography(cert_der)
+        else:
+            logger.warning("cryptography library not available - TLS analysis limited")
+            return set()
+
+    def _analyze_host(self, host: str, port: int = 443) -> Set[str]:
+        """
+        Analyze a single host's TLS certificate.
+
+        Args:
+            host: Host to analyze
+            port: Port number
+
+        Returns:
+            Set of discovered subdomains
+        """
+        discovered = set()
+        host_key = f"{host}:{port}"
+
+        if host_key in self.analyzed_hosts:
+            return discovered
+
+        self.analyzed_hosts.add(host_key)
+
+        cert_der = self._get_certificate(host, port)
+        if not cert_der:
+            return discovered
+
+        sans = self._extract_sans(cert_der)
+
+        for name in sans:
+            # Remove wildcard prefix
+            if name.startswith('*.'):
+                name = name[2:]
+
+            # Check if it's a subdomain of our target
+            if name.endswith(f".{self.domain}") and name != self.domain:
+                discovered.add(name)
+                logger.debug(f"Found SAN: {name} from {host}:{port}")
+
+        return discovered
+
+    def _probe_sni(self, ip: str, hostname: str, port: int = 443) -> bool:
+        """
+        Probe if a specific hostname works via SNI.
+
+        Args:
+            ip: IP address to connect to
+            hostname: Hostname to send via SNI
+            port: Port number
+
+        Returns:
+            True if the hostname is served at this IP
+        """
+        try:
+            ctx = self._create_ssl_context()
+
+            with socket.create_connection((ip, port), timeout=self.timeout) as sock:
+                with ctx.wrap_socket(sock, server_hostname=hostname) as ssl_sock:
+                    cert_der = ssl_sock.getpeercert(binary_form=True)
+                    if cert_der:
+                        sans = self._extract_sans(cert_der)
+                        return hostname.lower() in sans or f"*.{'.'.join(hostname.split('.')[1:])}".lower() in sans
+
+        except Exception:
+            pass
+
+        return False
+
+    def enumerate(
+        self,
+        known_hosts: Optional[Dict[str, str]] = None,
+        scan_ports: bool = False
+    ) -> Dict[str, Optional[str]]:
+        """
+        Enumerate subdomains from TLS certificates.
+
+        Args:
+            known_hosts: Dict of {subdomain: ip} to analyze
+            scan_ports: Whether to scan multiple TLS ports
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        logger.info(f"Starting TLS analysis for {self.domain}")
+
+        if not CRYPTOGRAPHY_AVAILABLE:
+            logger.warning("cryptography library not available - TLS analysis limited")
+
+        # Determine hosts to analyze
+        hosts_to_analyze = set()
+
+        if known_hosts:
+            for subdomain, ip in known_hosts.items():
+                if ip:
+                    hosts_to_analyze.add(ip)
+                hosts_to_analyze.add(subdomain)
+
+        # Always try the main domain
+        hosts_to_analyze.add(self.domain)
+        hosts_to_analyze.add(f"www.{self.domain}")
+
+        ports = self.COMMON_TLS_PORTS if scan_ports else [443]
+        all_discovered = set()
+
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = []
+            for host in hosts_to_analyze:
+                for port in ports:
+                    futures.append(executor.submit(self._analyze_host, host, port))
+
+            for future in as_completed(futures):
+                try:
+                    discovered = future.result()
+                    all_discovered.update(discovered)
+                except Exception as e:
+                    logger.debug(f"Error in TLS analysis: {e}")
+
+        # Build result dict
+        for subdomain in all_discovered:
+            self._add_discovered(subdomain)
+
+        logger.info(f"TLS analysis found {len(self.discovered)} subdomains")
+        return self.discovered
diff --git a/src/usfx/modules/vhost_scanner.py b/src/usfx/modules/vhost_scanner.py
@@ -0,0 +1,328 @@
+"""
+Virtual Host Discovery Module
+
+Discovers subdomains by brute-forcing Host headers on known IP addresses.
+This technique finds subdomains hosted on the same server that may not
+be discoverable through DNS alone.
+
+Note: This module does NOT use external proxy services (unlike the web version).
+It connects directly to target IPs, which is suitable for internal network scanning.
+"""
+
+import hashlib
+import logging
+import time
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
+
+import requests
+from requests.exceptions import RequestException
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class VHostScanner(BaseModule):
+    """Discovers virtual hosts through Host header manipulation"""
+
+    MODULE_NAME = "vhost_scanner"
+
+    # Common subdomain prefixes to try
+    VHOST_WORDLIST = [
+        # Common web apps
+        'www', 'app', 'web', 'portal', 'admin', 'panel', 'dashboard',
+        'api', 'api2', 'api3', 'rest', 'graphql',
+        'blog', 'news', 'cms', 'wordpress', 'wp',
+        'shop', 'store', 'cart', 'checkout', 'payment',
+        'forum', 'community', 'support', 'help', 'docs', 'documentation',
+        'wiki', 'kb', 'knowledgebase',
+
+        # Development
+        'dev', 'development', 'staging', 'stage', 'test', 'testing',
+        'qa', 'uat', 'sandbox', 'demo', 'preview', 'beta', 'alpha',
+        'local', 'localhost', 'debug',
+
+        # Internal
+        'internal', 'intranet', 'private', 'corp', 'corporate',
+        'employee', 'staff', 'hr', 'finance', 'legal',
+
+        # Security
+        'secure', 'login', 'signin', 'auth', 'sso', 'oauth', 'id', 'identity',
+        'account', 'accounts', 'my', 'profile', 'user', 'users',
+
+        # Services
+        'mail', 'email', 'webmail', 'mx', 'smtp', 'imap', 'pop',
+        'ftp', 'sftp', 'files', 'upload', 'download',
+        'cdn', 'static', 'assets', 'media', 'img', 'images', 'css', 'js',
+
+        # Infrastructure
+        'server', 'host', 'node', 'cluster',
+        'db', 'database', 'mysql', 'postgres', 'mongo', 'redis',
+        'proxy', 'gateway', 'lb', 'loadbalancer',
+        'vpn', 'remote', 'rdp', 'ssh',
+
+        # Monitoring
+        'monitor', 'monitoring', 'status', 'health', 'ping',
+        'metrics', 'grafana', 'prometheus', 'kibana', 'logs',
+
+        # Geographic/Versioned
+        'us', 'eu', 'asia', 'uk',
+        'v1', 'v2', 'v3', 'www2', 'www3', 'new', 'old', 'legacy',
+    ]
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        threads: int = 10,
+        timeout: float = 5.0
+    ):
+        """
+        Initialize vhost scanner.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            threads: Number of parallel threads
+            timeout: HTTP request timeout
+        """
+        super().__init__(domain, resolver)
+        self.threads = threads
+        self.timeout = timeout
+
+        # Suppress SSL warnings
+        import urllib3
+        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
+    def _get_response_signature(self, response: requests.Response) -> str:
+        """Create a signature for a response to detect differences"""
+        factors = [
+            str(response.status_code),
+            str(len(response.content)),
+            response.headers.get('Content-Type', ''),
+            hashlib.md5(response.content[:1000]).hexdigest(),
+        ]
+        return '|'.join(factors)
+
+    def _get_baseline(self, ip: str, port: int = 443) -> Tuple[Optional[str], Optional[str]]:
+        """Get baseline response for invalid hostname"""
+        invalid_host = f'invalid-{hashlib.md5(ip.encode()).hexdigest()[:8]}.{self.domain}'
+
+        try:
+            protocol = 'https' if port == 443 else 'http'
+            response = requests.get(
+                f'{protocol}://{ip}:{port}/',
+                headers={'Host': invalid_host},
+                timeout=self.timeout,
+                allow_redirects=False,
+                verify=False
+            )
+            return self._get_response_signature(response), response.text[:500]
+        except:
+            return None, None
+
+    def _check_vhost(
+        self,
+        ip: str,
+        hostname: str,
+        port: int,
+        baseline_sig: Optional[str]
+    ) -> Optional[Tuple[str, bool]]:
+        """Check if a hostname is a valid virtual host"""
+        try:
+            protocol = 'https' if port == 443 else 'http'
+            response = requests.get(
+                f'{protocol}://{ip}:{port}/',
+                headers={'Host': hostname},
+                timeout=self.timeout,
+                allow_redirects=False,
+                verify=False
+            )
+
+            sig = self._get_response_signature(response)
+
+            # Check if response differs from baseline
+            if baseline_sig and sig != baseline_sig:
+                is_valid = True
+
+                # Check for generic error pages
+                body_lower = response.text.lower()
+                error_indicators = [
+                    'not found', '404', 'does not exist',
+                    'no such', 'invalid host', 'unknown host',
+                    'default page', 'welcome to nginx',
+                    'apache2 default', 'it works!',
+                ]
+
+                for indicator in error_indicators:
+                    if indicator in body_lower and response.status_code in [200, 404]:
+                        is_valid = False
+                        break
+
+                if is_valid and response.status_code in [200, 301, 302, 307, 308, 401, 403]:
+                    return hostname, True
+
+            # Even if same as baseline, 200 with good content might be valid
+            if response.status_code == 200 and len(response.content) > 1000:
+                if '<html' in response.text.lower() or '{' in response.text[:100]:
+                    return hostname, True
+
+        except RequestException:
+            pass
+        except Exception as e:
+            logger.debug(f"VHost check error for {hostname}: {e}")
+
+        return None
+
+    def _scan_ip(self, ip: str, ports: List[int] = None) -> Dict[str, Optional[str]]:
+        """Scan a single IP for virtual hosts"""
+        if ports is None:
+            ports = [443, 80]
+
+        discovered = {}
+
+        # Use shortened wordlist for faster scanning
+        quick_wordlist = [
+            'www', 'app', 'api', 'admin', 'portal', 'dashboard',
+            'dev', 'staging', 'test', 'beta', 'demo',
+            'mail', 'webmail', 'blog', 'shop', 'store',
+            'cdn', 'static', 'assets', 'img', 'media',
+            'login', 'auth', 'sso', 'secure',
+            'internal', 'intranet', 'vpn', 'remote',
+            'db', 'mysql', 'api2', 'v1', 'v2',
+        ]
+
+        for port in ports:
+            # Get baseline
+            baseline_sig, _ = self._get_baseline(ip, port)
+
+            # Test each hostname
+            for prefix in quick_wordlist:
+                hostname = f'{prefix}.{self.domain}'
+
+                result = self._check_vhost(ip, hostname, port, baseline_sig)
+                if result:
+                    hostname, _ = result
+                    if hostname not in discovered:
+                        discovered[hostname] = ip
+                        logger.debug(f"VHost discovered: {hostname} on {ip}:{port}")
+
+        return discovered
+
+    def enumerate(
+        self,
+        known_subdomains: Dict[str, Optional[str]] = None,
+        vhost_ips: List[str] = None
+    ) -> Dict[str, Optional[str]]:
+        """
+        Scan known IPs for virtual hosts.
+
+        Args:
+            known_subdomains: Dict of subdomains with their IPs
+            vhost_ips: Additional IPs to scan for vhosts
+
+        Returns:
+            Dict of newly discovered subdomains
+        """
+        start_time = time.time()
+        max_duration = 45  # Maximum 45 seconds for vhost scanning
+
+        if not known_subdomains:
+            known_subdomains = {}
+
+        # Extract unique IPs
+        unique_ips = set()
+
+        # Add IPs from known subdomains
+        for ip in known_subdomains.values():
+            if ip and len(unique_ips) < 5:
+                unique_ips.add(ip)
+
+        # Add explicitly provided IPs
+        if vhost_ips:
+            for ip in vhost_ips:
+                unique_ips.add(ip)
+
+        if not unique_ips:
+            logger.info("No IPs for virtual host scanning")
+            return {}
+
+        logger.info(f"Scanning {len(unique_ips)} unique IPs for virtual hosts")
+
+        all_discovered = {}
+
+        # Scan each IP
+        with ThreadPoolExecutor(max_workers=self.threads) as executor:
+            futures = {
+                executor.submit(self._scan_ip, ip): ip
+                for ip in unique_ips
+            }
+
+            for future in as_completed(futures, timeout=30):
+                if time.time() - start_time > max_duration:
+                    logger.info("VHost scanning timeout reached")
+                    break
+
+                ip = futures[future]
+                try:
+                    results = future.result(timeout=10)
+                    for hostname, host_ip in results.items():
+                        if hostname not in known_subdomains and hostname not in all_discovered:
+                            all_discovered[hostname] = host_ip
+                            self._add_discovered(hostname, host_ip)
+                except Exception as e:
+                    logger.debug(f"VHost scan error for {ip}: {e}")
+
+        logger.info(f"Virtual host scanning discovered {len(all_discovered)} new subdomains in {time.time() - start_time:.1f}s")
+
+        return self.discovered
+
+    def brute_force_vhosts(
+        self,
+        ip: str,
+        custom_wordlist: List[str] = None,
+        ports: List[int] = None
+    ) -> List[str]:
+        """
+        Brute force virtual hosts on a specific IP.
+
+        Args:
+            ip: Target IP address
+            custom_wordlist: Optional custom wordlist
+            ports: Ports to scan
+
+        Returns:
+            List of discovered hostnames
+        """
+        if ports is None:
+            ports = [443, 80]
+
+        wordlist = custom_wordlist or self.VHOST_WORDLIST
+        discovered = []
+
+        for port in ports:
+            baseline_sig, _ = self._get_baseline(ip, port)
+
+            with ThreadPoolExecutor(max_workers=self.threads) as executor:
+                futures = {}
+                for prefix in wordlist:
+                    hostname = f'{prefix}.{self.domain}'
+                    futures[executor.submit(
+                        self._check_vhost, ip, hostname, port, baseline_sig
+                    )] = hostname
+
+                for future in as_completed(futures, timeout=120):
+                    try:
+                        result = future.result()
+                        if result:
+                            hostname, _ = result
+                            if hostname not in discovered:
+                                discovered.append(hostname)
+                    except:
+                        pass
+
+        return discovered
diff --git a/src/usfx/modules/zone_transfer.py b/src/usfx/modules/zone_transfer.py
@@ -0,0 +1,182 @@
+"""
+DNS Zone Transfer (AXFR) Module
+
+Attempts to retrieve full DNS zone data from authoritative nameservers.
+About 5-15% of domains still allow zone transfers.
+"""
+
+import logging
+from typing import Dict, List, Optional, Set, TYPE_CHECKING
+
+import dns.query
+import dns.rdataclass
+import dns.rdatatype
+import dns.zone
+
+from .base import BaseModule
+
+if TYPE_CHECKING:
+    from ..utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class ZoneTransfer(BaseModule):
+    """DNS Zone Transfer (AXFR) enumeration"""
+
+    MODULE_NAME = "zone_transfer"
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        timeout: float = 10.0
+    ):
+        """
+        Initialize zone transfer module.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            timeout: Timeout for zone transfer operations
+        """
+        super().__init__(domain, resolver, timeout)
+        self.nameservers: List[str] = []
+
+    def _get_nameservers(self) -> List[str]:
+        """
+        Get authoritative nameservers for the domain.
+
+        Returns:
+            List of nameserver hostnames
+        """
+        if self.nameservers:
+            return self.nameservers
+
+        self.nameservers = self.resolver.resolve_ns(self.domain)
+
+        if self.nameservers:
+            logger.info(f"Found {len(self.nameservers)} nameservers for {self.domain}")
+
+        return self.nameservers
+
+    def _resolve_ns_to_ip(self, ns_hostname: str) -> Optional[str]:
+        """
+        Resolve nameserver hostname to IP address.
+
+        Args:
+            ns_hostname: Nameserver hostname
+
+        Returns:
+            IP address or None
+        """
+        return self.resolver.resolve_a(ns_hostname)
+
+    def _attempt_axfr(self, ns_ip: str, ns_hostname: str) -> Set[str]:
+        """
+        Attempt zone transfer from a specific nameserver.
+
+        Args:
+            ns_ip: Nameserver IP address
+            ns_hostname: Nameserver hostname for logging
+
+        Returns:
+            Set of discovered subdomains
+        """
+        discovered = set()
+
+        try:
+            logger.debug(f"Attempting AXFR from {ns_hostname} ({ns_ip})")
+
+            zone = dns.zone.from_xfr(
+                dns.query.xfr(ns_ip, self.domain, timeout=self.timeout)
+            )
+
+            for name, node in zone.nodes.items():
+                subdomain = str(name)
+                if subdomain == '@':
+                    continue
+
+                fqdn = f"{subdomain}.{self.domain}"
+
+                # Get A record if available
+                ip = None
+                try:
+                    rdataset = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
+                    if rdataset:
+                        ip = str(list(rdataset)[0])
+                except KeyError:
+                    pass
+
+                discovered.add(fqdn)
+                self._add_discovered(fqdn, ip)
+
+            logger.info(f"Zone transfer SUCCESS from {ns_hostname}: {len(discovered)} records")
+            return discovered
+
+        except dns.exception.FormError:
+            logger.debug(f"Zone transfer REFUSED by {ns_hostname}")
+        except dns.query.TransferError as e:
+            logger.debug(f"Zone transfer FAILED from {ns_hostname}: {e}")
+        except dns.exception.Timeout:
+            logger.debug(f"Zone transfer TIMEOUT from {ns_hostname}")
+        except Exception as e:
+            logger.debug(f"Zone transfer ERROR from {ns_hostname}: {e}")
+
+        return discovered
+
+    def enumerate(self) -> Dict[str, Optional[str]]:
+        """
+        Attempt zone transfer from all nameservers.
+
+        Returns:
+            Dict of {subdomain: ip_address}
+        """
+        logger.info(f"Attempting zone transfer for {self.domain}")
+
+        nameservers = self._get_nameservers()
+        if not nameservers:
+            logger.warning(f"No nameservers found for {self.domain}")
+            return {}
+
+        all_discovered = set()
+
+        for ns in nameservers:
+            ns_ip = self._resolve_ns_to_ip(ns)
+            if not ns_ip:
+                continue
+
+            discovered = self._attempt_axfr(ns_ip, ns)
+            all_discovered.update(discovered)
+
+            if discovered:
+                # Zone transfer succeeded, we have complete data
+                logger.info(f"Zone transfer succeeded with {len(discovered)} records")
+                break
+
+        if not all_discovered:
+            logger.info(f"Zone transfer not allowed for {self.domain}")
+
+        return self.discovered
+
+    def is_available(self) -> bool:
+        """
+        Quick check if zone transfer might be available.
+
+        Returns:
+            True if zone transfer appears to be allowed
+        """
+        nameservers = self._get_nameservers()
+        if not nameservers:
+            return False
+
+        for ns in nameservers[:2]:  # Only check first 2 NS
+            ns_ip = self._resolve_ns_to_ip(ns)
+            if not ns_ip:
+                continue
+
+            discovered = self._attempt_axfr(ns_ip, ns)
+            if discovered:
+                return True
+
+        return False
diff --git a/src/usfx/orchestrator.py b/src/usfx/orchestrator.py
@@ -0,0 +1,525 @@
+"""
+Subdomain Enumeration Orchestrator
+
+Manages the execution order of enumeration modules and aggregates results.
+Uses parallel execution within phases for maximum speed.
+
+This is a simplified orchestrator for internal networks that:
+- Does NOT use external APIs
+- Does NOT store results in a database
+- Keeps all results in memory
+"""
+
+import logging
+import threading
+import time
+from concurrent.futures import Future, ThreadPoolExecutor, as_completed
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import Callable, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
+
+from .utils.validator import SubdomainValidator
+from .utils.wildcard import WildcardDetector
+
+if TYPE_CHECKING:
+    from .config import ScanConfig
+    from .utils.dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class Phase(Enum):
+    """Enumeration phases"""
+    QUICK_DISCOVERY = "quick_discovery"
+    DNS_ENUMERATION = "dns_enumeration"
+    EXTENSION = "extension"
+    VALIDATION = "validation"
+
+
+@dataclass
+class ModuleResult:
+    """Result from a single module"""
+    module_name: str
+    found_count: int
+    subdomains: Dict[str, Optional[str]]
+    duration_seconds: float
+    error: Optional[str] = None
+
+
+@dataclass
+class PhaseResult:
+    """Result from a phase"""
+    phase: Phase
+    modules: List[ModuleResult]
+    total_found: int
+    duration_seconds: float
+
+
+@dataclass
+class OrchestrationState:
+    """Current state of the orchestration"""
+    domain: str
+    current_phase: Optional[Phase] = None
+    current_module: Optional[str] = None
+    progress: int = 0
+    total_found: int = 0
+    discovered: Dict[str, dict] = field(default_factory=dict)
+    phase_results: List[PhaseResult] = field(default_factory=list)
+    started_at: float = field(default_factory=time.time)
+    is_cancelled: bool = False
+
+
+class SubdomainOrchestrator:
+    """Orchestrates subdomain enumeration modules"""
+
+    # Phase weight for overall progress calculation
+    PHASE_WEIGHTS = {
+        Phase.QUICK_DISCOVERY: (0, 15),      # 0-15%
+        Phase.DNS_ENUMERATION: (15, 60),     # 15-60%
+        Phase.EXTENSION: (60, 90),           # 60-90%
+        Phase.VALIDATION: (90, 100),         # 90-100%
+    }
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None,
+        config: Optional['ScanConfig'] = None
+    ):
+        """
+        Initialize orchestrator.
+
+        Args:
+            domain: Target domain
+            resolver: Custom DNS resolver
+            config: Scan configuration
+        """
+        self.domain = domain.lower().strip('.')
+        self.state = OrchestrationState(domain=self.domain)
+        self.config = config
+
+        # Create resolver
+        if resolver:
+            self.resolver = resolver
+        else:
+            from .utils.dns_resolver import InternalDNSResolver
+            dns_servers = config.dns_servers if config else None
+            timeout = config.timeout if config else 3.0
+            self.resolver = InternalDNSResolver(dns_servers=dns_servers, timeout=timeout)
+
+        self.validator = SubdomainValidator(resolver=self.resolver)
+        self.wildcard_detector = WildcardDetector(domain, self.resolver)
+
+        # Progress callback
+        self.progress_callback: Optional[Callable[[OrchestrationState], None]] = None
+
+        # Lock for thread-safe result collection
+        self._results_lock = threading.Lock()
+
+    def set_progress_callback(self, callback: Callable[[OrchestrationState], None]):
+        """Set callback for progress updates"""
+        self.progress_callback = callback
+
+    def _update_progress(self, phase: Phase, module: str, phase_progress: int):
+        """Update and broadcast progress"""
+        self.state.current_phase = phase
+        self.state.current_module = module
+
+        # Calculate overall progress based on phase and phase_progress
+        phase_start, phase_end = self.PHASE_WEIGHTS.get(phase, (0, 100))
+        phase_range = phase_end - phase_start
+        overall_progress = phase_start + (phase_progress * phase_range // 100)
+
+        self.state.progress = overall_progress
+
+        if self.progress_callback:
+            self.progress_callback(self.state)
+
+    def _add_results(self, results: Dict[str, Optional[str]], module_name: str):
+        """Add results from a module (thread-safe)"""
+        with self._results_lock:
+            for subdomain, ip in results.items():
+                if subdomain not in self.state.discovered:
+                    self.state.discovered[subdomain] = {
+                        'ip': ip,
+                        'discovered_by': module_name,
+                        'is_active': ip is not None
+                    }
+                    self.state.total_found += 1
+                elif ip and not self.state.discovered[subdomain].get('ip'):
+                    # Update IP if we have one now
+                    self.state.discovered[subdomain]['ip'] = ip
+                    self.state.discovered[subdomain]['is_active'] = True
+
+    def _run_module_task(
+        self,
+        module_factory: Callable,
+        module_name: str,
+        needs_known_subs: bool = False,
+        **kwargs
+    ) -> ModuleResult:
+        """Run a single module as a task"""
+        start_time = time.time()
+
+        try:
+            module = module_factory()
+
+            # Get current state snapshot for modules that need it
+            if needs_known_subs:
+                with self._results_lock:
+                    known_subs = {k: v.get('ip') for k, v in self.state.discovered.items()}
+
+                if module_name == "tls_analyzer":
+                    results = module.enumerate(known_hosts=known_subs)
+                elif module_name == "permutation":
+                    def resolve(subdomain):
+                        return self.resolver.resolve_a(subdomain)
+                    results = module.enumerate(known_subs, resolver_func=resolve)
+                else:
+                    results = module.enumerate(known_subs)
+            else:
+                results = module.enumerate()
+
+            duration = time.time() - start_time
+            self._add_results(results, module_name)
+
+            logger.info(f"Module {module_name} found {len(results)} subdomains in {duration:.1f}s")
+
+            return ModuleResult(
+                module_name=module_name,
+                found_count=len(results),
+                subdomains=results,
+                duration_seconds=duration
+            )
+
+        except Exception as e:
+            logger.error(f"Error in module {module_name}: {e}")
+            return ModuleResult(
+                module_name=module_name,
+                found_count=0,
+                subdomains={},
+                duration_seconds=time.time() - start_time,
+                error=str(e)
+            )
+
+    def _run_modules_parallel(
+        self,
+        phase: Phase,
+        module_tasks: List[Tuple[Callable, str, dict]],
+        max_workers: int = 10
+    ) -> List[ModuleResult]:
+        """Run multiple modules in parallel"""
+        results = []
+        completed_count = 0
+        total_modules = len(module_tasks)
+
+        module_names = [name for _, name, _ in module_tasks]
+        self._update_progress(phase, f"parallel: {', '.join(module_names)}", 0)
+
+        with ThreadPoolExecutor(max_workers=max_workers) as executor:
+            futures: Dict[Future, str] = {}
+            for module_factory, module_name, kwargs in module_tasks:
+                future = executor.submit(
+                    self._run_module_task,
+                    module_factory,
+                    module_name,
+                    **kwargs
+                )
+                futures[future] = module_name
+
+            for future in as_completed(futures):
+                module_name = futures[future]
+                try:
+                    result = future.result(timeout=300)
+                    results.append(result)
+                except Exception as e:
+                    logger.error(f"Module {module_name} failed: {e}")
+                    results.append(ModuleResult(
+                        module_name=module_name,
+                        found_count=0,
+                        subdomains={},
+                        duration_seconds=0,
+                        error=str(e)
+                    ))
+
+                completed_count += 1
+                progress = int(completed_count * 100 / total_modules)
+                self._update_progress(phase, module_name, progress)
+
+                if self.state.is_cancelled:
+                    for f in futures:
+                        f.cancel()
+                    break
+
+        return results
+
+    def _get_enabled_modules(self) -> Set[str]:
+        """Get set of enabled module names"""
+        if self.config and self.config.modules:
+            return self.config.modules
+        return {
+            "zone_transfer", "dns_records", "dns_bruteforce", "dnssec_walker",
+            "reverse_dns", "cname_chaser", "permutation", "recursive_enum",
+            "vhost_scanner", "tls_analyzer"
+        }
+
+    def run_phase1_quick_discovery(self) -> PhaseResult:
+        """Phase 1: Quick Discovery - Zone Transfer and DNS Records"""
+        from .modules.zone_transfer import ZoneTransfer
+        from .modules.dns_records import DNSRecordMiner
+
+        phase_start = time.time()
+        results = []
+        enabled = self._get_enabled_modules()
+
+        # Get config values
+        threads = self.config.threads if self.config else 30
+
+        module_tasks = []
+
+        if "zone_transfer" in enabled:
+            module_tasks.append((
+                lambda: ZoneTransfer(self.domain, self.resolver),
+                "zone_transfer",
+                {}
+            ))
+
+        if "dns_records" in enabled:
+            module_tasks.append((
+                lambda: DNSRecordMiner(self.domain, self.resolver),
+                "dns_records",
+                {}
+            ))
+
+        if module_tasks:
+            results = self._run_modules_parallel(Phase.QUICK_DISCOVERY, module_tasks, max_workers=2)
+
+        self._update_progress(Phase.QUICK_DISCOVERY, "complete", 100)
+
+        return PhaseResult(
+            phase=Phase.QUICK_DISCOVERY,
+            modules=results,
+            total_found=sum(r.found_count for r in results),
+            duration_seconds=time.time() - phase_start
+        )
+
+    def run_phase2_dns_enumeration(self) -> PhaseResult:
+        """Phase 2: DNS Enumeration - Brute force and DNSSEC"""
+        from .modules.dns_bruteforce import DNSBruteforcer
+        from .modules.dnssec_walker import DNSSECWalker
+
+        phase_start = time.time()
+        results = []
+        enabled = self._get_enabled_modules()
+
+        threads = self.config.threads if self.config else 30
+        wordlist_size = self.config.wordlist_size.value if self.config else 'medium'
+        wordlist_path = str(self.config.custom_wordlist) if self.config and self.config.custom_wordlist else None
+
+        module_tasks = []
+
+        if "dns_bruteforce" in enabled:
+            module_tasks.append((
+                lambda: DNSBruteforcer(
+                    self.domain, self.resolver,
+                    wordlist_path=wordlist_path,
+                    wordlist_size=wordlist_size,
+                    threads=threads
+                ),
+                "dns_bruteforce",
+                {}
+            ))
+
+        if "dnssec_walker" in enabled:
+            module_tasks.append((
+                lambda: DNSSECWalker(self.domain, self.resolver),
+                "dnssec_walker",
+                {}
+            ))
+
+        if module_tasks:
+            results = self._run_modules_parallel(Phase.DNS_ENUMERATION, module_tasks, max_workers=2)
+
+        self._update_progress(Phase.DNS_ENUMERATION, "complete", 100)
+
+        return PhaseResult(
+            phase=Phase.DNS_ENUMERATION,
+            modules=results,
+            total_found=sum(r.found_count for r in results),
+            duration_seconds=time.time() - phase_start
+        )
+
+    def run_phase3_extension(self) -> PhaseResult:
+        """Phase 3: Extension - Permutation, Reverse DNS, Recursive, VHost, TLS"""
+        from .modules.permutation import PermutationEngine
+        from .modules.reverse_dns import ReverseDNS
+        from .modules.recursive_enum import RecursiveEnumerator
+        from .modules.vhost_scanner import VHostScanner
+        from .modules.tls_analyzer import TLSAnalyzer
+        from .modules.cname_chaser import CNAMEChaser
+
+        phase_start = time.time()
+        results = []
+        enabled = self._get_enabled_modules()
+
+        threads = self.config.threads if self.config else 30
+        vhost_ips = self.config.vhost_ips if self.config else []
+        reverse_ranges = self.config.reverse_dns_ranges if self.config else []
+
+        module_tasks = []
+
+        if "permutation" in enabled:
+            module_tasks.append((
+                lambda: PermutationEngine(self.domain, self.resolver),
+                "permutation",
+                {"needs_known_subs": True}
+            ))
+
+        if "cname_chaser" in enabled:
+            module_tasks.append((
+                lambda: CNAMEChaser(self.domain, self.resolver, threads=threads),
+                "cname_chaser",
+                {"needs_known_subs": True}
+            ))
+
+        if "recursive_enum" in enabled:
+            module_tasks.append((
+                lambda: RecursiveEnumerator(self.domain, self.resolver, threads=threads),
+                "recursive_enum",
+                {"needs_known_subs": True}
+            ))
+
+        if "tls_analyzer" in enabled:
+            module_tasks.append((
+                lambda: TLSAnalyzer(self.domain, self.resolver, threads=threads),
+                "tls_analyzer",
+                {"needs_known_subs": True}
+            ))
+
+        if module_tasks:
+            results = self._run_modules_parallel(Phase.EXTENSION, module_tasks, max_workers=4)
+
+        # Run reverse DNS separately (may need special IP ranges)
+        if "reverse_dns" in enabled:
+            self._update_progress(Phase.EXTENSION, "reverse_dns", 80)
+            reverse_module = ReverseDNS(self.domain, self.resolver, threads=threads)
+            with self._results_lock:
+                known_ips = {v.get('ip') for v in self.state.discovered.values() if v.get('ip')}
+            reverse_results = reverse_module.enumerate(
+                additional_ips=known_ips,
+                ip_ranges=reverse_ranges if reverse_ranges else None
+            )
+            self._add_results(reverse_results, "reverse_dns")
+            results.append(ModuleResult(
+                module_name="reverse_dns",
+                found_count=len(reverse_results),
+                subdomains=reverse_results,
+                duration_seconds=0
+            ))
+
+        # Run vhost scanner separately (may need special IPs)
+        if "vhost_scanner" in enabled and vhost_ips:
+            self._update_progress(Phase.EXTENSION, "vhost_scanner", 90)
+            vhost_module = VHostScanner(self.domain, self.resolver, threads=threads)
+            with self._results_lock:
+                known_subs = {k: v.get('ip') for k, v in self.state.discovered.items()}
+            vhost_results = vhost_module.enumerate(known_subs, vhost_ips=vhost_ips)
+            self._add_results(vhost_results, "vhost_scanner")
+            results.append(ModuleResult(
+                module_name="vhost_scanner",
+                found_count=len(vhost_results),
+                subdomains=vhost_results,
+                duration_seconds=0
+            ))
+
+        self._update_progress(Phase.EXTENSION, "complete", 100)
+
+        return PhaseResult(
+            phase=Phase.EXTENSION,
+            modules=results,
+            total_found=sum(r.found_count for r in results),
+            duration_seconds=time.time() - phase_start
+        )
+
+    def run_phase4_validation(self) -> PhaseResult:
+        """Phase 4: Validation - DNS resolution verification and wildcard filtering"""
+        phase_start = time.time()
+
+        self._update_progress(Phase.VALIDATION, "validating", 0)
+
+        # Detect wildcards
+        has_wildcard, wildcard_ips = self.wildcard_detector.detect()
+
+        # Validate all discovered subdomains
+        subdomains_to_validate = set(self.state.discovered.keys())
+        threads = self.config.threads if self.config else 30
+        validated = self.validator.validate_batch(subdomains_to_validate, threads=threads)
+
+        # Update state with validation results
+        removed_count = 0
+        for subdomain, result in validated.items():
+            if subdomain in self.state.discovered:
+                if result['ip']:
+                    # Check wildcard
+                    if has_wildcard and result['ip'] in wildcard_ips:
+                        del self.state.discovered[subdomain]
+                        removed_count += 1
+                    else:
+                        self.state.discovered[subdomain]['ip'] = result['ip']
+                        self.state.discovered[subdomain]['is_active'] = True
+                else:
+                    self.state.discovered[subdomain]['is_active'] = False
+
+        self.state.total_found = len(self.state.discovered)
+
+        self._update_progress(Phase.VALIDATION, "complete", 100)
+
+        if removed_count > 0:
+            logger.info(f"Filtered {removed_count} wildcard results")
+
+        logger.info(f"Validation complete: {self.state.total_found} valid subdomains")
+
+        return PhaseResult(
+            phase=Phase.VALIDATION,
+            modules=[ModuleResult(
+                module_name="validation",
+                found_count=self.state.total_found,
+                subdomains={},
+                duration_seconds=time.time() - phase_start
+            )],
+            total_found=self.state.total_found,
+            duration_seconds=time.time() - phase_start
+        )
+
+    def cancel(self):
+        """Cancel the orchestration"""
+        self.state.is_cancelled = True
+
+    def run_all(self) -> OrchestrationState:
+        """Run all phases"""
+        logger.info(f"Starting subdomain enumeration for {self.domain}")
+
+        # Phase 1: Quick Discovery
+        result = self.run_phase1_quick_discovery()
+        self.state.phase_results.append(result)
+        if self.state.is_cancelled:
+            return self.state
+
+        # Phase 2: DNS Enumeration
+        result = self.run_phase2_dns_enumeration()
+        self.state.phase_results.append(result)
+        if self.state.is_cancelled:
+            return self.state
+
+        # Phase 3: Extension
+        result = self.run_phase3_extension()
+        self.state.phase_results.append(result)
+        if self.state.is_cancelled:
+            return self.state
+
+        # Phase 4: Validation
+        result = self.run_phase4_validation()
+        self.state.phase_results.append(result)
+
+        logger.info(f"Enumeration complete: {self.state.total_found} subdomains found")
+        return self.state
diff --git a/src/usfx/utils/__init__.py b/src/usfx/utils/__init__.py
@@ -0,0 +1,25 @@
+"""
+USFX Utilities
+
+Core utilities for subdomain enumeration:
+- dns_resolver: Custom DNS resolver supporting internal DNS servers
+- wildcard: Wildcard DNS detection
+- validator: Subdomain validation
+- wordlist: Wordlist management with bundled lists
+- output: Result output formatters (JSON/CSV/TXT)
+"""
+
+from .dns_resolver import InternalDNSResolver
+from .wildcard import WildcardDetector
+from .validator import SubdomainValidator
+from .wordlist import WordlistManager, get_bundled_wordlist
+from .output import OutputFormatter
+
+__all__ = [
+    "InternalDNSResolver",
+    "WildcardDetector",
+    "SubdomainValidator",
+    "WordlistManager",
+    "get_bundled_wordlist",
+    "OutputFormatter",
+]
diff --git a/src/usfx/utils/dns_resolver.py b/src/usfx/utils/dns_resolver.py
@@ -0,0 +1,364 @@
+"""
+Custom DNS Resolver for Internal Networks
+
+The core utility that enables subdomain enumeration against internal DNS servers.
+Supports multiple DNS servers, custom timeouts, and provides a unified interface
+for all enumeration modules.
+"""
+
+import logging
+from typing import List, Optional, Set, Tuple
+
+import dns.resolver
+import dns.exception
+import dns.rdatatype
+import dns.reversename
+
+logger = logging.getLogger(__name__)
+
+
+class InternalDNSResolver:
+    """
+    DNS resolver that supports custom internal DNS servers.
+
+    This is the key component that differentiates internal-usf from
+    internet-based subdomain finders. By specifying internal DNS servers,
+    you can enumerate subdomains that only exist on your internal network.
+
+    Usage:
+        # Use custom internal DNS servers
+        resolver = InternalDNSResolver(dns_servers=['192.168.1.1', '10.0.0.1'])
+
+        # Use system default DNS
+        resolver = InternalDNSResolver()
+
+        # Resolve a subdomain
+        ip = resolver.resolve_a('api.corp.local')
+    """
+
+    def __init__(
+        self,
+        dns_servers: Optional[List[str]] = None,
+        timeout: float = 3.0,
+        lifetime: float = 6.0
+    ):
+        """
+        Initialize the DNS resolver.
+
+        Args:
+            dns_servers: List of DNS server IPs. If None, uses system defaults.
+            timeout: Timeout for individual DNS queries in seconds.
+            lifetime: Total time allowed for a query (including retries).
+        """
+        self.resolver = dns.resolver.Resolver()
+        self.timeout = timeout
+        self.lifetime = lifetime
+
+        # Configure resolver
+        self.resolver.timeout = timeout
+        self.resolver.lifetime = lifetime
+
+        # Set custom DNS servers if provided
+        if dns_servers:
+            self.dns_servers = dns_servers
+            self.resolver.nameservers = dns_servers
+            logger.info(f"Using custom DNS servers: {', '.join(dns_servers)}")
+        else:
+            # Use system default DNS servers
+            self.dns_servers = self.resolver.nameservers.copy()
+            logger.info(f"Using system DNS servers: {', '.join(self.dns_servers)}")
+
+    def resolve_a(self, hostname: str) -> Optional[str]:
+        """
+        Resolve hostname to IPv4 address (A record).
+
+        Args:
+            hostname: Fully qualified domain name to resolve.
+
+        Returns:
+            Primary IP address or None if not found.
+        """
+        try:
+            answers = self.resolver.resolve(hostname, 'A')
+            return str(answers[0])
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
+            return None
+        except dns.resolver.NoNameservers:
+            logger.debug(f"No nameservers available for {hostname}")
+            return None
+        except dns.exception.Timeout:
+            logger.debug(f"Timeout resolving {hostname}")
+            return None
+        except Exception as e:
+            logger.debug(f"Error resolving {hostname}: {e}")
+            return None
+
+    def resolve_a_all(self, hostname: str) -> Set[str]:
+        """
+        Resolve hostname to all IPv4 addresses.
+
+        Args:
+            hostname: Fully qualified domain name to resolve.
+
+        Returns:
+            Set of all IP addresses, empty set if not found.
+        """
+        try:
+            answers = self.resolver.resolve(hostname, 'A')
+            return {str(rdata) for rdata in answers}
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return set()
+        except Exception as e:
+            logger.debug(f"Error resolving {hostname}: {e}")
+            return set()
+
+    def resolve_aaaa(self, hostname: str) -> Optional[str]:
+        """
+        Resolve hostname to IPv6 address (AAAA record).
+
+        Args:
+            hostname: Fully qualified domain name to resolve.
+
+        Returns:
+            Primary IPv6 address or None if not found.
+        """
+        try:
+            answers = self.resolver.resolve(hostname, 'AAAA')
+            return str(answers[0])
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return None
+        except Exception as e:
+            logger.debug(f"Error resolving AAAA for {hostname}: {e}")
+            return None
+
+    def resolve_cname(self, hostname: str) -> Optional[str]:
+        """
+        Resolve CNAME record for hostname.
+
+        Args:
+            hostname: Fully qualified domain name.
+
+        Returns:
+            CNAME target or None if not found.
+        """
+        try:
+            answers = self.resolver.resolve(hostname, 'CNAME')
+            return str(answers[0].target).rstrip('.')
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return None
+        except Exception as e:
+            logger.debug(f"Error resolving CNAME for {hostname}: {e}")
+            return None
+
+    def resolve_mx(self, domain: str) -> List[Tuple[int, str]]:
+        """
+        Resolve MX records for domain.
+
+        Args:
+            domain: Domain to query.
+
+        Returns:
+            List of (priority, hostname) tuples.
+        """
+        try:
+            answers = self.resolver.resolve(domain, 'MX')
+            return [(rdata.preference, str(rdata.exchange).rstrip('.'))
+                    for rdata in answers]
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return []
+        except Exception as e:
+            logger.debug(f"Error resolving MX for {domain}: {e}")
+            return []
+
+    def resolve_ns(self, domain: str) -> List[str]:
+        """
+        Resolve NS records for domain.
+
+        Args:
+            domain: Domain to query.
+
+        Returns:
+            List of nameserver hostnames.
+        """
+        try:
+            answers = self.resolver.resolve(domain, 'NS')
+            return [str(rdata).rstrip('.') for rdata in answers]
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return []
+        except Exception as e:
+            logger.debug(f"Error resolving NS for {domain}: {e}")
+            return []
+
+    def resolve_txt(self, domain: str) -> List[str]:
+        """
+        Resolve TXT records for domain.
+
+        Args:
+            domain: Domain to query.
+
+        Returns:
+            List of TXT record values.
+        """
+        try:
+            answers = self.resolver.resolve(domain, 'TXT')
+            return [str(rdata).strip('"') for rdata in answers]
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return []
+        except Exception as e:
+            logger.debug(f"Error resolving TXT for {domain}: {e}")
+            return []
+
+    def resolve_srv(self, name: str) -> List[Tuple[int, int, int, str]]:
+        """
+        Resolve SRV records.
+
+        Args:
+            name: SRV record name (e.g., _ldap._tcp.domain.local)
+
+        Returns:
+            List of (priority, weight, port, target) tuples.
+        """
+        try:
+            answers = self.resolver.resolve(name, 'SRV')
+            return [(rdata.priority, rdata.weight, rdata.port,
+                     str(rdata.target).rstrip('.'))
+                    for rdata in answers]
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return []
+        except Exception as e:
+            logger.debug(f"Error resolving SRV for {name}: {e}")
+            return []
+
+    def resolve_soa(self, domain: str) -> Optional[dict]:
+        """
+        Resolve SOA record for domain.
+
+        Args:
+            domain: Domain to query.
+
+        Returns:
+            Dict with SOA fields or None.
+        """
+        try:
+            answers = self.resolver.resolve(domain, 'SOA')
+            rdata = answers[0]
+            return {
+                'mname': str(rdata.mname).rstrip('.'),
+                'rname': str(rdata.rname).rstrip('.'),
+                'serial': rdata.serial,
+                'refresh': rdata.refresh,
+                'retry': rdata.retry,
+                'expire': rdata.expire,
+                'minimum': rdata.minimum,
+            }
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return None
+        except Exception as e:
+            logger.debug(f"Error resolving SOA for {domain}: {e}")
+            return None
+
+    def resolve_ptr(self, ip: str) -> Optional[str]:
+        """
+        Resolve PTR (reverse DNS) record for IP address.
+
+        Args:
+            ip: IP address to lookup.
+
+        Returns:
+            Hostname or None.
+        """
+        try:
+            reverse_name = dns.reversename.from_address(ip)
+            answers = self.resolver.resolve(reverse_name, 'PTR')
+            return str(answers[0]).rstrip('.').lower()
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return None
+        except Exception as e:
+            logger.debug(f"Error resolving PTR for {ip}: {e}")
+            return None
+
+    def resolve_any(self, hostname: str, record_type: str) -> List[str]:
+        """
+        Resolve any DNS record type.
+
+        Args:
+            hostname: Hostname to query.
+            record_type: DNS record type (A, AAAA, MX, NS, TXT, etc.)
+
+        Returns:
+            List of string representations of the records.
+        """
+        try:
+            answers = self.resolver.resolve(hostname, record_type)
+            return [str(rdata) for rdata in answers]
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer,
+                dns.resolver.NoNameservers, dns.exception.Timeout):
+            return []
+        except Exception as e:
+            logger.debug(f"Error resolving {record_type} for {hostname}: {e}")
+            return []
+
+    def check_exists(self, hostname: str) -> bool:
+        """
+        Check if a hostname exists (has any DNS record).
+
+        Args:
+            hostname: Hostname to check.
+
+        Returns:
+            True if hostname exists.
+        """
+        # Try A record first (most common)
+        if self.resolve_a(hostname):
+            return True
+
+        # Try CNAME
+        if self.resolve_cname(hostname):
+            return True
+
+        # Try AAAA
+        if self.resolve_aaaa(hostname):
+            return True
+
+        return False
+
+    def get_native_resolver(self) -> dns.resolver.Resolver:
+        """
+        Get the underlying dnspython resolver for advanced operations.
+
+        Returns:
+            The configured dns.resolver.Resolver instance.
+        """
+        return self.resolver
+
+    def create_child_resolver(
+        self,
+        timeout: Optional[float] = None,
+        lifetime: Optional[float] = None
+    ) -> 'InternalDNSResolver':
+        """
+        Create a child resolver with the same DNS servers but different timeouts.
+
+        Useful for modules that need different timeout configurations.
+
+        Args:
+            timeout: New timeout value (uses parent's if None)
+            lifetime: New lifetime value (uses parent's if None)
+
+        Returns:
+            New InternalDNSResolver instance.
+        """
+        return InternalDNSResolver(
+            dns_servers=self.dns_servers,
+            timeout=timeout if timeout is not None else self.timeout,
+            lifetime=lifetime if lifetime is not None else self.lifetime
+        )
diff --git a/src/usfx/utils/output.py b/src/usfx/utils/output.py
@@ -0,0 +1,371 @@
+"""
+Output Formatter
+
+Handles formatting and writing scan results to various formats:
+JSON, CSV, and TXT.
+"""
+
+import csv
+import json
+import logging
+from datetime import datetime
+from pathlib import Path
+from typing import List, Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from ..config import ScanResult, SubdomainResult
+
+logger = logging.getLogger(__name__)
+
+
+class OutputFormatter:
+    """Formats and outputs scan results in various formats"""
+
+    @staticmethod
+    def to_json(result: 'ScanResult', pretty: bool = True) -> str:
+        """
+        Convert scan result to JSON string.
+
+        Args:
+            result: ScanResult object
+            pretty: Whether to pretty-print the JSON
+
+        Returns:
+            JSON string
+        """
+        data = {
+            'domain': result.domain,
+            'scan_time': datetime.now().isoformat(),
+            'total_found': result.total_found,
+            'duration_seconds': round(result.duration_seconds, 2),
+            'dns_servers': result.dns_servers_used,
+            'subdomains': [
+                {
+                    'subdomain': s.subdomain,
+                    'ip': s.ip,
+                    'discovered_by': s.discovered_by,
+                    'is_active': s.is_active,
+                }
+                for s in result.subdomains
+            ],
+            'modules': [
+                {
+                    'name': m.module_name,
+                    'found': m.found_count,
+                    'duration': round(m.duration_seconds, 2),
+                    'error': m.error,
+                }
+                for m in result.module_results
+            ]
+        }
+
+        if pretty:
+            return json.dumps(data, indent=2, ensure_ascii=False)
+        return json.dumps(data, ensure_ascii=False)
+
+    @staticmethod
+    def to_csv(result: 'ScanResult') -> str:
+        """
+        Convert scan result to CSV string.
+
+        Args:
+            result: ScanResult object
+
+        Returns:
+            CSV string
+        """
+        import io
+        output = io.StringIO()
+        writer = csv.writer(output)
+
+        # Header
+        writer.writerow(['subdomain', 'ip', 'discovered_by', 'is_active'])
+
+        # Data
+        for s in result.subdomains:
+            writer.writerow([
+                s.subdomain,
+                s.ip or '',
+                s.discovered_by,
+                'yes' if s.is_active else 'no'
+            ])
+
+        return output.getvalue()
+
+    @staticmethod
+    def to_txt(result: 'ScanResult', include_ips: bool = True) -> str:
+        """
+        Convert scan result to plain text (one subdomain per line).
+
+        Args:
+            result: ScanResult object
+            include_ips: Whether to include IP addresses
+
+        Returns:
+            Plain text string
+        """
+        lines = []
+
+        for s in result.subdomains:
+            if include_ips and s.ip:
+                lines.append(f"{s.subdomain}\t{s.ip}")
+            else:
+                lines.append(s.subdomain)
+
+        return '\n'.join(lines)
+
+    @staticmethod
+    def write_file(
+        result: 'ScanResult',
+        output_path: Path,
+        format: str = 'json'
+    ) -> None:
+        """
+        Write scan results to file.
+
+        Args:
+            result: ScanResult object
+            output_path: Path to output file
+            format: Output format ('json', 'csv', 'txt')
+        """
+        format = format.lower()
+
+        if format == 'json':
+            content = OutputFormatter.to_json(result)
+        elif format == 'csv':
+            content = OutputFormatter.to_csv(result)
+        elif format == 'txt':
+            content = OutputFormatter.to_txt(result)
+        else:
+            raise ValueError(f"Unknown output format: {format}")
+
+        # Ensure parent directory exists
+        output_path.parent.mkdir(parents=True, exist_ok=True)
+
+        with open(output_path, 'w', encoding='utf-8') as f:
+            f.write(content)
+
+        logger.info(f"Results written to {output_path}")
+
+    @staticmethod
+    def format_summary(result: 'ScanResult') -> str:
+        """
+        Format a human-readable summary of scan results.
+
+        Args:
+            result: ScanResult object
+
+        Returns:
+            Formatted summary string
+        """
+        lines = [
+            "",
+            f"{'=' * 50}",
+            f"Scan Complete: {result.domain}",
+            f"{'=' * 50}",
+            f"Total subdomains found: {result.total_found}",
+            f"Duration: {result.duration_seconds:.1f}s",
+            f"DNS servers: {', '.join(result.dns_servers_used) or 'system default'}",
+            "",
+            "Module Results:",
+        ]
+
+        for module in result.module_results:
+            status = f"[ERROR: {module.error}]" if module.error else ""
+            lines.append(
+                f"  - {module.module_name}: {module.found_count} found "
+                f"({module.duration_seconds:.1f}s) {status}"
+            )
+
+        if result.subdomains:
+            lines.extend([
+                "",
+                "Discovered Subdomains:",
+                "-" * 50,
+            ])
+
+            # Group by active status
+            active = [s for s in result.subdomains if s.is_active]
+            inactive = [s for s in result.subdomains if not s.is_active]
+
+            if active:
+                lines.append(f"\nActive ({len(active)}):")
+                for s in active[:20]:  # Limit display
+                    ip_str = f" -> {s.ip}" if s.ip else ""
+                    lines.append(f"  {s.subdomain}{ip_str}")
+                if len(active) > 20:
+                    lines.append(f"  ... and {len(active) - 20} more")
+
+            if inactive:
+                lines.append(f"\nInactive/Unresolved ({len(inactive)}):")
+                for s in inactive[:10]:
+                    lines.append(f"  {s.subdomain}")
+                if len(inactive) > 10:
+                    lines.append(f"  ... and {len(inactive) - 10} more")
+
+        return '\n'.join(lines)
+
+
+class ConsoleOutput:
+    """
+    Console output helper with optional Rich formatting.
+    Falls back to plain text if Rich is not available.
+    """
+
+    def __init__(self, use_rich: bool = True, quiet: bool = False):
+        """
+        Initialize console output.
+
+        Args:
+            use_rich: Whether to use Rich for formatting
+            quiet: Whether to suppress non-essential output
+        """
+        self.quiet = quiet
+        self.use_rich = use_rich
+
+        if use_rich:
+            try:
+                from rich.console import Console
+                from rich.table import Table
+                from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
+                self.console = Console()
+                self._rich_available = True
+            except ImportError:
+                self._rich_available = False
+                self.use_rich = False
+        else:
+            self._rich_available = False
+
+    def print_banner(self, version: str) -> None:
+        """Print application banner"""
+        if self.quiet:
+            return
+
+        banner = f"""
+ USFX v{version}
+ Ultimate Subdomain Finder X for Internal Networks
+"""
+        if self.use_rich:
+            self.console.print(banner, style="bold blue")
+        else:
+            print(banner)
+
+    def print_config(self, domain: str, dns_servers: List[str], modules: List[str]) -> None:
+        """Print scan configuration"""
+        if self.quiet:
+            return
+
+        if self.use_rich:
+            self.console.print(f"\n[bold]Target:[/bold] {domain}")
+            if dns_servers:
+                self.console.print(f"[bold]DNS Servers:[/bold] {', '.join(dns_servers)}")
+            else:
+                self.console.print("[bold]DNS Servers:[/bold] system default")
+            self.console.print(f"[bold]Modules:[/bold] {', '.join(modules)}")
+            self.console.print()
+        else:
+            print(f"\nTarget: {domain}")
+            if dns_servers:
+                print(f"DNS Servers: {', '.join(dns_servers)}")
+            else:
+                print("DNS Servers: system default")
+            print(f"Modules: {', '.join(modules)}")
+            print()
+
+    def print_phase(self, phase: str) -> None:
+        """Print phase header"""
+        if self.quiet:
+            return
+
+        if self.use_rich:
+            self.console.print(f"\n[bold cyan][{phase}][/bold cyan]")
+        else:
+            print(f"\n[{phase}]")
+
+    def print_module_result(self, module: str, count: int, duration: float) -> None:
+        """Print module result"""
+        if self.quiet:
+            return
+
+        if self.use_rich:
+            self.console.print(f"  └─ {module}: [green]{count}[/green] found ({duration:.1f}s)")
+        else:
+            print(f"  └─ {module}: {count} found ({duration:.1f}s)")
+
+    def print_result_table(self, result: 'ScanResult') -> None:
+        """Print results as a table"""
+        if self.quiet or not result.subdomains:
+            return
+
+        if self.use_rich:
+            from rich.table import Table
+
+            table = Table(title=f"Results: {result.total_found} subdomains")
+            table.add_column("Subdomain", style="cyan")
+            table.add_column("IP Address", style="green")
+            table.add_column("Source", style="dim")
+
+            for s in result.subdomains[:50]:  # Limit to 50 rows
+                table.add_row(s.subdomain, s.ip or "-", s.discovered_by)
+
+            if result.total_found > 50:
+                table.add_row("...", f"({result.total_found - 50} more)", "")
+
+            self.console.print(table)
+        else:
+            print(f"\nResults: {result.total_found} subdomains")
+            print("-" * 60)
+            print(f"{'Subdomain':<35} {'IP Address':<15} Source")
+            print("-" * 60)
+
+            for s in result.subdomains[:50]:
+                print(f"{s.subdomain:<35} {(s.ip or '-'):<15} {s.discovered_by}")
+
+            if result.total_found > 50:
+                print(f"... ({result.total_found - 50} more)")
+
+    def print_summary(self, result: 'ScanResult') -> None:
+        """Print scan summary"""
+        if self.use_rich:
+            self.console.print(f"\n[bold green]Scan complete![/bold green]")
+            self.console.print(f"Found [bold]{result.total_found}[/bold] subdomains in {result.duration_seconds:.1f}s")
+        else:
+            print(f"\nScan complete!")
+            print(f"Found {result.total_found} subdomains in {result.duration_seconds:.1f}s")
+
+    def print_error(self, message: str) -> None:
+        """Print error message"""
+        if self.use_rich:
+            self.console.print(f"[bold red]Error:[/bold red] {message}")
+        else:
+            print(f"Error: {message}")
+
+    def create_progress(self):
+        """Create a progress context manager"""
+        if self.use_rich and not self.quiet:
+            from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn
+
+            return Progress(
+                SpinnerColumn(),
+                TextColumn("[bold blue]{task.description}"),
+                BarColumn(),
+                TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
+                TimeElapsedColumn(),
+                console=self.console,
+            )
+        else:
+            # Return a dummy context manager
+            class DummyProgress:
+                def __enter__(self):
+                    return self
+
+                def __exit__(self, *args):
+                    pass
+
+                def add_task(self, *args, **kwargs):
+                    return 0
+
+                def update(self, *args, **kwargs):
+                    pass
+
+            return DummyProgress()
diff --git a/src/usfx/utils/validator.py b/src/usfx/utils/validator.py
@@ -0,0 +1,210 @@
+"""
+Subdomain Validation
+
+Validates discovered subdomains by checking DNS resolution and HTTP accessibility.
+"""
+
+import logging
+import socket
+import ssl
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING
+from urllib.request import urlopen, Request
+from urllib.error import URLError, HTTPError
+
+if TYPE_CHECKING:
+    from .dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class SubdomainValidator:
+    """Validates discovered subdomains"""
+
+    def __init__(
+        self,
+        timeout: float = 3.0,
+        resolver: Optional['InternalDNSResolver'] = None
+    ):
+        """
+        Initialize validator.
+
+        Args:
+            timeout: Timeout for validation operations
+            resolver: Optional custom DNS resolver
+        """
+        self.timeout = timeout
+
+        if resolver:
+            self.resolver = resolver
+        else:
+            from .dns_resolver import InternalDNSResolver
+            self.resolver = InternalDNSResolver(timeout=timeout)
+
+    def resolve_subdomain(self, subdomain: str) -> Optional[str]:
+        """
+        Resolve a subdomain to its IP address.
+
+        Args:
+            subdomain: Full subdomain to resolve
+
+        Returns:
+            IP address or None if unresolvable
+        """
+        return self.resolver.resolve_a(subdomain)
+
+    def check_http(self, subdomain: str, https: bool = True) -> Optional[int]:
+        """
+        Check HTTP(S) accessibility of a subdomain.
+
+        Args:
+            subdomain: Full subdomain to check
+            https: Whether to use HTTPS (default True)
+
+        Returns:
+            HTTP status code or None if inaccessible
+        """
+        protocol = 'https' if https else 'http'
+        url = f"{protocol}://{subdomain}/"
+
+        try:
+            ctx = ssl.create_default_context()
+            ctx.check_hostname = False
+            ctx.verify_mode = ssl.CERT_NONE
+
+            req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
+
+            if https:
+                response = urlopen(req, timeout=self.timeout, context=ctx)
+            else:
+                response = urlopen(req, timeout=self.timeout)
+
+            return response.getcode()
+        except HTTPError as e:
+            return e.code
+        except URLError:
+            return None
+        except Exception:
+            return None
+
+    def validate_single(self, subdomain: str, check_http: bool = False) -> dict:
+        """
+        Validate a single subdomain.
+
+        Args:
+            subdomain: Subdomain to validate
+            check_http: Whether to also check HTTP accessibility
+
+        Returns:
+            Dict with ip, is_active, http_status, https_status
+        """
+        ip = self.resolve_subdomain(subdomain)
+        result = {
+            'ip': ip,
+            'is_active': ip is not None,
+            'http_status': None,
+            'https_status': None
+        }
+
+        if check_http and ip:
+            result['https_status'] = self.check_http(subdomain, https=True)
+            if result['https_status'] is None:
+                result['http_status'] = self.check_http(subdomain, https=False)
+
+        return result
+
+    def validate_batch(
+        self,
+        subdomains: Set[str],
+        threads: int = 20,
+        check_http: bool = False
+    ) -> Dict[str, dict]:
+        """
+        Validate a batch of subdomains.
+
+        Args:
+            subdomains: Set of subdomains to validate
+            threads: Number of parallel threads
+            check_http: Whether to also check HTTP accessibility
+
+        Returns:
+            Dict of {subdomain: {ip, is_active, http_status, https_status}}
+        """
+        results = {}
+
+        def validate_single_wrapper(subdomain: str) -> Tuple[str, dict]:
+            return subdomain, self.validate_single(subdomain, check_http)
+
+        with ThreadPoolExecutor(max_workers=threads) as executor:
+            futures = {executor.submit(validate_single_wrapper, sub): sub for sub in subdomains}
+
+            for future in as_completed(futures):
+                try:
+                    subdomain, result = future.result()
+                    results[subdomain] = result
+                except Exception as e:
+                    subdomain = futures[future]
+                    logger.error(f"Error validating {subdomain}: {e}")
+                    results[subdomain] = {
+                        'ip': None,
+                        'is_active': False,
+                        'http_status': None,
+                        'https_status': None
+                    }
+
+        return results
+
+    @staticmethod
+    def is_valid_domain(domain: str) -> bool:
+        """
+        Check if a domain string is valid.
+
+        Args:
+            domain: Domain to validate
+
+        Returns:
+            True if valid domain format
+        """
+        if not domain or len(domain) > 253:
+            return False
+
+        # Check for valid characters
+        allowed = set('abcdefghijklmnopqrstuvwxyz0123456789.-')
+        if not set(domain.lower()).issubset(allowed):
+            return False
+
+        # Check label lengths
+        labels = domain.split('.')
+        if len(labels) < 2:
+            return False
+
+        for label in labels:
+            if not label or len(label) > 63:
+                return False
+            if label.startswith('-') or label.endswith('-'):
+                return False
+
+        return True
+
+    @staticmethod
+    def extract_subdomain_from_fqdn(fqdn: str, parent_domain: str) -> Optional[str]:
+        """
+        Extract subdomain part from a fully qualified domain name.
+
+        Args:
+            fqdn: Full domain (e.g., 'api.staging.example.com')
+            parent_domain: Parent domain (e.g., 'example.com')
+
+        Returns:
+            Subdomain part (e.g., 'api.staging') or None
+        """
+        fqdn = fqdn.lower().rstrip('.')
+        parent_domain = parent_domain.lower().rstrip('.')
+
+        if not fqdn.endswith(f".{parent_domain}") and fqdn != parent_domain:
+            return None
+
+        if fqdn == parent_domain:
+            return None
+
+        return fqdn[:-len(parent_domain) - 1]
diff --git a/src/usfx/utils/wildcard.py b/src/usfx/utils/wildcard.py
@@ -0,0 +1,127 @@
+"""
+Wildcard DNS Detection
+
+Detects if a domain uses wildcard DNS records (*.domain.com)
+which would return valid responses for any subdomain query.
+"""
+
+import random
+import string
+import logging
+from typing import Optional, Set, Tuple, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .dns_resolver import InternalDNSResolver
+
+logger = logging.getLogger(__name__)
+
+
+class WildcardDetector:
+    """Detects wildcard DNS configurations"""
+
+    def __init__(
+        self,
+        domain: str,
+        resolver: Optional['InternalDNSResolver'] = None
+    ):
+        """
+        Initialize wildcard detector.
+
+        Args:
+            domain: Target domain
+            resolver: Optional custom DNS resolver (uses internal resolver if None)
+        """
+        self.domain = domain
+        self.wildcard_ips: Set[str] = set()
+        self.has_wildcard = False
+
+        if resolver:
+            self.resolver = resolver
+        else:
+            from .dns_resolver import InternalDNSResolver
+            self.resolver = InternalDNSResolver()
+
+    def _generate_random_subdomain(self, length: int = 16) -> str:
+        """Generate a random subdomain that is unlikely to exist"""
+        chars = string.ascii_lowercase + string.digits
+        return ''.join(random.choice(chars) for _ in range(length))
+
+    def detect(self, num_tests: int = 3) -> Tuple[bool, Set[str]]:
+        """
+        Detect if domain uses wildcard DNS.
+
+        Args:
+            num_tests: Number of random subdomain tests to perform
+
+        Returns:
+            Tuple of (has_wildcard, wildcard_ips)
+        """
+        test_results = []
+
+        for _ in range(num_tests):
+            random_sub = self._generate_random_subdomain()
+            test_domain = f"{random_sub}.{self.domain}"
+
+            ips = self.resolver.resolve_a_all(test_domain)
+            test_results.append(ips)
+
+            if ips:
+                logger.debug(f"Wildcard test {test_domain} resolved to {ips}")
+
+        # If all tests returned the same non-empty IP set, we have a wildcard
+        non_empty_results = [r for r in test_results if r]
+
+        if len(non_empty_results) >= 2:
+            # Check if all non-empty results have overlapping IPs
+            common_ips = non_empty_results[0]
+            for result in non_empty_results[1:]:
+                common_ips = common_ips & result
+
+            if common_ips:
+                self.has_wildcard = True
+                self.wildcard_ips = common_ips
+                logger.info(f"Wildcard detected for {self.domain}: {common_ips}")
+
+        return self.has_wildcard, self.wildcard_ips
+
+    def is_wildcard_result(self, ips: Set[str]) -> bool:
+        """
+        Check if a set of IPs matches the wildcard IPs.
+
+        Args:
+            ips: Set of IP addresses to check
+
+        Returns:
+            True if the IPs match the wildcard configuration
+        """
+        if not self.has_wildcard or not self.wildcard_ips:
+            return False
+
+        # If the result IPs are a subset of wildcard IPs, it's likely a wildcard result
+        return bool(ips & self.wildcard_ips)
+
+    def filter_wildcards(self, subdomains: dict) -> dict:
+        """
+        Filter out wildcard results from discovered subdomains.
+
+        Args:
+            subdomains: Dict of {subdomain: ip_address}
+
+        Returns:
+            Filtered dict without wildcard results
+        """
+        if not self.has_wildcard:
+            return subdomains
+
+        filtered = {}
+        for subdomain, ip in subdomains.items():
+            if ip and ip not in self.wildcard_ips:
+                filtered[subdomain] = ip
+            elif ip is None:
+                filtered[subdomain] = ip
+
+        filtered_count = len(subdomains) - len(filtered)
+        if filtered_count > 0:
+            logger.info(f"Filtered {filtered_count} wildcard results")
+
+        return filtered
diff --git a/src/usfx/utils/wordlist.py b/src/usfx/utils/wordlist.py
@@ -0,0 +1,284 @@
+"""
+Wordlist Management
+
+Manages subdomain wordlists for brute force enumeration.
+Uses importlib.resources for bundled wordlists to support pip installation.
+"""
+
+import logging
+from pathlib import Path
+from typing import List, Optional, Set
+
+try:
+    from importlib.resources import files as pkg_files
+    from importlib.resources import as_file
+except ImportError:
+    # Python < 3.9 fallback
+    from importlib_resources import files as pkg_files
+    from importlib_resources import as_file
+
+logger = logging.getLogger(__name__)
+
+
+# Built-in minimal wordlist as fallback
+BUILTIN_WORDLIST = [
+    # Common
+    'www', 'mail', 'ftp', 'localhost', 'webmail', 'smtp', 'pop', 'ns1', 'ns2',
+    'ns3', 'ns4', 'dns', 'dns1', 'dns2', 'mx', 'mx1', 'mx2', 'imap', 'remote',
+    'blog', 'shop', 'store', 'forum', 'cpanel', 'whm', 'webdisk', 'api',
+
+    # Development
+    'dev', 'development', 'test', 'testing', 'stage', 'staging', 'prod',
+    'production', 'qa', 'uat', 'sandbox', 'demo', 'beta', 'alpha', 'preview',
+    'pre', 'preprod', 'pre-production', 'dev1', 'dev2', 'test1', 'test2',
+
+    # Infrastructure
+    'db', 'database', 'mysql', 'postgres', 'postgresql', 'redis', 'mongo',
+    'mongodb', 'elastic', 'elasticsearch', 'cache', 'memcache', 'memcached',
+    'backup', 'bak', 'server', 'server1', 'server2', 'node', 'node1', 'node2',
+    'host', 'host1', 'host2', 'web', 'web1', 'web2', 'app', 'app1', 'app2',
+    'worker', 'worker1', 'worker2', 'lb', 'loadbalancer', 'proxy', 'gateway',
+
+    # Services
+    'api', 'api1', 'api2', 'rest', 'graphql', 'grpc', 'ws', 'websocket',
+    'socket', 'cdn', 'static', 'assets', 'images', 'img', 'media', 'files',
+    'upload', 'uploads', 'download', 'downloads', 'docs', 'documentation',
+    'wiki', 'help', 'support', 'status', 'health', 'monitor', 'monitoring',
+    'metrics', 'logs', 'logging', 'kibana', 'grafana', 'prometheus',
+
+    # Security & Admin
+    'admin', 'administrator', 'root', 'secure', 'security', 'ssl', 'vpn',
+    'portal', 'login', 'signin', 'auth', 'authentication', 'oauth', 'sso',
+    'panel', 'dashboard', 'console', 'control', 'manage', 'management',
+
+    # Cloud & DevOps
+    'jenkins', 'ci', 'cd', 'build', 'deploy', 'git', 'gitlab', 'github',
+    'bitbucket', 'jira', 'confluence', 'slack', 'teams', 'kubernetes', 'k8s',
+    'docker', 'container', 'registry', 'helm', 'terraform', 'ansible',
+    'aws', 'azure', 'gcp', 'cloud', 's3', 'storage', 'bucket',
+
+    # Email & Communication
+    'email', 'newsletter', 'mail2', 'mail3', 'webmail2', 'exchange',
+    'outlook', 'autodiscover', 'autoconfig', 'calendar', 'chat', 'im',
+
+    # Regional & Language
+    'en', 'es', 'de', 'fr', 'it', 'pt', 'ru', 'zh', 'ja', 'ko',
+    'us', 'eu', 'uk', 'au', 'ca', 'asia', 'emea', 'apac', 'latam',
+
+    # Versioning
+    'v1', 'v2', 'v3', 'v4', 'api-v1', 'api-v2', 'api-v3',
+    'old', 'new', 'legacy', 'archive', 'archives', 'backup1', 'backup2',
+
+    # Mobile
+    'mobile', 'm', 'app-api', 'ios', 'android', 'mobile-api',
+
+    # Internal (important for internal network enumeration)
+    'internal', 'intranet', 'corp', 'corporate', 'staff', 'employee',
+    'office', 'hr', 'finance', 'sales', 'marketing', 'legal',
+    'dc', 'dc1', 'dc2', 'ad', 'ldap', 'kerberos', 'ntp', 'radius',
+    'fileserver', 'printserver', 'sharepoint', 'exchange',
+
+    # Misc
+    'home', 'public', 'private', 'www1', 'www2', 'www3', 'web3',
+    'origin', 'origin-www', 'direct', 'main', 'primary', 'secondary',
+    'temp', 'tmp', 'data', 'report', 'reports', 'analytics', 'tracking',
+    'crm', 'erp', 'cms', 'ecommerce', 'payment', 'payments', 'checkout',
+    'cart', 'order', 'orders', 'billing', 'invoice', 'invoices',
+
+    # Name patterns
+    'web01', 'web02', 'web03', 'app01', 'app02', 'app03',
+    'srv01', 'srv02', 'srv03', 'srv1', 'srv2', 'srv3',
+    'dc1', 'dc2', 'datacenter', 'datacenter1', 'datacenter2'
+]
+
+
+def get_bundled_wordlist(size: str = 'medium') -> Path:
+    """
+    Get path to a bundled wordlist file.
+
+    Args:
+        size: Wordlist size - 'small', 'medium', or 'large'
+
+    Returns:
+        Path to the wordlist file
+
+    Raises:
+        FileNotFoundError: If the wordlist file doesn't exist
+    """
+    filename_map = {
+        'small': 'wordlist_small.txt',
+        'medium': 'wordlist.txt',
+        'large': 'wordlist_large.txt',
+    }
+
+    filename = filename_map.get(size, 'wordlist.txt')
+
+    try:
+        # Try to get the bundled resource
+        ref = pkg_files('usfx.data').joinpath(filename)
+        with as_file(ref) as path:
+            if path.exists():
+                return path
+
+        # Fallback: check relative to this file
+        data_dir = Path(__file__).parent.parent / 'data'
+        path = data_dir / filename
+        if path.exists():
+            return path
+
+        raise FileNotFoundError(f"Wordlist not found: {filename}")
+
+    except Exception as e:
+        logger.warning(f"Could not load bundled wordlist: {e}")
+        raise FileNotFoundError(f"Wordlist not found: {filename}")
+
+
+class WordlistManager:
+    """Manages subdomain wordlists with support for bundled and custom lists"""
+
+    def __init__(self, wordlist_path: Optional[str] = None):
+        """
+        Initialize wordlist manager.
+
+        Args:
+            wordlist_path: Optional custom wordlist file path
+        """
+        self.wordlist_path = wordlist_path
+        self._wordlist: Optional[List[str]] = None
+
+    def load_wordlist(self, size: str = 'medium') -> List[str]:
+        """
+        Load wordlist from file or use built-in.
+
+        Args:
+            size: Wordlist size for bundled lists ('small', 'medium', 'large')
+
+        Returns:
+            List of subdomain words
+        """
+        if self._wordlist is not None:
+            return self._wordlist
+
+        wordlist = set()
+
+        # Try to load from custom path first
+        if self.wordlist_path:
+            try:
+                with open(self.wordlist_path, 'r', encoding='utf-8') as f:
+                    for line in f:
+                        word = line.strip().lower()
+                        if word and not word.startswith('#'):
+                            wordlist.add(word)
+                logger.info(f"Loaded {len(wordlist)} words from {self.wordlist_path}")
+            except Exception as e:
+                logger.error(f"Error loading custom wordlist: {e}")
+
+        # If no custom wordlist or it failed, try bundled
+        if not wordlist:
+            try:
+                bundled_path = get_bundled_wordlist(size)
+                with open(bundled_path, 'r', encoding='utf-8') as f:
+                    for line in f:
+                        word = line.strip().lower()
+                        if word and not word.startswith('#'):
+                            wordlist.add(word)
+                logger.info(f"Loaded {len(wordlist)} words from bundled {size} wordlist")
+            except FileNotFoundError:
+                logger.warning("Bundled wordlist not found, using built-in minimal list")
+
+        # Always add built-in words as fallback
+        wordlist.update(BUILTIN_WORDLIST)
+
+        self._wordlist = sorted(list(wordlist))
+        logger.info(f"Total wordlist size: {len(self._wordlist)}")
+
+        return self._wordlist
+
+    def get_wordlist(
+        self,
+        size: str = 'medium',
+        max_words: Optional[int] = None,
+        include_numbers: bool = True,
+        include_env: bool = True
+    ) -> List[str]:
+        """
+        Get wordlist with optional filtering and expansion.
+
+        Args:
+            size: Wordlist size for bundled lists
+            max_words: Maximum number of words to return
+            include_numbers: Include numbered variations (api1, api2, etc.)
+            include_env: Include environment variations (api-dev, api-prod, etc.)
+
+        Returns:
+            List of subdomain words
+        """
+        base_list = self.load_wordlist(size)
+        result = set(base_list)
+
+        if include_numbers:
+            # Add numbered variations
+            for word in list(result)[:500]:  # Limit base words for expansion
+                for i in range(1, 6):
+                    result.add(f"{word}{i}")
+                    result.add(f"{word}0{i}")
+
+        if include_env:
+            # Add environment variations
+            envs = ['dev', 'test', 'stage', 'staging', 'prod', 'qa', 'uat']
+            for word in list(result)[:200]:  # Limit base words
+                for env in envs:
+                    result.add(f"{word}-{env}")
+                    result.add(f"{env}-{word}")
+
+        result_list = sorted(list(result))
+
+        if max_words:
+            result_list = result_list[:max_words]
+
+        return result_list
+
+    def add_discovered(self, subdomains: Set[str]) -> None:
+        """
+        Add newly discovered subdomains to wordlist.
+
+        Args:
+            subdomains: Set of discovered subdomains
+        """
+        if self._wordlist is None:
+            self.load_wordlist()
+
+        existing = set(self._wordlist)
+        new_words = []
+
+        for subdomain in subdomains:
+            parts = subdomain.split('.')
+            for part in parts:
+                if part and part not in existing:
+                    new_words.append(part)
+                    existing.add(part)
+
+        if new_words:
+            self._wordlist.extend(new_words)
+            self._wordlist.sort()
+            logger.debug(f"Added {len(new_words)} new words to wordlist")
+
+    def save_wordlist(self, path: str) -> None:
+        """
+        Save current wordlist to file.
+
+        Args:
+            path: Path to save the wordlist
+        """
+        if self._wordlist is None:
+            self.load_wordlist()
+
+        try:
+            with open(path, 'w', encoding='utf-8') as f:
+                f.write("# Subdomain Wordlist\n")
+                f.write("# Generated by USFX\n\n")
+                for word in self._wordlist:
+                    f.write(f"{word}\n")
+            logger.info(f"Saved {len(self._wordlist)} words to {path}")
+        except Exception as e:
+            logger.error(f"Error saving wordlist: {e}")