Skip to content

Stealth Mode

Stealth mode is enabled by default for all engagements. It minimizes the detection footprint by mimicking real browser traffic patterns, enforcing low request rates, and avoiding signatures that trigger WAF rules. The only ways to disable stealth are the --fast and --bug-bounty flags.


Overview

Setting Stealth (Default) --fast --bug-bounty
Rate limit 1-3 req/sec 50 req/sec 50 req/sec
Concurrency 2 threads 25 threads 25 threads
User-Agent Chrome 133 Tool default Tool default
TLS fingerprint Chrome via curl-impersonate Standard curl Standard curl
Jitter 1-4s random delay None None
Wordlist shuffle Yes No No
Phase breaks 30s between phases None None
DoS templates Excluded Excluded Included
Recon (Phase 1) Full Skipped Full + expanded

TLS Fingerprint Evasion

Standard curl's OpenSSL TLS fingerprint (JA3: 456523fc94726331a8d0512ce63e59e3) is on every major WAF blocklist. Stealth mode uses curl-impersonate to produce Chrome's exact JA3 hash at the TLS layer.

# Availability check (Docker container includes curl-impersonate)
if which curl_chrome131 >/dev/null 2>&1; then
    CURL_CMD="curl_chrome131"
elif which curl-chrome >/dev/null 2>&1; then
    CURL_CMD="curl-chrome"
else
    CURL_CMD="curl"  # Fallback with --tlsv1.3
fi

When curl-impersonate is unavailable, stealth mode falls back to standard curl with --tlsv1.3 to at least match modern browser TLS version negotiation.


Browser Impersonation Profiles

WAFs fingerprint HTTP header order, not just values. Chrome sends sec-ch-ua before User-Agent before Accept. Firefox does not send sec-ch-ua at all. A mismatched UA + header set triggers instant detection.

Chrome 133 Profile (Default)

CHROME_UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"

# Headers in Chrome's exact sending order
STEALTH_HEADERS_CHROME=(
    -H "sec-ch-ua: \"Chromium\";v=\"133\", \"Not?A_Brand\";v=\"99\""
    -H "sec-ch-ua-mobile: ?0"
    -H "sec-ch-ua-platform: \"macOS\""
    -H "User-Agent: $CHROME_UA"
    -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"
    -H "Accept-Language: en-US,en;q=0.9"
    -H "Accept-Encoding: gzip, deflate, br"
    -H "Connection: keep-alive"
    -H "Upgrade-Insecure-Requests: 1"
    -H "Sec-Fetch-Dest: document"
    -H "Sec-Fetch-Mode: navigate"
    -H "Sec-Fetch-Site: none"
    -H "Sec-Fetch-User: ?1"
)

A separate STEALTH_HEADERS_CHROME_API profile is used for API/XHR requests, with Accept: application/json and Sec-Fetch-Mode: cors.

Firefox 133 Profile

Available as an alternative. Firefox does not send sec-ch-ua headers -- including them with a Firefox UA triggers detection.

Profile Selection

The active profile is automatically matched to the curl-impersonate binary in use:

  • curl_chrome131 / curl-chrome --> Chrome headers
  • curl_ff133 / curl-firefox --> Firefox headers
  • Standard curl --> Chrome headers (best general compatibility)

Rate Limits

Global Limits

Activity Maximum Rate
Scanning 10 req/sec
Fuzzing 50 req/sec
Authentication 1 req/sec

Per-Tool Stealth Configuration

Tool Default Rate Stealth Rate Stealth Concurrency
nuclei -rl 10 -rl 2 -c 2
ffuf/ffufai -rate 10 -rate 1 -t 1
katana default -rl 2 -c 2 -d 3 (max depth)
httpx default -rl 2 -t 2 --
naabu -rate 100 -rate 10 --
nikto default -maxtime 600s -Pause 2 --
sqlmap --delay=1 --delay=2 --time-sec=1 --threads=1
dalfox default --delay 2000 --worker 1
arjun default -t 1 --delay 2 --
subfinder default -rate-limit 2 --
curl loops no delay sleep $(shuf -i 1-4 -n 1) --

Tool-Specific Examples

# nuclei -- stealth mode
nuclei -rl 2 -c 2 -timeout 10 \
    -H "User-Agent: $STEALTH_UA" \
    -etags dos,fuzz \
    -severity critical,high,medium \
    -l targets.txt

# sqlmap -- stealth mode
sqlmap -u "TARGET" --batch --level=2 --risk=1 \
    --technique=BT --threads=1 --delay=2 --time-sec=1 \
    --random-agent --safe-url="TARGET_BASEURL" --safe-freq=5 \
    --tamper=between,randomcase --output-dir=logs/sqlmap/

# ffuf -- stealth mode (with shuffled wordlist)
shuf wordlist.txt > /tmp/shuffled-wordlist.txt
ffuf -rate 1 -t 1 \
    -H "User-Agent: $STEALTH_UA" \
    -w /tmp/shuffled-wordlist.txt \
    -u TARGET_URL/FUZZ

Timing Jitter

Instead of fixed delays, stealth mode uses random jitter between requests to simulate human browsing patterns.

# Base jitter: 1-4 seconds (random) between requests
stealth_delay() {
    local min=$(( 1 * JITTER_MULT ))
    local max=$(( 4 * JITTER_MULT ))
    sleep $(python3 -c "import random; print(round(random.uniform($min, $max), 1))")
}

JITTER_MULT -- Parallel Agent Scaling

