etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

commit 8f30d55cec95c0fde4ccd57d7fb4d5a9cb0567e5
parent 37c1f6df16414df5c30fbde2839cb0055b2dfcdf
Author: djnn <[email protected]>
Date: Wed, 18 Feb 2026 12:57:12 +0900

ttyd fuzzing

Diffstat:
Afuzzing/ttyd/build_fuzzers.sh | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuzzing/ttyd/clean.sh | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuzzing/ttyd/fuzz_auth_header.c | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuzzing/ttyd/fuzz_http_parsing.c | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuzzing/ttyd/fuzz_websocket_auth.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuzzing/ttyd/gen-corpus.sh | 346+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuzzing/ttyd/run-fuzzers.sh | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

7 files changed, 1440 insertions(+), 0 deletions(-)

diff --git a/fuzzing/ttyd/build_fuzzers.sh b/fuzzing/ttyd/build_fuzzers.sh
@@ -0,0 +1,170 @@
+#!/bin/bash
+# Build script for TTYD authentication fuzzing harnesses
+
+set -e
+
+MODE="${1:-afl}"  # afl or libfuzzer
+SANITIZER="address"
+
+echo "[*] Building TTYD fuzzing harnesses"
+echo "[*] Mode: $MODE"
+echo "[*] Sanitizer: $SANITIZER"
+echo
+
+# Install dependencies if missing (requires root)
+if [ "$(id -u)" -eq 0 ]; then
+    DEPS_NEEDED=""
+    dpkg -s libjson-c-dev &>/dev/null || DEPS_NEEDED="$DEPS_NEEDED libjson-c-dev"
+    dpkg -s afl++ &>/dev/null || DEPS_NEEDED="$DEPS_NEEDED afl++"
+    if [ -n "$DEPS_NEEDED" ]; then
+        echo "[*] Installing missing dependencies:$DEPS_NEEDED"
+        apt-get install -y $DEPS_NEEDED
+        echo "[+] Dependencies installed"
+        echo
+    fi
+fi
+
+# System setup for AFL++ (requires root)
+if [ "$MODE" == "afl" ] && [ "$(id -u)" -eq 0 ]; then
+    echo "[*] Configuring system for AFL++..."
+
+    # Fix core_pattern for crash detection
+    CORE_PATTERN=$(cat /proc/sys/kernel/core_pattern)
+    if [[ "$CORE_PATTERN" == "|"* ]]; then
+        echo "[*] Setting core_pattern to 'core'..."
+        echo core > /proc/sys/kernel/core_pattern
+        echo "[+] core_pattern set"
+    else
+        echo "[+] core_pattern already OK: $CORE_PATTERN"
+    fi
+
+    # Set CPU governor to performance
+    if [ -d /sys/devices/system/cpu/cpu0/cpufreq ]; then
+        CUR_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null || true)
+        if [ "$CUR_GOV" != "performance" ]; then
+            echo "[*] Setting CPU governor to performance (was: $CUR_GOV)..."
+            for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
+                echo performance > "$gov" 2>/dev/null || true
+            done
+            echo "[+] CPU governor set"
+        else
+            echo "[+] CPU governor already set to performance"
+        fi
+    fi
+
+    echo
+elif [ "$MODE" == "afl" ] && [ "$(id -u)" -ne 0 ]; then
+    echo "[!] Warning: Not running as root. System tuning skipped."
+    echo "    Run with sudo for AFL++ system setup (core_pattern, CPU governor)."
+    echo
+fi
+
+case "$MODE" in
+    afl)
+        echo "[*] Building with AFL++"
+
+        # Check if AFL++ is installed
+        if ! command -v afl-clang-fast &> /dev/null; then
+            echo "[!] AFL++ not found. Please install AFL++:"
+            echo "    sudo apt-get install afl++"
+            echo "    Or: https://github.com/AFLplusplus/AFLplusplus"
+            exit 1
+        fi
+
+        CC="afl-clang-fast"
+        CFLAGS="-fsanitize=$SANITIZER -g -O1"
+        SUFFIX=""
+
+        echo "[+] Compiler: $CC"
+        ;;
+
+    libfuzzer)
+        echo "[*] Building with LibFuzzer"
+
+        # Check if clang is installed
+        if ! command -v clang &> /dev/null; then
+            echo "[!] Clang not found. Please install:"
+            echo "    sudo apt-get install clang"
+            exit 1
+        fi
+
+        CC="clang"
+        CFLAGS="-fsanitize=fuzzer,$SANITIZER -g -O1"
+        SUFFIX="_lf"
+
+        echo "[+] Compiler: $CC"
+        ;;
+
+    *)
+        echo "[!] Unknown mode: $MODE"
+        echo "Usage: $0 [afl|libfuzzer]"
+        exit 1
+        ;;
+esac
+
+# Find json-c: try pkg-config first, then common paths
+JSON_C_CFLAGS=$(pkg-config --cflags json-c 2>/dev/null || true)
+JSON_C_LIBS=$(pkg-config --libs json-c 2>/dev/null || true)
+if [ -z "$JSON_C_CFLAGS" ]; then
+    for dir in /usr/include /usr/local/include; do
+        if [ -f "$dir/json-c/json.h" ]; then
+            JSON_C_CFLAGS="-I$dir"
+            break
+        fi
+    done
+fi
+if [ -z "$JSON_C_LIBS" ]; then
+    JSON_C_LIBS="-ljson-c"
+fi
+if [ -z "$JSON_C_CFLAGS" ]; then
+    echo "[!] json-c headers not found. Install with: sudo apt-get install libjson-c-dev"
+    exit 1
+fi
+
+# Build each harness
+echo
+echo "[*] Building fuzz_auth_header..."
+$CC $CFLAGS fuzz_auth_header.c -o fuzz_auth_header$SUFFIX
+echo "[+] Built: fuzz_auth_header$SUFFIX"
+
+echo "[*] Building fuzz_websocket_auth..."
+$CC $CFLAGS $JSON_C_CFLAGS fuzz_websocket_auth.c $JSON_C_LIBS -o fuzz_websocket_auth$SUFFIX
+echo "[+] Built: fuzz_websocket_auth$SUFFIX"
+
+echo "[*] Building fuzz_http_parsing..."
+$CC $CFLAGS fuzz_http_parsing.c -o fuzz_http_parsing$SUFFIX
+echo "[+] Built: fuzz_http_parsing$SUFFIX"
+
+# Build CmpLog variants (AFL++ only)
+if [ "$MODE" == "afl" ]; then
+    echo
+    echo "[*] Building CmpLog variants (for solving string comparisons)..."
+
+    AFL_LLVM_CMPLOG=1 $CC $CFLAGS fuzz_auth_header.c -o fuzz_auth_header_cmplog
+    echo "[+] Built: fuzz_auth_header_cmplog"
+
+    AFL_LLVM_CMPLOG=1 $CC $CFLAGS $JSON_C_CFLAGS fuzz_websocket_auth.c $JSON_C_LIBS -o fuzz_websocket_auth_cmplog
+    echo "[+] Built: fuzz_websocket_auth_cmplog"
+
+    AFL_LLVM_CMPLOG=1 $CC $CFLAGS fuzz_http_parsing.c -o fuzz_http_parsing_cmplog
+    echo "[+] Built: fuzz_http_parsing_cmplog"
+fi
+
+echo
+echo "[+] All harnesses built successfully!"
+
+# Generate corpus
+echo
+echo "[*] Generating seed corpus..."
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+bash "$SCRIPT_DIR/gen-corpus.sh"
+
+echo
+echo "[+] Ready to fuzz! Run:"
+if [ "$MODE" == "afl" ]; then
+    echo "   ./run_fuzzers.sh                # Launch all in tmux"
+    echo "   # Or individually:"
+    echo "   afl-fuzz -i corpus/auth_header -o output/auth_header ./fuzz_auth_header"
+else
+    echo "   ./fuzz_auth_header_lf corpus/auth_header"
+fi
diff --git a/fuzzing/ttyd/clean.sh b/fuzzing/ttyd/clean.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# Clean up TTYD fuzzing artifacts
+# Usage: ./clean.sh [--keep-crashes] [--corpus-only]
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+KEEP_CRASHES=false
+CORPUS_ONLY=false
+
+for arg in "$@"; do
+    case "$arg" in
+        --keep-crashes) KEEP_CRASHES=true ;;
+        --corpus-only)  CORPUS_ONLY=true ;;
+        -h|--help)
+            echo "Usage: $0 [OPTIONS]"
+            echo
+            echo "Options:"
+            echo "  --keep-crashes  Save crash files before cleaning output"
+            echo "  --corpus-only   Only clean the corpus directory"
+            echo "  -h, --help      Show this help"
+            exit 0
+            ;;
+        *)
+            echo "[!] Unknown option: $arg"
+            echo "    Use --help for usage"
+            exit 1
+            ;;
+    esac
+done
+
+# Stop any running fuzzers
+if tmux has-session -t ttyd-fuzz 2>/dev/null; then
+    echo "[*] Stopping running fuzzer session..."
+    tmux kill-session -t ttyd-fuzz
+    echo "[+] Fuzzer session killed"
+fi
+
+# Clean corpus
+if [ -d "$SCRIPT_DIR/corpus" ]; then
+    echo "[*] Removing corpus/"
+    rm -rf "$SCRIPT_DIR/corpus"
+    echo "[+] Corpus removed"
+fi
+
+if $CORPUS_ONLY; then
+    echo "[+] Done (corpus-only mode)"
+    exit 0
+fi
+
+# Clean output (fuzzing results)
+if [ -d "$SCRIPT_DIR/output" ]; then
+    if $KEEP_CRASHES; then
+        # Save crashes before cleaning
+        CRASH_DIR="$SCRIPT_DIR/saved_crashes_$(date +%Y%m%d_%H%M%S)"
+        HAS_CRASHES=false
+
+        for target_dir in "$SCRIPT_DIR"/output/*/; do
+            [ -d "$target_dir" ] || continue
+            target_name=$(basename "$target_dir")
+
+            for node_dir in "$target_dir"*/; do
+                [ -d "$node_dir" ] || continue
+                crash_path="$node_dir/crashes"
+
+                if [ -d "$crash_path" ] && [ "$(ls -A "$crash_path" 2>/dev/null)" ]; then
+                    node_name=$(basename "$node_dir")
+                    dest="$CRASH_DIR/$target_name/$node_name"
+                    mkdir -p "$dest"
+                    cp -r "$crash_path"/* "$dest/"
+                    HAS_CRASHES=true
+                fi
+            done
+        done
+
+        if $HAS_CRASHES; then
+            echo "[+] Crashes saved to: $CRASH_DIR"
+        else
+            echo "[*] No crashes found to save"
+            rmdir "$CRASH_DIR" 2>/dev/null || true
+        fi
+    fi
+
+    echo "[*] Removing output/"
+    rm -rf "$SCRIPT_DIR/output"
+    echo "[+] Output removed"
+fi
+
+# Clean compiled binaries
+REMOVED=0
+for binary in fuzz_auth_header fuzz_websocket_auth fuzz_http_parsing; do
+    for suffix in "" "_cmplog" "_lf"; do
+        target="$SCRIPT_DIR/${binary}${suffix}"
+        if [ -f "$target" ]; then
+            rm -f "$target"
+            REMOVED=$((REMOVED + 1))
+        fi
+    done
+done
+
+if [ "$REMOVED" -gt 0 ]; then
+    echo "[+] Removed $REMOVED compiled binaries"
+else
+    echo "[*] No binaries to remove"
+fi
+
+echo "[+] Clean complete"
diff --git a/fuzzing/ttyd/fuzz_auth_header.c b/fuzzing/ttyd/fuzz_auth_header.c
@@ -0,0 +1,206 @@
+/*
+ * AFL++ / LibFuzzer Harness for TTYD Authentication Header Parsing
+ *
+ * Target: HTTP Authorization header parsing in check_auth()
+ * Goal: Find buffer overflows, format string bugs, logic errors
+ *
+ * Compile with AFL++:
+ *   afl-clang-fast -fsanitize=address -g -O1 fuzz_auth_header.c -o fuzz_auth_header
+ *
+ * Compile with libFuzzer:
+ *   clang -fsanitize=fuzzer,address -g -O1 fuzz_auth_header.c -o fuzz_auth_header
+ *
+ * Run:
+ *   afl-fuzz -i input/ -o output/ ./fuzz_auth_header
+ *   ./fuzz_auth_header  # libFuzzer
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+// Simulated ttyd structures
+struct server_t {
+    char *credential;      // Base64 encoded username:password
+    char *auth_header;     // Custom auth header name
+} server_instance;
+
+struct server_t *server = &server_instance;
+
+// Simulated check_auth logic from protocol.c
+bool check_auth_fuzzing(const char *auth_header_value, const char *custom_header_value) {
+    // Simulate auth_header mode
+    if (server->auth_header != NULL) {
+        if (custom_header_value != NULL && strlen(custom_header_value) > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    // Simulate credential mode
+    if (server->credential != NULL) {
+        if (auth_header_value == NULL) return false;
+
+        char buf[256];
+        size_t n = strlen(auth_header_value);
+        if (n >= sizeof(buf)) n = sizeof(buf) - 1;
+        memcpy(buf, auth_header_value, n);
+        buf[n] = '\0';
+
+        // Check for "Basic " prefix and compare
+        if (n >= 7 && strstr(buf, "Basic ") && strcmp(buf + 6, server->credential) == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    return true;  // No auth required
+}
+
+// Base64 encoding for credential generation
+static const char base64_chars[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+void base64_encode_simple(const char *input, char *output, size_t out_size) {
+    size_t len = strlen(input);
+    size_t i, j = 0;
+
+    for (i = 0; i < len && j < out_size - 1; i += 3) {
+        uint32_t octet_a = i < len ? (unsigned char)input[i] : 0;
+        uint32_t octet_b = i + 1 < len ? (unsigned char)input[i + 1] : 0;
+        uint32_t octet_c = i + 2 < len ? (unsigned char)input[i + 2] : 0;
+        uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
+
+        if (j + 4 >= out_size) break;
+        output[j++] = base64_chars[(triple >> 18) & 0x3F];
+        output[j++] = base64_chars[(triple >> 12) & 0x3F];
+        output[j++] = i + 1 < len ? base64_chars[(triple >> 6) & 0x3F] : '=';
+        output[j++] = i + 2 < len ? base64_chars[triple & 0x3F] : '=';
+    }
+    output[j] = '\0';
+}
+
+// Fuzzing target
+int fuzz_target(const uint8_t *data, size_t size) {
+    if (size < 10) return 0;  // Need minimum data
+
+    // Parse fuzzer input
+    // Format: [mode:1][credential_len:2][credential][auth_header]
+
+    uint8_t mode = data[0] % 3;  // 0=no auth, 1=basic auth, 2=custom header
+    uint16_t cred_len = (data[1] << 8) | data[2];
+
+    if (cred_len > size - 3) cred_len = size - 3;
+    if (cred_len > 500) cred_len = 500;  // Reasonable limit
+
+    char credential_raw[512] = {0};
+    char credential_b64[1024] = {0};
+    char auth_header_input[1024] = {0};
+    char custom_header_value[256] = {0};
+
+    // Extract credential
+    if (cred_len > 0) {
+        memcpy(credential_raw, data + 3, cred_len);
+        credential_raw[cred_len] = '\0';
+        base64_encode_simple(credential_raw, credential_b64, sizeof(credential_b64));
+    }
+
+    // Extract auth header value
+    size_t remaining = size - 3 - cred_len;
+    if (remaining > 0) {
+        size_t copy_len = remaining < sizeof(auth_header_input) - 1 ?
+                         remaining : sizeof(auth_header_input) - 1;
+        memcpy(auth_header_input, data + 3 + cred_len, copy_len);
+        auth_header_input[copy_len] = '\0';
+
+        // Also extract custom header value (first 255 bytes)
+        size_t custom_len = remaining < sizeof(custom_header_value) - 1 ?
+                           remaining : sizeof(custom_header_value) - 1;
+        memcpy(custom_header_value, data + 3 + cred_len, custom_len);
+        custom_header_value[custom_len] = '\0';
+    }
+
+    // Setup server state based on mode
+    if (mode == 0) {
+        // No authentication required
+        server->credential = NULL;
+        server->auth_header = NULL;
+    } else if (mode == 1) {
+        // Basic auth mode
+        server->credential = credential_b64[0] ? strdup(credential_b64) : NULL;
+        server->auth_header = NULL;
+    } else {
+        // Custom header mode
+        server->credential = NULL;
+        server->auth_header = strdup("X-Auth-User");
+    }
+
+    // Fuzz the authentication check
+    bool result = check_auth_fuzzing(auth_header_input, custom_header_value);
+
+    // Cleanup
+    if (server->credential) free(server->credential);
+    if (server->auth_header) free(server->auth_header);
+    server->credential = NULL;
+    server->auth_header = NULL;
+
+    return 0;
+}
+
+#ifdef __AFL_FUZZ_TESTCASE_LEN
+// AFL++ persistent mode
+__AFL_FUZZ_INIT();
+
+int main(void) {
+    #ifdef __AFL_HAVE_MANUAL_CONTROL
+        __AFL_INIT();
+    #endif
+
+    unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
+
+    while (__AFL_LOOP(10000)) {
+        int len = __AFL_FUZZ_TESTCASE_LEN;
+        fuzz_target(buf, len);
+    }
+
+    return 0;
+}
+#else
+// LibFuzzer mode
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    return fuzz_target(data, size);
+}
+
+// Standalone mode for testing
+#ifndef LIBFUZZER
+int main(int argc, char **argv) {
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
+        return 1;
+    }
+
+    FILE *f = fopen(argv[1], "rb");
+    if (!f) {
+        perror("fopen");
+        return 1;
+    }
+
+    fseek(f, 0, SEEK_END);
+    long size = ftell(f);
+    fseek(f, 0, SEEK_SET);
+
+    uint8_t *data = malloc(size);
+    fread(data, 1, size, f);
+    fclose(f);
+
+    fuzz_target(data, size);
+    free(data);
+
+    printf("Test completed successfully\n");
+    return 0;
+}
+#endif
+#endif
diff --git a/fuzzing/ttyd/fuzz_http_parsing.c b/fuzzing/ttyd/fuzz_http_parsing.c
@@ -0,0 +1,250 @@
+/*
+ * AFL++ / LibFuzzer Harness for TTYD HTTP Request Parsing
+ *
+ * Target: HTTP header parsing, path validation, credential extraction
+ * Goal: Find parsing bugs, buffer overflows, injection vulnerabilities
+ *
+ * Compile with AFL++:
+ *   afl-clang-fast -fsanitize=address -g -O1 fuzz_http_parsing.c -o fuzz_http_parsing
+ *
+ * Compile with libFuzzer:
+ *   clang -fsanitize=fuzzer,address -g -O1 fuzz_http_parsing.c -o fuzz_http_parsing
+ *
+ * Run:
+ *   afl-fuzz -i input/ -o output/ ./fuzz_http_parsing
+ *   ./fuzz_http_parsing  # libFuzzer
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#define MAX_PATH_LEN 128
+#define MAX_HEADER_LEN 256
+
+// Simulated HTTP parsing
+typedef struct {
+    char path[MAX_PATH_LEN];
+    char auth_header[MAX_HEADER_LEN];
+    char origin_header[MAX_HEADER_LEN];
+    char host_header[MAX_HEADER_LEN];
+} http_request_t;
+
+// Parse HTTP request from fuzzer input
+bool parse_http_request(const uint8_t *data, size_t size, http_request_t *req) {
+    if (size == 0 || data == NULL || req == NULL) return false;
+
+    memset(req, 0, sizeof(http_request_t));
+
+    // Simple HTTP request parsing
+    // Format: METHOD PATH\r\nHeader: Value\r\n\r\n
+
+    const char *ptr = (const char *)data;
+    const char *end = ptr + size;
+
+    // Parse request line
+    const char *path_start = strchr(ptr, ' ');
+    if (!path_start || path_start >= end) return false;
+    path_start++;
+
+    const char *path_end = strchr(path_start, ' ');
+    if (!path_end || path_end >= end) path_end = strchr(path_start, '\r');
+    if (!path_end || path_end >= end) path_end = end;
+
+    size_t path_len = path_end - path_start;
+    if (path_len >= MAX_PATH_LEN) path_len = MAX_PATH_LEN - 1;
+    memcpy(req->path, path_start, path_len);
+    req->path[path_len] = '\0';
+
+    // Parse headers
+    ptr = path_end;
+    while (ptr < end) {
+        // Find header line
+        const char *line_start = strchr(ptr, '\n');
+        if (!line_start || line_start >= end) break;
+        line_start++;
+
+        if (line_start[0] == '\r' || line_start[0] == '\n') break;  // End of headers
+
+        const char *colon = strchr(line_start, ':');
+        if (!colon || colon >= end) {
+            ptr = line_start;
+            continue;
+        }
+
+        // Extract header name and value
+        size_t name_len = colon - line_start;
+        const char *value_start = colon + 1;
+        while (value_start < end && isspace(*value_start)) value_start++;
+
+        const char *value_end = strchr(value_start, '\r');
+        if (!value_end || value_end >= end) value_end = end;
+
+        size_t value_len = value_end - value_start;
+
+        // Check for specific headers
+        if (name_len == 13 && strncasecmp(line_start, "Authorization", 13) == 0) {
+            if (value_len >= MAX_HEADER_LEN) value_len = MAX_HEADER_LEN - 1;
+            memcpy(req->auth_header, value_start, value_len);
+            req->auth_header[value_len] = '\0';
+        } else if (name_len == 6 && strncasecmp(line_start, "Origin", 6) == 0) {
+            if (value_len >= MAX_HEADER_LEN) value_len = MAX_HEADER_LEN - 1;
+            memcpy(req->origin_header, value_start, value_len);
+            req->origin_header[value_len] = '\0';
+        } else if (name_len == 4 && strncasecmp(line_start, "Host", 4) == 0) {
+            if (value_len >= MAX_HEADER_LEN) value_len = MAX_HEADER_LEN - 1;
+            memcpy(req->host_header, value_start, value_len);
+            req->host_header[value_len] = '\0';
+        }
+
+        ptr = value_end;
+    }
+
+    return true;
+}
+
+// Validate path (check for path traversal, etc.)
+bool validate_path(const char *path) {
+    if (path == NULL || path[0] == '\0') return false;
+
+    // Check for path traversal sequences
+    if (strstr(path, "..") != NULL) return false;
+    if (strstr(path, "//") != NULL) return false;
+
+    // Check for null bytes
+    size_t len = strlen(path);
+    for (size_t i = 0; i < len; i++) {
+        if (path[i] == '\0') return false;
+    }
+
+    return true;
+}
+
+// Check origin vs host (CSRF protection)
+bool check_origin(const char *origin, const char *host) {
+    if (origin == NULL || host == NULL) return false;
+    if (origin[0] == '\0' || host[0] == '\0') return false;
+
+    // Extract hostname from origin (http://host:port)
+    const char *host_start = strstr(origin, "://");
+    if (host_start) host_start += 3;
+    else host_start = origin;
+
+    // Compare with Host header
+    return strncasecmp(host_start, host, strlen(host)) == 0;
+}
+
+// Extract and validate Basic auth
+bool extract_basic_auth(const char *auth_header, char *username, char *password, size_t max_len) {
+    if (auth_header == NULL || username == NULL || password == NULL) return false;
+
+    // Check for "Basic " prefix
+    if (strncmp(auth_header, "Basic ", 6) != 0) return false;
+
+    const char *encoded = auth_header + 6;
+
+    // Simple base64 decode (not production quality)
+    // For fuzzing purposes, just extract the string
+    size_t len = strlen(encoded);
+    if (len >= max_len) len = max_len - 1;
+
+    // Look for colon separator in decoded value (simplified)
+    const char *colon = strchr(encoded, ':');
+    if (colon) {
+        size_t user_len = colon - encoded;
+        if (user_len >= max_len) user_len = max_len - 1;
+        memcpy(username, encoded, user_len);
+        username[user_len] = '\0';
+
+        size_t pass_len = len - user_len - 1;
+        if (pass_len >= max_len) pass_len = max_len - 1;
+        memcpy(password, colon + 1, pass_len);
+        password[pass_len] = '\0';
+    }
+
+    return true;
+}
+
+// Fuzzing target
+int fuzz_target(const uint8_t *data, size_t size) {
+    if (size < 10) return 0;
+
+    http_request_t req;
+    if (!parse_http_request(data, size, &req)) {
+        return 0;
+    }
+
+    // Validate parsed data
+    bool path_valid = validate_path(req.path);
+    bool origin_valid = check_origin(req.origin_header, req.host_header);
+
+    char username[128] = {0};
+    char password[128] = {0};
+    bool auth_valid = extract_basic_auth(req.auth_header, username, password, sizeof(username));
+
+    // Exercise various code paths
+    if (path_valid && origin_valid && auth_valid) {
+        // All validations passed
+    }
+
+    return 0;
+}
+
+#ifdef __AFL_FUZZ_TESTCASE_LEN
+// AFL++ persistent mode
+__AFL_FUZZ_INIT();
+
+int main(void) {
+    #ifdef __AFL_HAVE_MANUAL_CONTROL
+        __AFL_INIT();
+    #endif
+
+    unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
+
+    while (__AFL_LOOP(10000)) {
+        int len = __AFL_FUZZ_TESTCASE_LEN;
+        fuzz_target(buf, len);
+    }
+
+    return 0;
+}
+#else
+// LibFuzzer mode
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    return fuzz_target(data, size);
+}
+
+// Standalone mode for testing
+#ifndef LIBFUZZER
+int main(int argc, char **argv) {
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
+        return 1;
+    }
+
+    FILE *f = fopen(argv[1], "rb");
+    if (!f) {
+        perror("fopen");
+        return 1;
+    }
+
+    fseek(f, 0, SEEK_END);
+    long size = ftell(f);
+    fseek(f, 0, SEEK_SET);
+
+    uint8_t *data = malloc(size);
+    fread(data, 1, size, f);
+    fclose(f);
+
+    fuzz_target(data, size);
+    free(data);
+
+    printf("Test completed successfully\n");
+    return 0;
+}
+#endif
+#endif
diff --git a/fuzzing/ttyd/fuzz_websocket_auth.c b/fuzzing/ttyd/fuzz_websocket_auth.c
@@ -0,0 +1,189 @@
+/*
+ * AFL++ / LibFuzzer Harness for TTYD WebSocket Authentication
+ *
+ * Target: WebSocket AuthToken JSON parsing and validation
+ * Goal: Find JSON parsing bugs, buffer overflows, logic errors
+ *
+ * Compile with AFL++:
+ *   afl-clang-fast -fsanitize=address -g -O1 fuzz_websocket_auth.c -ljson-c -o fuzz_websocket_auth
+ *
+ * Compile with libFuzzer:
+ *   clang -fsanitize=fuzzer,address -g -O1 fuzz_websocket_auth.c -ljson-c -o fuzz_websocket_auth
+ *
+ * Run:
+ *   afl-fuzz -i input/ -o output/ ./fuzz_websocket_auth
+ *   ./fuzz_websocket_auth  # libFuzzer
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <json-c/json.h>
+
+// Simulated ttyd structures
+struct server_t {
+    char *credential;
+} server_instance;
+
+struct pss_tty_t {
+    bool authenticated;
+} pss_instance;
+
+struct server_t *server = &server_instance;
+
+// Simulated parse_window_size and auth check from protocol.c
+struct json_object *parse_window_size_fuzzing(const char *buf, size_t len,
+                                              uint16_t *columns, uint16_t *rows) {
+    if (buf == NULL || len == 0) return NULL;
+
+    // Ensure null termination
+    char *safe_buf = malloc(len + 1);
+    if (!safe_buf) return NULL;
+    memcpy(safe_buf, buf, len);
+    safe_buf[len] = '\0';
+
+    struct json_tokener *tok = json_tokener_new();
+    struct json_object *obj = json_tokener_parse_ex(tok, safe_buf, len);
+    json_tokener_free(tok);
+    free(safe_buf);
+
+    if (obj == NULL) return NULL;
+
+    // Extract columns and rows
+    struct json_object *o = NULL;
+    if (json_object_object_get_ex(obj, "columns", &o)) {
+        *columns = (uint16_t)json_object_get_int(o);
+    }
+    if (json_object_object_get_ex(obj, "rows", &o)) {
+        *rows = (uint16_t)json_object_get_int(o);
+    }
+
+    return obj;
+}
+
+bool check_websocket_auth_fuzzing(const char *buf, size_t len) {
+    struct pss_tty_t *pss = &pss_instance;
+    pss->authenticated = false;
+
+    uint16_t columns = 0, rows = 0;
+    struct json_object *obj = parse_window_size_fuzzing(buf, len, &columns, &rows);
+
+    if (obj == NULL) {
+        return false;
+    }
+
+    if (server->credential != NULL) {
+        struct json_object *o = NULL;
+        if (json_object_object_get_ex(obj, "AuthToken", &o)) {
+            const char *token = json_object_get_string(o);
+            if (token != NULL && strcmp(token, server->credential) == 0) {
+                pss->authenticated = true;
+            }
+        }
+
+        if (!pss->authenticated) {
+            json_object_put(obj);
+            return false;
+        }
+    }
+
+    json_object_put(obj);
+    return true;
+}
+
+// Fuzzing target
+int fuzz_target(const uint8_t *data, size_t size) {
+    if (size < 2) return 0;
+
+    // Parse fuzzer input
+    // Format: [has_credential:1][credential_len:1][credential][json_message]
+
+    bool has_credential = (data[0] % 2) == 1;
+    uint8_t cred_len = data[1];
+
+    if (cred_len > size - 2) cred_len = size - 2;
+    if (cred_len > 200) cred_len = 200;
+
+    char credential[256] = {0};
+    if (has_credential && cred_len > 0) {
+        memcpy(credential, data + 2, cred_len);
+        credential[cred_len] = '\0';
+        server->credential = credential;
+    } else {
+        server->credential = NULL;
+    }
+
+    // Extract JSON message
+    size_t json_start = 2 + (has_credential ? cred_len : 0);
+    if (json_start >= size) {
+        server->credential = NULL;
+        return 0;
+    }
+
+    size_t json_len = size - json_start;
+    const char *json_data = (const char *)(data + json_start);
+
+    // Fuzz the WebSocket auth check
+    bool result = check_websocket_auth_fuzzing(json_data, json_len);
+
+    server->credential = NULL;
+    return 0;
+}
+
+#ifdef __AFL_FUZZ_TESTCASE_LEN
+// AFL++ persistent mode
+__AFL_FUZZ_INIT();
+
+int main(void) {
+    #ifdef __AFL_HAVE_MANUAL_CONTROL
+        __AFL_INIT();
+    #endif
+
+    unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
+
+    while (__AFL_LOOP(10000)) {
+        int len = __AFL_FUZZ_TESTCASE_LEN;
+        fuzz_target(buf, len);
+    }
+
+    return 0;
+}
+#else
+// LibFuzzer mode
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    return fuzz_target(data, size);
+}
+
+// Standalone mode for testing
+#ifndef LIBFUZZER
+int main(int argc, char **argv) {
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
+        return 1;
+    }
+
+    FILE *f = fopen(argv[1], "rb");
+    if (!f) {
+        perror("fopen");
+        return 1;
+    }
+
+    fseek(f, 0, SEEK_END);
+    long size = ftell(f);
+    fseek(f, 0, SEEK_SET);
+
+    uint8_t *data = malloc(size);
+    fread(data, 1, size, f);
+    fclose(f);
+
+    fuzz_target(data, size);
+    free(data);
+
+    printf("Test completed successfully\n");
+    return 0;
+}
+#endif
+#endif
diff --git a/fuzzing/ttyd/gen-corpus.sh b/fuzzing/ttyd/gen-corpus.sh
@@ -0,0 +1,346 @@
+#!/bin/bash
+# Generate seed corpus for TTYD fuzzing harnesses
+# Creates structured inputs matching each harness's expected format
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+mkdir -p "$SCRIPT_DIR/corpus/auth_header"
+mkdir -p "$SCRIPT_DIR/corpus/websocket_auth"
+mkdir -p "$SCRIPT_DIR/corpus/http_parsing"
+
+echo "[*] Generating corpus for fuzz_auth_header"
+echo "    Format: [mode:1][credential_len:2][credential][auth_header_value]"
+
+DIR="$SCRIPT_DIR/corpus/auth_header"
+
+# Mode 0: no auth required
+# mode=0x00, cred_len=0x0000, payload
+printf '\x00\x00\x00anything-goes' > "$DIR/noauth_basic"
+printf '\x00\x00\x00' > "$DIR/noauth_empty"
+printf '\x00\x00\x00Basic YWRtaW46cGFzcw==' > "$DIR/noauth_with_basic"
+
+# Mode 1: basic auth (mode % 3 == 1)
+# mode=0x01, cred_len=0x000b (11), credential="admin:pass", auth_header
+printf '\x01\x00\x0badmin:pass\x00Basic YWRtaW46cGFzcw==' > "$DIR/basic_valid"
+printf '\x01\x00\x0badmin:pass\x00Basic INVALIDBASE64==' > "$DIR/basic_mismatch"
+printf '\x01\x00\x0badmin:pass\x00Basic ' > "$DIR/basic_empty_token"
+printf '\x01\x00\x0badmin:pass\x00' > "$DIR/basic_no_header"
+printf '\x01\x00\x0badmin:pass\x00Bearer token123' > "$DIR/basic_wrong_scheme"
+printf '\x01\x00\x0badmin:pass\x00basic YWRtaW46cGFzcw==' > "$DIR/basic_lowercase"
+printf '\x01\x00\x00\x00Basic YWRtaW46cGFzcw==' > "$DIR/basic_no_credential"
+
+# Mode 1: long credentials
+python3 -c "
+import sys
+cred = b'A' * 255 + b':' + b'B' * 255
+cred_len = len(cred)
+header = b'\x01' + cred_len.to_bytes(2, 'big') + cred + b'Basic ' + b'A' * 500
+sys.stdout.buffer.write(header)
+" > "$DIR/basic_long_cred"
+
+# Mode 1: special characters in credentials
+printf '\x01\x00\x13user:p@ss\x00w0rd!#$%%\x00Basic dXNlcjpwQHNzAHcwcmQhIyQl' > "$DIR/basic_special_chars"
+printf '\x01\x00\x0auser\x00:pass\x00Basic dXNlcgA6cGFzcw==' > "$DIR/basic_null_in_cred"
+
+# Mode 2: custom header auth (mode % 3 == 2)
+printf '\x02\x00\x00some-token-value' > "$DIR/custom_with_value"
+printf '\x02\x00\x00' > "$DIR/custom_empty"
+printf '\x02\x00\x00\x00' > "$DIR/custom_null_byte"
+
+# Edge cases
+printf '\x01\x00\x01\x00\x00' > "$DIR/edge_single_byte_cred"
+printf '\x01\xff\xff' > "$DIR/edge_huge_cred_len"
+
+# Format string attempts
+printf '\x01\x00\x10%%s%%s%%s%%s%%s%%s%%s%%s\x00Basic %%x%%x%%x%%x' > "$DIR/fmt_string"
+printf '\x01\x00\x04%%n%%n\x00%%n%%n%%n%%n' > "$DIR/fmt_write"
+
+# Buffer boundary sizes
+python3 -c "
+import sys
+# 254 byte auth header (near buf[256] boundary)
+cred = b'x:y'
+header = b'Basic ' + b'A' * 248
+sys.stdout.buffer.write(b'\x01\x00\x03' + cred + header)
+" > "$DIR/boundary_254"
+
+python3 -c "
+import sys
+# 256 byte auth header (exact buf[256] boundary)
+cred = b'x:y'
+header = b'Basic ' + b'A' * 250
+sys.stdout.buffer.write(b'\x01\x00\x03' + cred + header)
+" > "$DIR/boundary_256"
+
+python3 -c "
+import sys
+# 257 byte auth header (overflow buf[256])
+cred = b'x:y'
+header = b'Basic ' + b'A' * 251
+sys.stdout.buffer.write(b'\x01\x00\x03' + cred + header)
+" > "$DIR/boundary_257"
+
+# Mixed-case Basic prefix variations
+printf '\x01\x00\x0badmin:pass\x00BASIC YWRtaW46cGFzcw==' > "$DIR/basic_uppercase"
+printf '\x01\x00\x0badmin:pass\x00bAsIc YWRtaW46cGFzcw==' > "$DIR/basic_mixedcase"
+
+# Base64 padding edge cases
+printf '\x01\x00\x02a:\x00Basic YTo=' > "$DIR/basic_b64_pad1"
+printf '\x01\x00\x01a\x00Basic YQ==' > "$DIR/basic_b64_pad2"
+printf '\x01\x00\x03abc\x00Basic YWJj' > "$DIR/basic_b64_nopad"
+printf '\x01\x00\x05ad:mn\x00Basic =====' > "$DIR/basic_b64_allpad"
+printf '\x01\x00\x05ad:mn\x00Basic @#$!!' > "$DIR/basic_b64_invalid_chars"
+
+# Credential separator positions
+printf '\x01\x00\x01:\x00Basic Og==' > "$DIR/basic_colon_only"
+printf '\x01\x00\x07:::::::' > "$DIR/basic_all_colons"
+printf '\x01\x00\x0aadmin:a:b:c\x00Basic YWRtaW46YTpiOmM=' > "$DIR/basic_multi_colon"
+
+# Whitespace in auth header
+printf '\x01\x00\x0badmin:pass\x00Basic  YWRtaW46cGFzcw==' > "$DIR/basic_double_space"
+printf '\x01\x00\x0badmin:pass\x00 Basic YWRtaW46cGFzcw==' > "$DIR/basic_leading_space"
+
+echo "[+] Generated $(ls "$DIR" | wc -l) seeds for auth_header"
+
+###############################################################################
+echo
+echo "[*] Generating corpus for fuzz_websocket_auth"
+echo "    Format: [has_credential:1][credential_len:1][credential][json_message]"
+
+DIR="$SCRIPT_DIR/corpus/websocket_auth"
+
+# No credential (even byte = no cred)
+printf '\x00\x00{"columns":80,"rows":24}' > "$DIR/noauth_normal"
+printf '\x00\x00{"columns":200,"rows":50}' > "$DIR/noauth_large_term"
+printf '\x00\x00{}' > "$DIR/noauth_empty_json"
+printf '\x00\x00{"columns":80,"rows":24,"AuthToken":"ignored"}' > "$DIR/noauth_with_token"
+
+# With credential (odd byte = has cred)
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":"admin"}' > "$DIR/auth_valid"
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":"wrong"}' > "$DIR/auth_invalid"
+printf '\x01\x05admin{"columns":80,"rows":24}' > "$DIR/auth_missing_token"
+printf '\x01\x05admin{"AuthToken":"admin"}' > "$DIR/auth_no_dimensions"
+printf '\x01\x00{"columns":80,"rows":24}' > "$DIR/auth_empty_cred"
+
+# Malformed JSON
+printf '\x00\x00{invalid json}' > "$DIR/json_malformed"
+printf '\x00\x00{"columns":' > "$DIR/json_truncated"
+printf '\x00\x00' > "$DIR/json_empty"
+printf '\x00\x00\x00' > "$DIR/json_null"
+printf '\x01\x03foo[1,2,3]' > "$DIR/json_array"
+printf '\x01\x03foo"just a string"' > "$DIR/json_string"
+
+# Large/extreme values
+printf '\x00\x00{"columns":99999,"rows":99999}' > "$DIR/json_huge_values"
+printf '\x00\x00{"columns":-1,"rows":-1}' > "$DIR/json_negative"
+printf '\x00\x00{"columns":0,"rows":0}' > "$DIR/json_zero"
+printf '\x00\x00{"columns":65535,"rows":65535}' > "$DIR/json_uint16_max"
+printf '\x00\x00{"columns":65536,"rows":65536}' > "$DIR/json_uint16_overflow"
+
+# Deeply nested / large JSON
+python3 -c "
+import sys
+nested = '{\"a\":' * 50 + '1' + '}' * 50
+sys.stdout.buffer.write(b'\x00\x00' + nested.encode())
+" > "$DIR/json_deep_nested"
+
+python3 -c "
+import sys
+big = '{\"columns\":80,\"rows\":24,\"AuthToken\":\"' + 'A' * 5000 + '\"}'
+sys.stdout.buffer.write(b'\x01\x05admin' + big.encode())
+" > "$DIR/json_huge_token"
+
+# Unicode / special strings in JSON
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":"\x00\x00\x00"}' > "$DIR/json_null_bytes_token"
+printf '\x00\x00{"columns":80,"rows":24,"extra":"\\u0000\\u0000"}' > "$DIR/json_unicode_null"
+
+# Format strings in JSON
+printf '\x01\x05admin{"AuthToken":"%%s%%s%%s%%s%%n"}' > "$DIR/json_fmt_string"
+
+# AuthToken type confusion (non-string values)
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":true}' > "$DIR/auth_token_bool"
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":null}' > "$DIR/auth_token_null"
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":12345}' > "$DIR/auth_token_int"
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":["admin"]}' > "$DIR/auth_token_array"
+printf '\x01\x05admin{"columns":80,"rows":24,"AuthToken":{"nested":"admin"}}' > "$DIR/auth_token_object"
+
+# Duplicate keys
+printf '\x01\x05admin{"AuthToken":"wrong","AuthToken":"admin"}' > "$DIR/auth_token_dup"
+printf '\x00\x00{"columns":80,"columns":200,"rows":24}' > "$DIR/json_dup_columns"
+
+# Very long key names
+python3 -c "
+import sys
+key = 'A' * 1000
+msg = '{\"' + key + '\":1,\"columns\":80,\"rows\":24}'
+sys.stdout.buffer.write(b'\x00\x00' + msg.encode())
+" > "$DIR/json_long_key"
+
+# Whitespace variations in JSON
+printf '\x00\x00{  "columns" : 80 , "rows" : 24  }' > "$DIR/json_extra_whitespace"
+printf '\x00\x00{"columns":80,"rows":24}\x00garbage' > "$DIR/json_trailing_garbage"
+
+# Large credential
+python3 -c "
+import sys
+cred = b'A' * 200
+sys.stdout.buffer.write(b'\x01\xc8' + cred + b'{\"AuthToken\":\"' + cred + b'\"}')
+" > "$DIR/auth_long_cred"
+
+echo "[+] Generated $(ls "$DIR" | wc -l) seeds for websocket_auth"
+
+###############################################################################
+echo
+echo "[*] Generating corpus for fuzz_http_parsing"
+echo "    Format: raw HTTP request"
+
+DIR="$SCRIPT_DIR/corpus/http_parsing"
+
+# Standard valid requests
+printf 'GET / HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/get_root"
+printf 'GET /token HTTP/1.1\r\nHost: localhost:7681\r\nAuthorization: Basic YWRtaW46cGFzcw==\r\n\r\n' > "$DIR/get_with_auth"
+printf 'GET /ws HTTP/1.1\r\nHost: localhost:7681\r\nOrigin: http://localhost:7681\r\n\r\n' > "$DIR/get_with_origin"
+printf 'GET / HTTP/1.1\r\nHost: localhost:7681\r\nAuthorization: Basic YWRtaW46cGFzcw==\r\nOrigin: http://localhost:7681\r\n\r\n' > "$DIR/get_full_headers"
+printf 'POST /token HTTP/1.1\r\nHost: localhost:7681\r\nContent-Length: 0\r\n\r\n' > "$DIR/post_token"
+
+# Path traversal attempts
+printf 'GET /../../../etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/path_traversal_basic"
+printf 'GET /..%%2f..%%2f..%%2fetc%%2fpasswd HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/path_traversal_encoded"
+printf 'GET /%%2e%%2e/%%2e%%2e/etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/path_traversal_dot_encoded"
+printf 'GET //etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/path_double_slash"
+printf 'GET /./././../etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/path_traversal_mixed"
+printf 'GET /\x00/etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/path_null_byte"
+
+# Long paths
+python3 -c "
+import sys
+path = '/' + 'A' * 127  # at MAX_PATH_LEN boundary
+sys.stdout.buffer.write(f'GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n'.encode())
+" > "$DIR/path_long_127"
+
+python3 -c "
+import sys
+path = '/' + 'A' * 200  # over MAX_PATH_LEN
+sys.stdout.buffer.write(f'GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n'.encode())
+" > "$DIR/path_long_200"
+
+# Auth header edge cases
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic \r\n\r\n' > "$DIR/auth_empty_b64"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: \r\n\r\n' > "$DIR/auth_empty"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer token123\r\n\r\n' > "$DIR/auth_bearer"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic dXNlcjpwYXNz\r\n\r\n' > "$DIR/auth_valid_b64"
+
+# Long auth header
+python3 -c "
+import sys
+auth = 'Basic ' + 'A' * 300
+sys.stdout.buffer.write(f'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: {auth}\r\n\r\n'.encode())
+" > "$DIR/auth_long_value"
+
+# Auth with colon for extract_basic_auth
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic user:password\r\n\r\n' > "$DIR/auth_with_colon"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic :nouser\r\n\r\n' > "$DIR/auth_empty_user"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic nopass:\r\n\r\n' > "$DIR/auth_empty_pass"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic a:b:c:d:e\r\n\r\n' > "$DIR/auth_multi_colon"
+
+# Origin/Host CSRF checks
+printf 'GET / HTTP/1.1\r\nHost: localhost:7681\r\nOrigin: http://evil.com\r\n\r\n' > "$DIR/csrf_mismatch"
+printf 'GET / HTTP/1.1\r\nHost: localhost:7681\r\nOrigin: http://localhost:7681\r\n\r\n' > "$DIR/csrf_match"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nOrigin: http://localhost:9999\r\n\r\n' > "$DIR/csrf_port_diff"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nOrigin: https://localhost\r\n\r\n' > "$DIR/csrf_scheme_diff"
+printf 'GET / HTTP/1.1\r\nHost: target.com\r\nOrigin: http://target.com.evil.com\r\n\r\n' > "$DIR/csrf_subdomain_trick"
+
+# Malformed HTTP
+printf 'GARBAGE\r\n\r\n' > "$DIR/malformed_no_space"
+printf 'GET\r\n\r\n' > "$DIR/malformed_no_path"
+printf '\r\n\r\n' > "$DIR/malformed_empty_line"
+printf 'GET / HTTP/1.1\r\nBadHeader\r\n\r\n' > "$DIR/malformed_no_colon"
+printf 'GET / HTTP/1.1\r\n: empty-name\r\n\r\n' > "$DIR/malformed_empty_header_name"
+printf 'GET / HTTP/1.1\r\nHost:\r\n\r\n' > "$DIR/malformed_empty_header_value"
+
+# No terminating \r\n\r\n
+printf 'GET / HTTP/1.1\r\nHost: localhost' > "$DIR/malformed_no_end"
+
+# Lots of headers
+python3 -c "
+import sys
+req = 'GET / HTTP/1.1\r\n'
+for i in range(100):
+    req += f'X-Header-{i}: value-{i}\r\n'
+req += '\r\n'
+sys.stdout.buffer.write(req.encode())
+" > "$DIR/many_headers"
+
+# Header injection / CRLF injection
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nX-Injected\r\nAuthorization: Basic AAAA\r\n\r\n' > "$DIR/header_injection"
+printf 'GET / HTTP/1.1\r\nHost: localhost%%0d%%0aInjected: true\r\n\r\n' > "$DIR/crlf_encoded"
+
+# Format strings in various positions
+printf 'GET /%%s%%s%%s%%n HTTP/1.1\r\nHost: %%x%%x%%x\r\nAuthorization: Basic %%n%%n\r\n\r\n' > "$DIR/fmt_everywhere"
+
+# More HTTP methods
+printf 'PUT /api HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_put"
+printf 'DELETE /api HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_delete"
+printf 'PATCH /api HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_patch"
+printf 'OPTIONS / HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_options"
+printf 'HEAD / HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_head"
+printf 'CONNECT localhost:443 HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_connect"
+printf 'TRACE / HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/method_trace"
+
+# HTTP version variations
+printf 'GET / HTTP/0.9\r\n\r\n' > "$DIR/http_09"
+printf 'GET / HTTP/2.0\r\nHost: localhost\r\n\r\n' > "$DIR/http_20"
+printf 'GET / HTTP/9.9\r\nHost: localhost\r\n\r\n' > "$DIR/http_99"
+
+# WebSocket upgrade (relevant to ttyd)
+printf 'GET /ws HTTP/1.1\r\nHost: localhost:7681\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n' > "$DIR/ws_upgrade"
+printf 'GET /ws HTTP/1.1\r\nHost: localhost:7681\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nOrigin: http://localhost:7681\r\nAuthorization: Basic YWRtaW46cGFzcw==\r\n\r\n' > "$DIR/ws_upgrade_auth"
+
+# Duplicate headers
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic YWRtaW46cGFzcw==\r\nAuthorization: Basic d3Jvbmc6d3Jvbmc=\r\n\r\n' > "$DIR/dup_auth_headers"
+printf 'GET / HTTP/1.1\r\nHost: evil.com\r\nHost: localhost\r\n\r\n' > "$DIR/dup_host_headers"
+printf 'GET / HTTP/1.1\r\nHost: localhost\r\nOrigin: http://evil.com\r\nOrigin: http://localhost\r\n\r\n' > "$DIR/dup_origin_headers"
+
+# Very long header names / values
+python3 -c "
+import sys
+name = 'X-' + 'A' * 300
+req = f'GET / HTTP/1.1\r\nHost: localhost\r\n{name}: value\r\n\r\n'
+sys.stdout.buffer.write(req.encode())
+" > "$DIR/long_header_name"
+
+python3 -c "
+import sys
+value = 'A' * 1000
+req = f'GET / HTTP/1.1\r\nHost: localhost\r\nX-Custom: {value}\r\n\r\n'
+sys.stdout.buffer.write(req.encode())
+" > "$DIR/long_header_value"
+
+# Request with body
+printf 'POST /token HTTP/1.1\r\nHost: localhost:7681\r\nContent-Length: 27\r\nContent-Type: application/json\r\n\r\n{"username":"admin","pass":"x"}' > "$DIR/post_with_body"
+
+# ttyd-specific paths
+printf 'GET /ws HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/path_ws"
+printf 'GET /token HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/path_token"
+printf 'GET /auth_token.js HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/path_auth_token_js"
+printf 'GET /favicon.ico HTTP/1.1\r\nHost: localhost:7681\r\n\r\n' > "$DIR/path_favicon"
+
+# Tab and space variations in request line
+printf 'GET\t/\tHTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/tab_separated"
+printf 'GET  /  HTTP/1.1\r\nHost: localhost\r\n\r\n' > "$DIR/double_space_request"
+
+# LF-only line endings (no CR)
+printf 'GET / HTTP/1.1\nHost: localhost\nAuthorization: Basic YWRtaW46cGFzcw==\n\n' > "$DIR/lf_only"
+
+echo "[+] Generated $(ls "$DIR" | wc -l) seeds for http_parsing"
+
+###############################################################################
+echo
+TOTAL=$(find "$SCRIPT_DIR/corpus" -type f | wc -l)
+echo "[+] Total corpus: $TOTAL seed files"
+echo "[+] Corpus directories:"
+echo "    corpus/auth_header/    ($(ls "$SCRIPT_DIR/corpus/auth_header" | wc -l) files)"
+echo "    corpus/websocket_auth/ ($(ls "$SCRIPT_DIR/corpus/websocket_auth" | wc -l) files)"
+echo "    corpus/http_parsing/   ($(ls "$SCRIPT_DIR/corpus/http_parsing" | wc -l) files)"
diff --git a/fuzzing/ttyd/run-fuzzers.sh b/fuzzing/ttyd/run-fuzzers.sh
@@ -0,0 +1,171 @@
+#!/bin/bash
+# Run all TTYD fuzzers in tmux sessions
+# Supports: dictionaries, CmpLog, parallel fuzzing
+# Safe to detach from SSH - sessions persist in background
+
+set -e
+
+SESSION="ttyd-fuzz"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+# Check dependencies
+if ! command -v tmux &> /dev/null; then
+    echo "[!] tmux not found. Install with: sudo apt-get install tmux"
+    exit 1
+fi
+
+if ! command -v afl-fuzz &> /dev/null; then
+    echo "[!] afl-fuzz not found. Install AFL++ first."
+    exit 1
+fi
+
+# Check that harnesses are built
+for harness in fuzz_auth_header fuzz_websocket_auth fuzz_http_parsing; do
+    if [ ! -x "$SCRIPT_DIR/$harness" ]; then
+        echo "[!] $harness not found. Run ./build_fuzzers.sh first."
+        exit 1
+    fi
+done
+
+# Generate corpus if missing
+for dir in auth_header websocket_auth http_parsing; do
+    if [ ! -d "$SCRIPT_DIR/corpus/$dir" ] || [ -z "$(ls -A "$SCRIPT_DIR/corpus/$dir" 2>/dev/null)" ]; then
+        echo "[*] Corpus missing. Run ./build_fuzzers.sh first to generate it."
+        exit 1
+    fi
+done
+
+# Find dictionaries
+DICT_DIR=""
+for candidate in /usr/share/afl++/dictionaries /usr/local/share/afl++/dictionaries "$SCRIPT_DIR/dictionaries"; do
+    if [ -d "$candidate" ]; then
+        DICT_DIR="$candidate"
+        break
+    fi
+done
+
+if [ -z "$DICT_DIR" ]; then
+    echo "[!] Warning: No AFL++ dictionaries directory found. Running without dictionaries."
+    echo "    Expected at: /usr/share/afl++/dictionaries/"
+fi
+
+# Calculate core allocation
+TOTAL_CORES=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
+CORES_PER_TARGET=$((TOTAL_CORES / 3))
+[ "$CORES_PER_TARGET" -lt 1 ] && CORES_PER_TARGET=1
+
+echo "[*] System: $TOTAL_CORES cores, $CORES_PER_TARGET per target"
+
+# Create output directories
+mkdir -p "$SCRIPT_DIR/output/auth_header" "$SCRIPT_DIR/output/websocket_auth" "$SCRIPT_DIR/output/http_parsing"
+
+# Kill existing session if running
+tmux kill-session -t "$SESSION" 2>/dev/null || true
+
+echo "[*] Starting fuzzing sessions in tmux session: $SESSION"
+echo
+
+# Build afl-fuzz command for a given target
+# Usage: build_afl_cmd <harness_name> <corpus_dir> <output_dir> <dict_file> <node_type> [extra_flags]
+build_afl_cmd() {
+    local harness=$1
+    local corpus=$2
+    local output=$3
+    local dict=$4
+    local node_flag=$5
+    shift 5
+    local extra_flags="$*"
+
+    local cmd="afl-fuzz -i $corpus -o $output $node_flag"
+
+    # Add dictionary if available
+    if [ -n "$DICT_DIR" ] && [ -f "$DICT_DIR/$dict" ]; then
+        cmd="$cmd -x $DICT_DIR/$dict"
+    fi
+
+    # Add any extra flags (e.g., -c for CmpLog)
+    if [ -n "$extra_flags" ]; then
+        cmd="$cmd $extra_flags"
+    fi
+
+    cmd="$cmd -- ./$harness"
+    echo "$cmd"
+}
+
+# Launch a fuzzing campaign for one target
+# Usage: launch_target <window_name> <harness_name> <corpus_dir_name> <dict_file>
+launch_target() {
+    local window_name=$1
+    local harness=$2
+    local corpus_dir=$3
+    local dict=$4
+    local output_dir="output/$corpus_dir"
+    local corpus_path="corpus/$corpus_dir"
+    local has_cmplog=false
+
+    if [ -x "$SCRIPT_DIR/${harness}_cmplog" ]; then
+        has_cmplog=true
+    fi
+
+    # Master node
+    local master_cmd
+    master_cmd=$(build_afl_cmd "$harness" "$corpus_path" "$output_dir" "$dict" "-M main")
+
+    if [ "$window_name" = "auth_header" ]; then
+        # First window - create the session
+        tmux new-session -d -s "$SESSION" -n "$window_name" \
+            "cd '$SCRIPT_DIR' && $master_cmd; read"
+    else
+        tmux new-window -t "$SESSION" -n "$window_name" \
+            "cd '$SCRIPT_DIR' && $master_cmd; read"
+    fi
+
+    local node_count=1
+
+    # CmpLog node (if cmplog binary exists)
+    if $has_cmplog && [ "$CORES_PER_TARGET" -ge 2 ]; then
+        local cmplog_cmd
+        cmplog_cmd=$(build_afl_cmd "$harness" "$corpus_path" "$output_dir" "$dict" "-S cmplog" "-c ./${harness}_cmplog")
+        tmux split-window -h -t "$SESSION:$window_name" \
+            "cd '$SCRIPT_DIR' && $cmplog_cmd; read"
+        node_count=$((node_count + 1))
+    fi
+
+    # Additional secondary nodes if cores permit
+    local remaining=$((CORES_PER_TARGET - node_count))
+    for i in $(seq 1 $remaining); do
+        local slave_cmd
+        slave_cmd=$(build_afl_cmd "$harness" "$corpus_path" "$output_dir" "$dict" "-S slave_$i")
+        tmux split-window -v -t "$SESSION:$window_name" \
+            "cd '$SCRIPT_DIR' && $slave_cmd; read"
+    done
+
+    echo "[+] $window_name: master + $([ $has_cmplog = true ] && [ $CORES_PER_TARGET -ge 2 ] && echo "cmplog + " || true)$((CORES_PER_TARGET - node_count)) secondary nodes"
+}
+
+# Launch the three campaigns
+launch_target "auth_header"    "fuzz_auth_header"    "auth_header"    "http.dict"
+launch_target "websocket_auth" "fuzz_websocket_auth" "websocket_auth" "json.dict"
+launch_target "http_parsing"   "fuzz_http_parsing"   "http_parsing"   "http.dict"
+
+# Add a status window
+tmux new-window -t "$SESSION" -n "status" \
+    "cd '$SCRIPT_DIR' && watch -n 5 'afl-whatsup -s output/ 2>/dev/null || echo \"=== Auth Header ===\"; cat output/auth_header/main/fuzzer_stats 2>/dev/null | grep -E \"(execs_done|execs_per_sec|saved_crashes|saved_hangs|last_find)\"; echo; echo \"=== WebSocket Auth ===\"; cat output/websocket_auth/main/fuzzer_stats 2>/dev/null | grep -E \"(execs_done|execs_per_sec|saved_crashes|saved_hangs|last_find)\"; echo; echo \"=== HTTP Parsing ===\"; cat output/http_parsing/main/fuzzer_stats 2>/dev/null | grep -E \"(execs_done|execs_per_sec|saved_crashes|saved_hangs|last_find)\"'"
+
+# Select first window
+tmux select-window -t "$SESSION:0"
+
+echo
+echo "[+] All fuzzers started in tmux session: $SESSION"
+echo
+echo "Commands:"
+echo "  tmux attach -t $SESSION          # Attach to session"
+echo "  tmux select-window -t $SESSION:0 # auth_header"
+echo "  tmux select-window -t $SESSION:1 # websocket_auth"
+echo "  tmux select-window -t $SESSION:2 # http_parsing"
+echo "  tmux select-window -t $SESSION:3 # status overview"
+echo "  Ctrl-b d                         # Detach (fuzzers keep running)"
+echo "  tmux kill-session -t $SESSION    # Stop all fuzzers"
+echo
+echo "[*] You can safely disconnect from SSH. Fuzzers will keep running."
+echo "[*] Crashes will be saved in output/*/main/crashes/"