When multiple agents run concurrently against the same target (wave execution), each agent multiplies its jitter by JITTER_MULT to keep the combined request rate within stealth thresholds.

Concurrent Agents JITTER_MULT Per-Agent Jitter Combined Rate
1 (solo) 1 1-4s ~0.5 req/sec
2 2 2-8s ~0.5 req/sec
3 3 3-12s ~0.5 req/sec

JITTER_MULT is set via environment variable by the wave dispatcher (see agent-dispatch.md). It affects:

  • Inter-request jitter (stealth_delay)
  • Rate limit backoff duration (stealth_backoff)
  • All curl loops inside skills
# Rate limit backoff also scales with JITTER_MULT
stealth_backoff() {
    local min=$(( 10 * JITTER_MULT ))
    local max=$(( 30 * JITTER_MULT ))
    sleep $(python3 -c "import random; print(round(random.uniform($min, $max), 1))")
}

WAF Detection and Response

The stealth_curl() function in the shared boilerplate automatically handles WAF responses:

HTTP Code Behavior
429 Log warning, backoff 10-30s (scaled by JITTER_MULT), retry once
502/503 Log warning, wait 5s, return response body
3x 429 consecutive Stop the current skill entirely
# stealth_curl() handles this automatically:
# 1. Send request with Chrome UA + stealth headers + TLS 1.3
# 2. If 429: backoff, retry
# 3. If 503: warn, brief pause
# 4. Apply random jitter before next request

Wordlist Randomization

All stealth-mode fuzzing randomizes wordlists to avoid sequential access patterns detectable by WAFs:

stealth_shuffle_wordlist() {
    local input="$1"
    local output="/tmp/shuffled-$(basename "$input")"
    shuf "$input" > "$output"
    echo "$output"
}

# Usage
wordlist=$(stealth_shuffle_wordlist "wordlists/SecLists/Discovery/Web-Content/common.txt")
ffuf -w "$wordlist" -u TARGET_URL/FUZZ

Sequential directory brute-forcing (a, aa, aab, ...) is a well-known WAF signature. Shuffling eliminates this pattern.


Endpoint Exclusions

In stealth mode, the following endpoints are skipped during fuzzing and automated scanning to avoid triggering side effects:

STEALTH_EXCLUDE_ENDPOINTS=(
    "/logout" "/signout"
    "/api/*/delete" "/api/*/remove"
    "/export/*" "/download/*"
    "/admin/restart" "/admin/shutdown"
    "/webhook/*" "/payment/*" "/checkout/*"
    "/api/*/purge" "/cache/clear"
    "/queue/*" "/cron/*" "/jobs/*"
)

For ffuf, these are converted to an exclude regex:

STEALTH_EXCLUDE_REGEX="(logout|signout|delete|remove|restart|shutdown|payment|checkout|purge|cache/clear)"

Phase Timing Distribution

Stealth mode inserts 30-second breaks between major phases to let traffic patterns normalize:

stealth_phase_break() {
    echo "[stealth] Phase complete. Pausing 30s to normalize traffic patterns..."
    sleep 30
}

Stealth mode maintains a persistent cookie jar to accumulate cookies like a real browser session (cf_clearance, _ga, csrftoken):

COOKIE_JAR="${EDIR:-/tmp}/logs/.cookie-jar"

# Initialize with a homepage visit (warms up WAF clearance cookies)
init_cookie_jar() {
    if [ ! -s "$COOKIE_JAR" ]; then
        $CURL_CMD -s -o /dev/null \
            -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
            --max-time 15 "$target"
    fi
}

DNS-over-HTTPS

Stealth mode routes DNS queries through Cloudflare's DoH endpoint to prevent DNS query leakage to network monitoring and IDS systems:

DOH_URL="https://cloudflare-dns.com/dns-query"
# Applied via: --doh-url "$DOH_URL" on stealth_curl calls

Comparing Modes

Default (Stealth ON)

  • Chrome 133 User-Agent with correct header order
  • curl-impersonate for TLS fingerprint evasion
  • 1-3 req/sec with random jitter
  • Shuffled wordlists
  • DoS/fuzz nuclei templates excluded
  • DNS-over-HTTPS enabled
  • 30s phase breaks
  • Cookie jar persistence

--fast (Stealth OFF, Recon Skipped)

  • 50 req/sec, 25 threads
  • Tool default User-Agents
  • No jitter or phase breaks
  • Phase 1 (recon) skipped entirely
  • All other phases remain mandatory
  • Mutually exclusive with --bug-bounty

--bug-bounty (Stealth OFF, Expanded Recon)

  • 50 req/sec, 25 threads
  • Tool default User-Agents
  • No jitter or phase breaks
  • Expanded recon: alterx, puredns, asnmap, uncover
  • All nuclei templates included (DoS and fuzz)
  • sqlmap at --risk=2 --level=3 --technique=BEUST
  • Mutually exclusive with --fast

Integration Checklist

Every skill checks context.json for stealth mode at startup via the shared boilerplate:

stealth_enabled=true  # Default
if [ -f "$CONTEXT_FILE" ]; then
    stealth_enabled=$(python3 -c "
        import json, os
        ctx = json.load(open(os.environ.get('CONTEXT_FILE','context.json')))
        print('true' if ctx.get('stealth',{}).get('enabled', True) else 'false')
    ")
fi

All curl calls are routed through stealth_curl(), which conditionally applies headers, jitter, rate limiting, and backoff based on this flag.