etc.git


etc

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

commit 7a8ec4aa46de96798dc7556dbe9e8836b0de4f99
parent 8f30d55cec95c0fde4ccd57d7fb4d5a9cb0567e5
Author: djnn <[email protected]>
Date: Wed, 18 Feb 2026 19:47:45 +0900

feat(ttyd): smarter fuzzing

Diffstat:
Mfuzzing/ttyd/build_fuzzers.sh | 1062+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mfuzzing/ttyd/clean.sh | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mfuzzing/ttyd/fuzz_auth_header.c | 237+++++++++++++++++++++++++------------------------------------------------------
Mfuzzing/ttyd/fuzz_websocket_auth.c | 214+++++++++++++++++++++++++++----------------------------------------------------
Dfuzzing/ttyd/gen-corpus.sh | 346-------------------------------------------------------------------------------

5 files changed, 1178 insertions(+), 766 deletions(-)

diff --git a/fuzzing/ttyd/build_fuzzers.sh b/fuzzing/ttyd/build_fuzzers.sh
@@ -1,170 +1,1028 @@
 #!/bin/bash
-# Build script for TTYD authentication fuzzing harnesses
+# Build TTYD fuzzing harnesses
+#
+# What this script does:
+#   1. Installs build dependencies
+#   2. Clones ttyd source + runs cmake (for validation & compile_commands.json)
+#   3. Generates mock headers/stubs so ttyd's .c files compile without real lws/uv
+#   4. Compiles instrumented ttyd objects (http.c, protocol.c, utils.c)
+#   5. Builds AFL++ harnesses that #include the real ttyd source files
+#   6. Sets up AFL++ dictionaries (download from AFL++ repo, fall back to custom)
+#   7. Generates seed corpus
+#   8. Installs a crontab entry to back up crashes every 15 minutes
+#
+# Usage: ./build_fuzzers.sh [afl|libfuzzer]
+#
+# Note: gen-corpus.sh is merged into this script and is no longer separate.
 
 set -e
 
-MODE="${1:-afl}"  # afl or libfuzzer
-SANITIZER="address"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+MODE="${1:-afl}"
+SANITIZER="address,undefined"
+
+TTYD_SRC="$SCRIPT_DIR/ttyd-src"
+TTYD_OBJ="$SCRIPT_DIR/ttyd-obj"
+FUZZ_INCLUDE="$SCRIPT_DIR/fuzz-include"
+CRONTAB_MARKER="# ttyd-fuzz-crash-backup"
+BACKUP_SCRIPT="$SCRIPT_DIR/backup_crashes.sh"
 
 echo "[*] Building TTYD fuzzing harnesses"
-echo "[*] Mode: $MODE"
+echo "[*] Mode:      $MODE"
 echo "[*] Sanitizer: $SANITIZER"
+echo "[*] Script:    $SCRIPT_DIR"
 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
+# ─── 1. dependency install ────────────────────────────────────────────────────
+
+install_deps() {
+    if [ "$(id -u)" -ne 0 ]; then
+        echo "[!] Warning: Not running as root. Skipping apt-get installs."
+        echo "    If builds fail, run: sudo apt-get install afl++ libjson-c-dev"
+        echo "    cmake git libwebsockets-dev libuv1-dev pkg-config"
+        return
+    fi
+
+    local DEPS="libjson-c-dev cmake git pkg-config"
+    if [ "$MODE" = "afl" ]; then
+        DEPS="$DEPS afl++"
+    else
+        DEPS="$DEPS clang"
+    fi
+
+    local MISSING=""
+    for pkg in $DEPS; do
+        dpkg -s "$pkg" &>/dev/null || MISSING="$MISSING $pkg"
+    done
+
+    if [ -n "$MISSING" ]; then
+        echo "[*] Installing:$MISSING"
+        apt-get install -y --no-install-recommends $MISSING
         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++..."
+    # System tuning for AFL++
+    if [ "$MODE" = "afl" ]; then
+        local CORE_PATTERN
+        CORE_PATTERN=$(cat /proc/sys/kernel/core_pattern 2>/dev/null || echo "")
+        if [[ "$CORE_PATTERN" == "|"* ]]; then
+            echo core > /proc/sys/kernel/core_pattern
+            echo "[+] core_pattern set to 'core'"
+        fi
+        for gov in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
+            [ -f "$gov" ] && echo performance > "$gov" 2>/dev/null || true
+        done
+    fi
+}
+
+# ─── 2. clone + cmake ttyd ────────────────────────────────────────────────────
 
-    # 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"
+setup_ttyd_source() {
+    # Clone
+    if [ ! -d "$TTYD_SRC/.git" ]; then
+        echo "[*] Cloning ttyd source..."
+        git clone --depth=1 https://github.com/tsl0922/ttyd "$TTYD_SRC"
+        echo "[+] Cloned ttyd to $TTYD_SRC"
     else
-        echo "[+] core_pattern already OK: $CORE_PATTERN"
+        echo "[*] ttyd source already present — pulling latest..."
+        git -C "$TTYD_SRC" pull --ff-only 2>/dev/null || \
+            echo "[!] Pull skipped (uncommitted local changes or offline)"
     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
+    # cmake configure (validates deps, produces compile_commands.json for IDEs)
+    local BUILD_DIR="$TTYD_SRC/build"
+    mkdir -p "$BUILD_DIR"
+    echo "[*] Running cmake configure..."
+    (
+        cd "$BUILD_DIR"
+        # Use the real compiler here (not AFL++) — this is only for validation/headers
+        cmake -DCMAKE_BUILD_TYPE=Debug \
+              -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+              .. 2>&1 | tail -5 || true
+    )
+    echo "[+] cmake done (compile_commands.json at $BUILD_DIR/compile_commands.json)"
+
+    # ── generate mock headers ────────────────────────────────────────────────
+    mkdir -p "$FUZZ_INCLUDE"
+
+    # fuzz-include/libwebsockets.h  — replaces the real lws header when we
+    # compile ttyd source files.  Covers all types/constants/functions used
+    # by http.c and protocol.c.  Implementations live in fuzz_lws_mock.c.
+    cat > "$FUZZ_INCLUDE/libwebsockets.h" <<'EOF'
+#pragma once
+/* Mock libwebsockets header for AFL++ fuzzing of ttyd.
+ * Provides all types and function declarations used by http.c / protocol.c.
+ * Runtime implementations are in fuzz_lws_mock.c.                         */
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+/* ── opaque types ──────────────────────────────────────────────────────── */
+struct lws           { int _fd; };
+struct lws_context   { int _dummy; };
+struct lws_vhost     { int _dummy; };
+
+typedef struct lws           lws_t;
+typedef struct lws_context   lws_context;
+
+/* ── callback reasons ──────────────────────────────────────────────────── */
+typedef enum lws_callback_reasons {
+    LWS_CALLBACK_HTTP = 0,
+    LWS_CALLBACK_HTTP_WRITEABLE,
+    LWS_CALLBACK_HTTP_FILE_COMPLETION,
+    LWS_CALLBACK_HTTP_BIND_PROTOCOL,
+    LWS_CALLBACK_FILTER_HTTP_CONNECTION,
+    LWS_CALLBACK_CLOSED_HTTP,
+    LWS_CALLBACK_ADD_HEADERS,
+    LWS_CALLBACK_CHECK_ACCESS_RIGHTS,
+    LWS_CALLBACK_PROCESS_HTML,
+    LWS_CALLBACK_ESTABLISHED = 100,
+    LWS_CALLBACK_CLOSED,
+    LWS_CALLBACK_RECEIVE,
+    LWS_CALLBACK_SERVER_WRITEABLE,
+    LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
+    LWS_CALLBACK_PROTOCOL_INIT,
+    LWS_CALLBACK_PROTOCOL_DESTROY,
+    LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
+    LWS_CALLBACK_SESSION_INFO,
+    _LWS_CALLBACK_DUMMY = 9999
+} lws_callback_reasons;
+
+/* ── write protocols ───────────────────────────────────────────────────── */
+typedef enum lws_write_protocol {
+    LWS_WRITE_TEXT = 0,
+    LWS_WRITE_BINARY,
+    LWS_WRITE_CONTINUATION,
+    LWS_WRITE_HTTP,
+    LWS_WRITE_PING,
+    LWS_WRITE_PONG,
+    LWS_WRITE_CLOSE,
+    LWS_WRITE_HTTP_HEADERS,
+    LWS_WRITE_HTTP_FINAL_HEADERS,
+    _LWS_WRITE_DUMMY = 9999
+} lws_write_protocol;
+
+/* ── token indexes ─────────────────────────────────────────────────────── */
+typedef enum lws_token_indexes {
+    WSI_TOKEN_GET_URI = 0,
+    WSI_TOKEN_POST_URI,
+    WSI_TOKEN_OPTIONS_URI,
+    WSI_TOKEN_HOST,
+    WSI_TOKEN_CONNECTION,
+    WSI_TOKEN_UPGRADE,
+    WSI_TOKEN_ORIGIN,
+    WSI_TOKEN_HTTP,
+    WSI_TOKEN_HTTP_ACCEPT,
+    WSI_TOKEN_HTTP_AUTHORIZATION,
+    WSI_TOKEN_HTTP_COOKIE,
+    WSI_TOKEN_HTTP_CONTENT_LENGTH,
+    WSI_TOKEN_HTTP_CONTENT_TYPE,
+    WSI_TOKEN_HTTP_RANGE,
+    WSI_TOKEN_HTTP_REFERER,
+    WSI_TOKEN_HTTP_COLON_AUTHORITY,
+    WSI_TOKEN_HTTP_COLON_METHOD,
+    WSI_TOKEN_HTTP_COLON_PATH,
+    WSI_TOKEN_HTTP_COLON_SCHEME,
+    WSI_TOKEN_HTTP_ACCEPT_ENCODING,
+    WSI_TOKEN_HTTP_ACCEPT_LANGUAGE,
+    WSI_TOKEN_HTTP_PRAGMA,
+    WSI_TOKEN_HTTP_CACHE_CONTROL,
+    WSI_TOKEN_HTTP_IF_MODIFIED_SINCE,
+    WSI_TOKEN_HTTP_IF_NONE_MATCH,
+    WSI_TOKEN_HTTP_TRANSFER_ENCODING,
+    WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
+    WSI_TOKEN_HTTP_PROXY_AUTHENTICATE,
+    WSI_TOKEN_HTTP_PROXY_AUTHORIZATION,
+    WSI_TOKEN_HTTP_X_FORWARDED_FOR,
+    WSI_TOKEN_HTTP_URI_ARGS,
+    WSI_TOKEN_COLON_STATUS,
+    WSI_TOKEN_HTTP_ACCEPT_RANGES,
+    WSI_TOKEN_HTTP_LOCATION,
+    WSI_TOKEN_HTTP_CONTENT_RANGE,
+    WSI_TOKEN_HTTP_SET_COOKIE,
+    WSI_TOKEN_HTTP_CONTENT_DISPOSITION,
+    WSI_TOKEN_HTTP_CONTENT_ENCODING,
+    WSI_TOKEN_HTTP_CONTENT_LANGUAGE,
+    WSI_TOKEN_HTTP_CONTENT_LOCATION,
+    ARRAY_SIZE_WSI_TOKENS,
+} lws_token_indexes;
+
+/* ── HTTP status codes ─────────────────────────────────────────────────── */
+#define HTTP_STATUS_OK                    200
+#define HTTP_STATUS_NO_CONTENT            204
+#define HTTP_STATUS_PARTIAL_CONTENT       206
+#define HTTP_STATUS_MOVED_PERMANENTLY     301
+#define HTTP_STATUS_FOUND                 302
+#define HTTP_STATUS_NOT_MODIFIED          304
+#define HTTP_STATUS_BAD_REQUEST           400
+#define HTTP_STATUS_UNAUTHORIZED          401
+#define HTTP_STATUS_FORBIDDEN             403
+#define HTTP_STATUS_NOT_FOUND             404
+#define HTTP_STATUS_PROXY_AUTH_REQUIRED   407
+#define HTTP_STATUS_INTERNAL_SERVER_ERROR 500
+#define HTTP_STATUS_NOT_IMPLEMENTED       501
+
+/* ── misc types ────────────────────────────────────────────────────────── */
+typedef uint64_t lws_filepos_t;
+
+/* ── misc constants ────────────────────────────────────────────────────── */
+#define LWS_PRE 32
+#define LWS_CLOSE_STATUS_NOSTATUS               0
+#define LWS_CLOSE_STATUS_NORMAL                 1000
+#define LWS_CLOSE_STATUS_POLICY_VIOLATION       1008
+#define LWS_CLOSE_STATUS_UNEXPECTED_CONDITION   1011
+
+typedef enum pending_timeout {
+    NO_PENDING_TIMEOUT = 0,
+    PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
+    _PENDING_TIMEOUT_DUMMY = 100
+} pending_timeout;
+
+/* ── protocol registration (used in server.c, not in fuzzing target) ─── */
+typedef int (*lws_callback_function)(struct lws *wsi,
+                                     enum lws_callback_reasons reason,
+                                     void *user, void *in, size_t len);
+struct lws_protocols {
+    const char            *name;
+    lws_callback_function  callback;
+    size_t                 per_session_data_size;
+    size_t                 rx_buffer_size;
+    unsigned int           id;
+    void                  *user;
+    size_t                 tx_packet_size;
+};
+
+struct lws_http_mount     { int _dummy; };
+struct lws_context_creation_info { int _dummy; };
+
+/* ── logging macros ────────────────────────────────────────────────────── */
+#define lwsl_err(fmt, ...)    fprintf(stderr, "ERR: " fmt, ##__VA_ARGS__)
+#define lwsl_warn(fmt, ...)   fprintf(stderr, "WRN: " fmt, ##__VA_ARGS__)
+#define lwsl_notice(fmt, ...) do {} while (0)
+#define lwsl_info(fmt, ...)   do {} while (0)
+#define lwsl_debug(fmt, ...)  do {} while (0)
+#define lwsl_user(fmt, ...)   do {} while (0)
+
+/* ── function declarations (implementations in fuzz_lws_mock.c) ────────── */
+int    lws_hdr_copy(struct lws *wsi, char *dest, int len,
+                    enum lws_token_indexes h);
+int    lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen);
+int    lws_hdr_custom_copy(struct lws *wsi, char *dst, int len,
+                            const char *name, int nlen);
+int    lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len,
+                              enum lws_token_indexes h, int frag_idx);
+int    lws_return_http_status(struct lws *wsi, unsigned int code,
+                               const char *html_body);
+int    lws_add_http_header_status(struct lws *wsi, unsigned int code,
+                                   unsigned char **p, unsigned char *end);
+int    lws_add_http_header_content_length(struct lws *wsi,
+                                           lws_filepos_t content_length,
+                                           unsigned char **p,
+                                           unsigned char *end);
+int    lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes h,
+                                    const unsigned char *value, int length,
+                                    unsigned char **p, unsigned char *end);
+int    lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
+                                   const unsigned char *value, int length,
+                                   unsigned char **p, unsigned char *end);
+int    lws_finalize_http_header(struct lws *wsi, unsigned char **p,
+                                 unsigned char *end);
+int    lws_finalize_write_http_header(struct lws *wsi, unsigned char *start,
+                                       unsigned char **p, unsigned char *end);
+int    lws_write(struct lws *wsi, unsigned char *buf, size_t len,
+                 enum lws_write_protocol protocol);
+int    lws_callback_on_writable(struct lws *wsi);
+void   lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs);
+void   lws_close_reason(struct lws *wsi, int status,
+                         unsigned char *buf, size_t len);
+struct lws_context *lws_get_context(const struct lws *wsi);
+int    lws_parse_uri(char *p, const char **prot, const char **host,
+                     int *port, const char **path);
+int    lws_is_final_fragment(struct lws *wsi);
+size_t lws_remaining_packet_payload(struct lws *wsi);
+int    lws_get_peer_simple(struct lws *wsi, char *name, size_t namelen);
+int    lws_serve_http_file(struct lws *wsi, const char *file,
+                            const char *content_type,
+                            const char *other_headers, int other_headers_len);
+int    lws_http_transaction_completed(struct lws *wsi);
+struct lws_context *lws_create_context(struct lws_context_creation_info *info);
+void   lws_context_destroy(struct lws_context *context);
+int    lws_service(struct lws_context *context, int timeout_ms);
+void   lws_cancel_service(struct lws_context *context);
+void  *lws_wsi_user(struct lws *wsi);
+struct lws *lws_get_network_wsi(struct lws *wsi);
+int    lws_get_peer_write_allowance(struct lws *wsi);
+int    lws_write_http(struct lws *wsi, void *buf, size_t len);
+int    lws_send_pipe_choked(struct lws *wsi);
+EOF
+    echo "[+] Generated fuzz-include/libwebsockets.h"
+
+    # fuzz-include/uv.h  — minimal libuv stubs (server.h and pty.h use uv types)
+    cat > "$FUZZ_INCLUDE/uv.h" <<'EOF'
+#pragma once
+/* Minimal libuv stub for fuzzing ttyd — only types, no event loop. */
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+typedef struct uv_loop_s       uv_loop_t;
+typedef struct uv_handle_s     uv_handle_t;
+typedef struct uv_async_s      uv_async_t;
+typedef struct uv_pipe_s       uv_pipe_t;
+typedef struct uv_timer_s      uv_timer_t;
+typedef struct uv_thread_s     uv_thread_t;
+typedef struct uv_mutex_s      uv_mutex_t;
+typedef struct uv_process_s    uv_process_t;
+typedef struct uv_signal_s     uv_signal_t;
+
+struct uv_loop_s   { void *data; };
+struct uv_handle_s { void *data; uv_loop_t *loop; };
+struct uv_async_s  { void *data; uv_loop_t *loop; };
+struct uv_pipe_s   { void *data; uv_loop_t *loop; };
+struct uv_timer_s  { void *data; uv_loop_t *loop; };
+struct uv_thread_s { unsigned long _tid; };
+struct uv_mutex_s  { int _m; };
+struct uv_process_s{ void *data; };
+struct uv_signal_s { void *data; };
+
+typedef void (*uv_close_cb)(uv_handle_t *handle);
+typedef void (*uv_async_cb)(uv_async_t *handle);
+typedef void (*uv_alloc_cb)(uv_handle_t *handle, size_t suggested_size, void *buf);
+typedef void (*uv_read_cb)(uv_pipe_t *stream, ssize_t nread, const void *buf);
+typedef void (*uv_exit_cb)(uv_process_t *process, int64_t exit_status, int term_signal);
+typedef void (*uv_walk_cb)(uv_handle_t *handle, void *arg);
+typedef void (*uv_thread_cb)(void *arg);
+typedef void (*uv_timer_cb)(uv_timer_t *handle);
+
+#define UV_RUN_DEFAULT 0
+#define UV_RUN_ONCE    1
+#define UV_RUN_NOWAIT  2
+
+static inline int   uv_loop_init(uv_loop_t *l)               { return 0; }
+static inline int   uv_run(uv_loop_t *l, int mode)            { return 0; }
+static inline void  uv_stop(uv_loop_t *l)                     {}
+static inline int   uv_loop_close(uv_loop_t *l)               { return 0; }
+static inline void  uv_close(uv_handle_t *h, uv_close_cb cb)  {}
+static inline int   uv_async_init(uv_loop_t *l, uv_async_t *a, uv_async_cb cb) { a->loop = l; return 0; }
+static inline int   uv_async_send(uv_async_t *a)              { return 0; }
+static inline int   uv_timer_init(uv_loop_t *l, uv_timer_t *t){ return 0; }
+static inline int   uv_timer_start(uv_timer_t *t, uv_timer_cb cb, uint64_t timeout, uint64_t repeat) { return 0; }
+static inline int   uv_timer_stop(uv_timer_t *t)              { return 0; }
+static inline int   uv_mutex_init(uv_mutex_t *m)              { return 0; }
+static inline void  uv_mutex_lock(uv_mutex_t *m)              {}
+static inline void  uv_mutex_unlock(uv_mutex_t *m)            {}
+static inline void  uv_mutex_destroy(uv_mutex_t *m)           {}
+static inline int   uv_thread_create(uv_thread_t *t, uv_thread_cb cb, void *arg) { return 0; }
+static inline int   uv_thread_join(uv_thread_t *t)            { return 0; }
+static inline uv_loop_t *uv_default_loop(void)                { return NULL; }
+static inline int          uv_walk(uv_loop_t *l, uv_walk_cb cb, void *arg) { return 0; }
+static inline const char  *uv_err_name(int err)   { return "UNKNOWN"; }
+static inline const char  *uv_strerror(int err)   { return "unknown error"; }
+EOF
+    echo "[+] Generated fuzz-include/uv.h"
+
+    # fuzz_lws_mock.h  — declares fuzzer-controlled lws state, included by harnesses
+    cat > "$SCRIPT_DIR/fuzz_lws_mock.h" <<'EOF'
+#pragma once
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Fuzzer-controlled header values.  Set these before calling any ttyd
+ * function that reads HTTP headers; the mock lws_hdr_* functions return
+ * whatever is stored here.                                                 */
+extern char _fuzz_auth_header[4096];
+extern int  _fuzz_auth_header_len;
+extern char _fuzz_custom_header[4096];
+extern int  _fuzz_custom_header_len;
+
+/* Reset all mock state — call at the start of every fuzz iteration. */
+void fuzz_lws_reset(void);
+
+/* Convenience setters. */
+static inline void fuzz_lws_set_auth(const char *v, int len) {
+    int n = len < 4095 ? len : 4095;
+    memcpy(_fuzz_auth_header, v, n);
+    _fuzz_auth_header[n] = '\0';
+    _fuzz_auth_header_len = n;
+}
+static inline void fuzz_lws_set_custom(const char *v, int len) {
+    int n = len < 4095 ? len : 4095;
+    memcpy(_fuzz_custom_header, v, n);
+    _fuzz_custom_header[n] = '\0';
+    _fuzz_custom_header_len = n;
+}
+EOF
+    echo "[+] Generated fuzz_lws_mock.h"
+
+    # fuzz_lws_mock.c  — runtime implementations of the mocked lws functions
+    cat > "$SCRIPT_DIR/fuzz_lws_mock.c" <<'EOF'
+/* Mock implementations of libwebsockets runtime functions for fuzzing ttyd.
+ * lws_hdr_copy / lws_hdr_custom_* return fuzzer-controlled data so AFL++
+ * can drive the auth logic.  All other lws functions are safe no-ops.      */
+
+#include "fuzz-include/libwebsockets.h"
+#include "fuzz_lws_mock.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+
+/* ── fuzzer-controlled state ─────────────────────────────────────────── */
+char _fuzz_auth_header[4096];
+int  _fuzz_auth_header_len;
+char _fuzz_custom_header[4096];
+int  _fuzz_custom_header_len;
+
+void fuzz_lws_reset(void) {
+    memset(_fuzz_auth_header,   0, sizeof(_fuzz_auth_header));
+    _fuzz_auth_header_len   = 0;
+    memset(_fuzz_custom_header, 0, sizeof(_fuzz_custom_header));
+    _fuzz_custom_header_len = 0;
+}
+
+/* ── header accessors ────────────────────────────────────────────────── */
+int lws_hdr_copy(struct lws *wsi, char *dest, int len,
+                 enum lws_token_indexes h) {
+    const char *src = NULL;
+    int srclen = 0;
+    if (h == WSI_TOKEN_HTTP_AUTHORIZATION) {
+        src    = _fuzz_auth_header;
+        srclen = _fuzz_auth_header_len;
+    }
+    if (!src || !dest || len <= 0) { if (dest && len > 0) dest[0] = '\0'; return 0; }
+    int n = srclen < len - 1 ? srclen : len - 1;
+    memcpy(dest, src, n);
+    dest[n] = '\0';
+    return n;
+}
+
+int lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen) {
+    return _fuzz_custom_header_len;
+}
+
+int lws_hdr_custom_copy(struct lws *wsi, char *dst, int len,
+                         const char *name, int nlen) {
+    if (!dst || len <= 0) return 0;
+    int n = _fuzz_custom_header_len < len - 1 ? _fuzz_custom_header_len : len - 1;
+    memcpy(dst, _fuzz_custom_header, n);
+    dst[n] = '\0';
+    return n;
+}
+
+int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len,
+                           enum lws_token_indexes h, int frag_idx) {
+    if (dst && len > 0) dst[0] = '\0';
+    return 0;
+}
+
+/* ── URI parsing ─────────────────────────────────────────────────────── */
+int lws_parse_uri(char *p, const char **prot, const char **host,
+                  int *port, const char **path) {
+    if (prot)  *prot  = "";
+    if (host)  *host  = "";
+    if (port)  *port  = 0;
+    if (path)  *path  = p ? p : "";
+    return 0;
+}
+
+/* ── HTTP response helpers — all no-ops for fuzzing ─────────────────── */
+int lws_return_http_status(struct lws *wsi, unsigned int code,
+                            const char *html_body) { return 0; }
+int lws_add_http_header_status(struct lws *wsi, unsigned int code,
+                                unsigned char **p, unsigned char *end) { return 0; }
+int lws_add_http_header_content_length(struct lws *wsi, lws_filepos_t content_length,
+                                        unsigned char **p, unsigned char *end) { return 0; }
+int lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes h,
+                                  const unsigned char *v, int len,
+                                  unsigned char **p, unsigned char *end) { return 0; }
+int lws_add_http_header_by_name(struct lws *wsi, const unsigned char *n,
+                                 const unsigned char *v, int len,
+                                 unsigned char **p, unsigned char *end) { return 0; }
+int lws_finalize_http_header(struct lws *wsi, unsigned char **p,
+                              unsigned char *end) { return 0; }
+int lws_finalize_write_http_header(struct lws *wsi, unsigned char *start,
+                                    unsigned char **p, unsigned char *end) { return 0; }
+int lws_write(struct lws *wsi, unsigned char *buf, size_t len,
+              enum lws_write_protocol protocol) { return (int)len; }
+int lws_callback_on_writable(struct lws *wsi) { return 0; }
+void lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) {}
+void lws_close_reason(struct lws *wsi, int status,
+                       unsigned char *buf, size_t len) {}
+struct lws_context *lws_get_context(const struct lws *wsi) { return NULL; }
+int lws_is_final_fragment(struct lws *wsi) { return 1; }
+size_t lws_remaining_packet_payload(struct lws *wsi) { return 0; }
+int lws_get_peer_simple(struct lws *wsi, char *name, size_t namelen) {
+    if (name && namelen > 0) { strncpy(name, "127.0.0.1", namelen - 1); name[namelen-1] = '\0'; }
+    return 0;
+}
+int lws_serve_http_file(struct lws *wsi, const char *file,
+                         const char *content_type, const char *other_headers,
+                         int other_headers_len) { return 0; }
+int lws_http_transaction_completed(struct lws *wsi) { return 0; }
+struct lws_context *lws_create_context(struct lws_context_creation_info *i) { return NULL; }
+void lws_context_destroy(struct lws_context *ctx) {}
+int lws_service(struct lws_context *ctx, int timeout_ms) { return 0; }
+void lws_cancel_service(struct lws_context *ctx) {}
+void *lws_wsi_user(struct lws *wsi) { return NULL; }
+struct lws *lws_get_network_wsi(struct lws *wsi) { return wsi; }
+int lws_get_peer_write_allowance(struct lws *wsi) { return -1; }
+int lws_write_http(struct lws *wsi, void *buf, size_t len) { return (int)len; }
+int lws_send_pipe_choked(struct lws *wsi) { return 0; }
+EOF
+    echo "[+] Generated fuzz_lws_mock.c"
+
+    # mock_server_globals.c  — provides the global `struct server *server`
+    # expected by http.c and protocol.c
+    cat > "$SCRIPT_DIR/mock_server_globals.c" <<'EOF'
+/* Provides globals that ttyd's http.c and protocol.c reference.
+ * Normally defined in main.c; here we provide stub definitions.
+ * Fuzzing harnesses call fuzz_server_reset() before each iteration.       */
+
+#include "fuzz-include/libwebsockets.h"
+#include "fuzz-include/uv.h"
+#include "ttyd-src/src/server.h"
+#include <string.h>
+
+/* Globals declared extern in server.h */
+struct server        *server    = NULL;
+struct lws_context   *context   = NULL;
+volatile bool         force_exit = false;
+struct endpoints      endpoints  = {"/ws", "/", "/token", "/"};
+
+/* Fuzzer-private server instance */
+struct server  _fuzz_server;
+
+void fuzz_server_reset(void) {
+    memset(&_fuzz_server, 0, sizeof(_fuzz_server));
+    _fuzz_server.writable      = true;
+    _fuzz_server.check_origin  = false;
+    _fuzz_server.max_clients   = 0;
+    server    = &_fuzz_server;
+    context   = NULL;
+    force_exit = false;
+}
+EOF
+    echo "[+] Generated mock_server_globals.c"
+
+    # mock_pty_stubs.c  — stubs for PTY functions called by protocol.c
+    # Prevents actual process spawning during fuzzing
+    cat > "$SCRIPT_DIR/mock_pty_stubs.c" <<'EOF'
+/* Stub implementations of PTY functions used by ttyd's protocol.c.
+ * All functions are no-ops so no real processes are ever launched during
+ * fuzzing.  Signatures match pty.h exactly.                               */
+
+#include "fuzz-include/libwebsockets.h"
+#include "fuzz-include/uv.h"
+#include "ttyd-src/src/server.h"
+#include "ttyd-src/src/pty.h"
+#include <stdlib.h>
+#include <string.h>
+
+pty_buf_t *pty_buf_init(char *base, size_t len)         { return NULL; }
+void       pty_buf_free(pty_buf_t *buf)                  {}
+pty_process *process_init(void *ctx, uv_loop_t *loop,
+                           char *argv[], char *envp[])   { return NULL; }
+bool       process_running(pty_process *process)         { return false; }
+void       process_free(pty_process *process)            {}
+int        pty_spawn(pty_process *process,
+                     pty_read_cb read_cb,
+                     pty_exit_cb exit_cb)                { return -1; }
+void       pty_pause(pty_process *process)               {}
+void       pty_resume(pty_process *process)              {}
+int        pty_write(pty_process *process,
+                     pty_buf_t *buf)                     { return -1; }
+bool       pty_resize(pty_process *process)              { return false; }
+bool       pty_kill(pty_process *process, int sig)       { return false; }
+EOF
+    echo "[+] Generated mock_pty_stubs.c"
+
+    # ── compile instrumented ttyd objects ────────────────────────────────
+    mkdir -p "$TTYD_OBJ"
+    local JSON_CFLAGS
+    JSON_CFLAGS=$(pkg-config --cflags json-c 2>/dev/null || echo "")
+
+    local OBJ_CC OBJ_CFLAGS
+    if [ "$MODE" = "afl" ]; then
+        OBJ_CC="afl-clang-fast"
+    else
+        OBJ_CC="clang"
     fi
+    OBJ_CFLAGS="-fsanitize=$SANITIZER -g -O1 -D_GNU_SOURCE -std=gnu99"
+    OBJ_CFLAGS="$OBJ_CFLAGS -I$FUZZ_INCLUDE -I$TTYD_SRC/src $JSON_CFLAGS"
+    # Suppress warnings from upstream code we don't control
+    OBJ_CFLAGS="$OBJ_CFLAGS -Wno-implicit-function-declaration -Wno-int-conversion"
 
     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
+    echo "[*] Compiling instrumented ttyd objects..."
+    # Only utils.c is pre-compiled as a shared object.
+    # http.c and protocol.c are compiled directly into each harness via
+    # #include — pre-compiling them here would cause multiple-definition
+    # linker errors.
+    for src in utils; do
+        echo "    $src.c → ttyd-obj/$src.o"
+        $OBJ_CC $OBJ_CFLAGS -c "$TTYD_SRC/src/$src.c" -o "$TTYD_OBJ/$src.o"
+    done
+
+    # Mock objects
+    for src in fuzz_lws_mock mock_server_globals mock_pty_stubs; do
+        echo "    $src.c → ttyd-obj/$src.o"
+        $OBJ_CC $OBJ_CFLAGS -c "$SCRIPT_DIR/$src.c" -o "$TTYD_OBJ/$src.o"
+    done
+
+    echo "[+] ttyd objects compiled to $TTYD_OBJ/"
+}
+
+# ─── 3. dictionaries ──────────────────────────────────────────────────────────
+
+setup_dictionaries() {
+    local DICT_DIR="$SCRIPT_DIR/dictionaries"
+    mkdir -p "$DICT_DIR"
+
+    # Try system AFL++ dictionaries first
+    for sys_dir in /usr/share/afl++/dictionaries /usr/local/share/afl++/dictionaries; do
+        if [ -d "$sys_dir" ]; then
+            [ -f "$sys_dir/http.dict" ] && [ ! -f "$DICT_DIR/http.dict" ] && \
+                cp "$sys_dir/http.dict" "$DICT_DIR/http.dict" && \
+                echo "[+] Copied http.dict from $sys_dir"
+            [ -f "$sys_dir/json.dict" ] && [ ! -f "$DICT_DIR/json.dict" ] && \
+                cp "$sys_dir/json.dict" "$DICT_DIR/json.dict" && \
+                echo "[+] Copied json.dict from $sys_dir"
+            break
+        fi
+    done
+
+    # Download from AFL++ GitHub if still missing (http.dict and json.dict
+    # ship under those exact names in the AFL++ stable branch)
+    local BASE="https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/dictionaries"
+    local DL=""
+    command -v wget  &>/dev/null && DL="wget -q -O"
+    command -v curl  &>/dev/null && [ -z "$DL" ] && DL="curl -fsSL -o"
+
+    for dict in http json; do
+        if [ ! -f "$DICT_DIR/$dict.dict" ] && [ -n "$DL" ]; then
+            echo "[*] Downloading $dict.dict from AFL++ repo..."
+            $DL "$DICT_DIR/$dict.dict" "$BASE/$dict.dict" && \
+                echo "[+] Downloaded $dict.dict" || \
+                rm -f "$DICT_DIR/$dict.dict"
+        fi
+    done
+
+    # Minimal fallback if still missing
+    if [ ! -f "$DICT_DIR/http.dict" ]; then
+        cat > "$DICT_DIR/http.dict" <<'DICT'
+"Basic "
+"Bearer "
+"Authorization: Basic "
+"Origin: "
+"Host: "
+"GET "
+"POST "
+"HTTP/1.1"
+"\r\n\r\n"
+"X-Auth-User"
+"Upgrade: websocket"
+"Sec-WebSocket-Version: 13"
+DICT
+        echo "[+] Created dictionaries/http.dict (fallback)"
+    fi
+
+    if [ ! -f "$DICT_DIR/json.dict" ]; then
+        cat > "$DICT_DIR/json.dict" <<'DICT'
+"{"
+"}"
+"null"
+"true"
+"false"
+"\"columns\":"
+"\"rows\":"
+"\"AuthToken\":"
+DICT
+        echo "[+] Created dictionaries/json.dict (fallback)"
+    fi
+
+    echo "[+] Dictionaries: $(ls "$DICT_DIR")"
+}
+
+# ─── 4. corpus generation (formerly gen-corpus.sh) ───────────────────────────
+
+generate_corpus() {
+    mkdir -p "$SCRIPT_DIR/corpus/auth_header"
+    mkdir -p "$SCRIPT_DIR/corpus/websocket_auth"
+    mkdir -p "$SCRIPT_DIR/corpus/http_parsing"
+
+    # ── auth_header ──────────────────────────────────────────────────────────
+    # Format: [mode:1][credential_len:2 BE][credential][auth_header_value]
+    echo "[*] Generating corpus/auth_header/"
+    local D="$SCRIPT_DIR/corpus/auth_header"
+
+    printf '\x00\x00\x00anything-goes'                         > "$D/noauth_basic"
+    printf '\x00\x00\x00'                                      > "$D/noauth_empty"
+    printf '\x00\x00\x00Basic YWRtaW46cGFzcw=='               > "$D/noauth_with_basic"
+    printf '\x01\x00\x0badmin:pass\x00Basic YWRtaW46cGFzcw==' > "$D/basic_valid"
+    printf '\x01\x00\x0badmin:pass\x00Basic INVALIDBASE64=='   > "$D/basic_mismatch"
+    printf '\x01\x00\x0badmin:pass\x00Basic '                  > "$D/basic_empty_token"
+    printf '\x01\x00\x0badmin:pass\x00'                        > "$D/basic_no_header"
+    printf '\x01\x00\x0badmin:pass\x00Bearer token123'         > "$D/basic_wrong_scheme"
+    printf '\x01\x00\x0badmin:pass\x00basic YWRtaW46cGFzcw==' > "$D/basic_lowercase"
+    printf '\x01\x00\x00\x00Basic YWRtaW46cGFzcw=='           > "$D/basic_no_credential"
+    printf '\x02\x00\x00some-token-value'                      > "$D/custom_with_value"
+    printf '\x02\x00\x00'                                      > "$D/custom_empty"
+    printf '\x01\x00\x0badmin:pass\x00BASIC YWRtaW46cGFzcw==' > "$D/basic_uppercase"
+    printf '\x01\x00\x0badmin:pass\x00bAsIc YWRtaW46cGFzcw==' > "$D/basic_mixedcase"
+    printf '\x01\x00\x02a:\x00Basic YTo='                      > "$D/basic_b64_pad1"
+    printf '\x01\x00\x01a\x00Basic YQ=='                       > "$D/basic_b64_pad2"
+    printf '\x01\x00\x01:\x00Basic Og=='                       > "$D/basic_colon_only"
+    printf '\x01\x00\x0badmin:pass\x00Basic  YWRtaW46cGFzcw==' > "$D/basic_double_space"
+    printf '\x01\x00\x0badmin:pass\x00 Basic YWRtaW46cGFzcw==' > "$D/basic_leading_space"
+    python3 -c "
+import sys
+cred = b'A' * 255 + b':' + b'B' * 255
+hdr  = b'\x01' + len(cred).to_bytes(2,'big') + cred + b'Basic ' + b'A'*500
+sys.stdout.buffer.write(hdr)
+" > "$D/basic_long_cred"
+    python3 -c "
+import sys
+cred  = b'x:y'
+hdr   = b'Basic ' + b'A' * 248
+sys.stdout.buffer.write(b'\x01' + len(cred).to_bytes(2,'big') + cred + hdr)
+" > "$D/boundary_254"
+    python3 -c "
+import sys
+cred  = b'x:y'
+hdr   = b'Basic ' + b'A' * 250
+sys.stdout.buffer.write(b'\x01' + len(cred).to_bytes(2,'big') + cred + hdr)
+" > "$D/boundary_256"
+    python3 -c "
+import sys
+cred  = b'x:y'
+hdr   = b'Basic ' + b'A' * 251
+sys.stdout.buffer.write(b'\x01' + len(cred).to_bytes(2,'big') + cred + hdr)
+" > "$D/boundary_257"
+    echo "[+] Generated $(ls "$D" | wc -l) seeds for auth_header"
+
+    # ── websocket_auth ───────────────────────────────────────────────────────
+    # Format: [has_credential:1][credential_len:2 BE][credential][json_message]
+    echo "[*] Generating corpus/websocket_auth/"
+    local W="$SCRIPT_DIR/corpus/websocket_auth"
+
+    printf '\x00\x00\x00{"columns":80,"rows":24}'                              > "$W/noauth_normal"
+    printf '\x00\x00\x00{"columns":200,"rows":50}'                             > "$W/noauth_large_term"
+    printf '\x00\x00\x00{}'                                                    > "$W/noauth_empty_json"
+    printf '\x00\x00\x00{"columns":80,"rows":24,"AuthToken":"ignored"}'        > "$W/noauth_with_token"
+    printf '\x01\x00\x05admin{"columns":80,"rows":24,"AuthToken":"admin"}'     > "$W/auth_valid"
+    printf '\x01\x00\x05admin{"columns":80,"rows":24,"AuthToken":"wrong"}'     > "$W/auth_invalid"
+    printf '\x01\x00\x05admin{"columns":80,"rows":24}'                         > "$W/auth_missing_token"
+    printf '\x01\x00\x05admin{"AuthToken":"admin"}'                            > "$W/auth_no_dimensions"
+    printf '\x01\x00\x00{"columns":80,"rows":24}'                              > "$W/auth_empty_cred"
+    printf '\x00\x00\x00{invalid json}'                                        > "$W/json_malformed"
+    printf '\x00\x00\x00{"columns":'                                           > "$W/json_truncated"
+    printf '\x00\x00\x00'                                                      > "$W/json_empty"
+    printf '\x01\x00\x03foo[1,2,3]'                                            > "$W/json_array"
+    printf '\x00\x00\x00{"columns":99999,"rows":99999}'                        > "$W/json_huge_values"
+    printf '\x00\x00\x00{"columns":-1,"rows":-1}'                              > "$W/json_negative"
+    printf '\x00\x00\x00{"columns":65535,"rows":65535}'                        > "$W/json_uint16_max"
+    printf '\x00\x00\x00{"columns":65536,"rows":65536}'                        > "$W/json_uint16_overflow"
+    printf '\x00\x00\x00{  "columns" : 80 , "rows" : 24  }'                   > "$W/json_extra_whitespace"
+    python3 -c "import sys; sys.stdout.buffer.write(b'\x00\x00\x00' + (b'{\"a\":' * 50 + b'1' + b'}' * 50))" > "$W/json_deep_nested"
+    python3 -c "
+import sys
+cred = b'admin'; hdr = b'\x01' + len(cred).to_bytes(2,'big') + cred
+sys.stdout.buffer.write(hdr + b'{\"columns\":80,\"rows\":24,\"AuthToken\":\"' + b'A'*5000 + b'\"}')
+" > "$W/json_huge_token"
+    python3 -c "import sys; cred=b'admin'; h=b'\x01'+len(cred).to_bytes(2,'big')+cred; sys.stdout.buffer.write(h+b'{\"columns\":80,\"rows\":24,\"AuthToken\":true}')"    > "$W/auth_token_bool"
+    python3 -c "import sys; cred=b'admin'; h=b'\x01'+len(cred).to_bytes(2,'big')+cred; sys.stdout.buffer.write(h+b'{\"columns\":80,\"rows\":24,\"AuthToken\":null}')"    > "$W/auth_token_null"
+    python3 -c "import sys; cred=b'admin'; h=b'\x01'+len(cred).to_bytes(2,'big')+cred; sys.stdout.buffer.write(h+b'{\"columns\":80,\"rows\":24,\"AuthToken\":12345}')"   > "$W/auth_token_int"
+    python3 -c "import sys; cred=b'admin'; h=b'\x01'+len(cred).to_bytes(2,'big')+cred; sys.stdout.buffer.write(h+b'{\"AuthToken\":\"wrong\",\"AuthToken\":\"admin\"}')" > "$W/auth_token_dup"
+    python3 -c "
+import sys
+cred = b'A'*200
+sys.stdout.buffer.write(b'\x01' + len(cred).to_bytes(2,'big') + cred + b'{\"AuthToken\":\"' + cred + b'\"}')
+" > "$W/auth_long_cred"
+    echo "[+] Generated $(ls "$W" | wc -l) seeds for websocket_auth"
+
+    # ── http_parsing ─────────────────────────────────────────────────────────
+    echo "[*] Generating corpus/http_parsing/"
+    local H="$SCRIPT_DIR/corpus/http_parsing"
+
+    printf 'GET / HTTP/1.1\r\nHost: localhost:7681\r\n\r\n'                                                                    > "$H/get_root"
+    printf 'GET /token HTTP/1.1\r\nHost: localhost:7681\r\nAuthorization: Basic YWRtaW46cGFzcw==\r\n\r\n'                      > "$H/get_with_auth"
+    printf 'GET /ws HTTP/1.1\r\nHost: localhost:7681\r\nOrigin: http://localhost:7681\r\n\r\n'                                  > "$H/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' > "$H/get_full_headers"
+    printf 'GET /../../../etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n'                                                      > "$H/path_traversal"
+    printf 'GET //etc/passwd HTTP/1.1\r\nHost: localhost\r\n\r\n'                                                              > "$H/path_double_slash"
+    printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic \r\n\r\n'                                                > "$H/auth_empty_b64"
+    printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer token123\r\n\r\n'                                       > "$H/auth_bearer"
+    printf 'GET / HTTP/1.1\r\nHost: localhost:7681\r\nOrigin: http://evil.com\r\n\r\n'                                        > "$H/csrf_mismatch"
+    printf 'GET / HTTP/1.1\r\nHost: target.com\r\nOrigin: http://target.com.evil.com\r\n\r\n'                                 > "$H/csrf_subdomain_trick"
+    printf 'GARBAGE\r\n\r\n'                                                                                                    > "$H/malformed_no_space"
+    printf 'GET / HTTP/1.1\r\nHost: localhost\r\nAuthorization: Basic user:password\r\n\r\n'                                   > "$H/auth_with_colon"
+    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' > "$H/ws_upgrade"
+    printf 'GET /%%s%%s%%s%%n HTTP/1.1\r\nHost: %%x%%x%%x\r\nAuthorization: Basic %%n%%n\r\n\r\n'                              > "$H/fmt_everywhere"
+    python3 -c "
+import sys
+path = '/' + 'A' * 200
+sys.stdout.buffer.write(f'GET {path} HTTP/1.1\r\nHost: localhost\r\n\r\n'.encode())
+" > "$H/path_long_200"
+    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())
+" > "$H/auth_long_value"
+    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())
+" > "$H/many_headers"
+    echo "[+] Generated $(ls "$H" | wc -l) seeds for http_parsing"
+
+    echo "[+] Total corpus: $(find "$SCRIPT_DIR/corpus" -type f | wc -l) files"
+}
+
+# ─── 5. crash backup crontab ──────────────────────────────────────────────────
+
+setup_crash_backup() {
+    local BACKUP_DIR="$SCRIPT_DIR/crashes-backup"
+
+    # Write the backup script
+    cat > "$BACKUP_SCRIPT" <<BKUP
+#!/bin/bash
+# Auto-generated by build_fuzzers.sh — backs up AFL++ crashes to a dated archive
+# $CRONTAB_MARKER
+SCRIPT_DIR="$SCRIPT_DIR"
+BACKUP_DIR="$BACKUP_DIR"
+OUTPUT_DIR="\$SCRIPT_DIR/output"
+
+mkdir -p "\$BACKUP_DIR"
+
+HAS_CRASHES=false
+for crash_dir in "\$OUTPUT_DIR"/*/main/crashes "\$OUTPUT_DIR"/*/cmplog/crashes; do
+    [ -d "\$crash_dir" ] || continue
+    count=\$(find "\$crash_dir" -maxdepth 1 -name 'id:*' 2>/dev/null | wc -l)
+    [ "\$count" -gt 0 ] || continue
+    HAS_CRASHES=true
+    break
+done
+
+\$HAS_CRASHES || exit 0
+
+STAMP=\$(date +%Y%m%d_%H%M%S)
+ARCHIVE="\$BACKUP_DIR/crashes_\${STAMP}.tar.gz"
+tar -czf "\$ARCHIVE" \
+    --exclude='README.txt' \
+    \$(find "\$OUTPUT_DIR" -type d -name 'crashes' 2>/dev/null | tr '\n' ' ') \
+    2>/dev/null || true
+
+echo "[\$(date)] Backed up crashes → \$ARCHIVE" >> "\$BACKUP_DIR/backup.log"
+BKUP
+    chmod +x "$BACKUP_SCRIPT"
+    echo "[+] Created $BACKUP_SCRIPT"
+
+    # Install crontab entry (idempotent — remove stale entry first)
+    local TMPFILE
+    TMPFILE=$(mktemp)
+    crontab -l 2>/dev/null | grep -v "$CRONTAB_MARKER" | grep -v "backup_crashes.sh" > "$TMPFILE" || true
+    {
+        cat "$TMPFILE"
+        echo "$CRONTAB_MARKER"
+        echo "*/15 * * * * $BACKUP_SCRIPT >> $SCRIPT_DIR/crashes-backup/backup.log 2>&1"
+    } | crontab -
+    rm -f "$TMPFILE"
+    echo "[+] Crontab entry installed (every 15 min)"
+    crontab -l | grep -A1 "$CRONTAB_MARKER"
+}
+
+# ─── 6. select compiler ───────────────────────────────────────────────────────
 
 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"
+        if ! command -v afl-clang-fast &>/dev/null; then
+            echo "[!] afl-clang-fast not found. Install AFL++ first."
             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"
+        if ! command -v clang &>/dev/null; then
+            echo "[!] clang not found. Install clang first."
             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]"
+        echo "[!] Unknown mode: $MODE  (use 'afl' or '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)
+# ─── find json-c ─────────────────────────────────────────────────────────────
+
+JSON_C_CFLAGS=$(pkg-config --cflags json-c 2>/dev/null || echo "")
+JSON_C_LIBS=$(pkg-config --libs   json-c 2>/dev/null || echo "-ljson-c")
 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
+    for d in /usr/include /usr/local/include; do
+        [ -f "$d/json-c/json.h" ] && JSON_C_CFLAGS="-I$d" && break
     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"
+    echo "[!] json-c headers not found. Run: sudo apt-get install libjson-c-dev"
     exit 1
 fi
 
-# Build each harness
+# ─── run setup stages ────────────────────────────────────────────────────────
+
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[1/7] Installing dependencies"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+install_deps
+
 echo
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[2/7] Setting up ttyd source + instrumented objects"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+setup_ttyd_source
+
+# ─── build harnesses ─────────────────────────────────────────────────────────
+
+echo
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[3/7] Building harnesses (with real ttyd code)"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+# Common flags for all harnesses: our mock includes take priority over system
+COMMON_CFLAGS="$CFLAGS -I$FUZZ_INCLUDE -I$TTYD_SRC/src $JSON_C_CFLAGS"
+COMMON_CFLAGS="$COMMON_CFLAGS -Wno-implicit-function-declaration -Wno-int-conversion"
+
+# http.c and protocol.c are compiled into each harness via #include — do NOT
+# include their pre-compiled objects here or every symbol will be defined twice.
+MOCK_OBJS="$TTYD_OBJ/utils.o"
+MOCK_OBJS="$MOCK_OBJS $TTYD_OBJ/fuzz_lws_mock.o $TTYD_OBJ/mock_server_globals.o $TTYD_OBJ/mock_pty_stubs.o"
+
+# http.c decompresses gzip-encoded HTML using zlib
+EXTRA_LIBS="-lz"
+
 echo "[*] Building fuzz_auth_header..."
-$CC $CFLAGS fuzz_auth_header.c -o fuzz_auth_header$SUFFIX
+$CC $COMMON_CFLAGS fuzz_auth_header.c $MOCK_OBJS $JSON_C_LIBS $EXTRA_LIBS -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
+$CC $COMMON_CFLAGS fuzz_websocket_auth.c $MOCK_OBJS $JSON_C_LIBS $EXTRA_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
+$CC $COMMON_CFLAGS fuzz_http_parsing.c $MOCK_OBJS $JSON_C_LIBS -o fuzz_http_parsing$SUFFIX
 echo "[+] Built: fuzz_http_parsing$SUFFIX"
 
-# Build CmpLog variants (AFL++ only)
-if [ "$MODE" == "afl" ]; then
+# 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 "[*] Building CmpLog variants..."
+    for target in fuzz_auth_header fuzz_websocket_auth; do
+        AFL_LLVM_CMPLOG=1 $CC $COMMON_CFLAGS ${target}.c $MOCK_OBJS $JSON_C_LIBS $EXTRA_LIBS \
+            -o ${target}_cmplog
+        echo "[+] Built: ${target}_cmplog"
+    done
+    AFL_LLVM_CMPLOG=1 $CC $COMMON_CFLAGS fuzz_http_parsing.c $MOCK_OBJS $JSON_C_LIBS \
+        -o fuzz_http_parsing_cmplog
     echo "[+] Built: fuzz_http_parsing_cmplog"
 fi
 
 echo
-echo "[+] All harnesses built successfully!"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[4/7] Setting up dictionaries"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+setup_dictionaries
 
-# Generate corpus
 echo
-echo "[*] Generating seed corpus..."
-SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-bash "$SCRIPT_DIR/gen-corpus.sh"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[5/7] Generating seed corpus"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+generate_corpus
 
 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"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[6/7] Installing crash-backup crontab"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+setup_crash_backup
+
+echo
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo "[7/7] Done"
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+echo
+echo "Ready to fuzz:"
+if [ "$MODE" = "afl" ]; then
+    echo "  ./run-fuzzers.sh          # launch all targets in tmux"
+    echo "  afl-fuzz -i corpus/auth_header -o output/auth_header -x dictionaries/http.dict ./fuzz_auth_header"
 else
-    echo "   ./fuzz_auth_header_lf corpus/auth_header"
+    echo "  ./fuzz_auth_header_lf corpus/auth_header/"
 fi
+echo
+echo "Crashes backed up automatically every 15 min → crashes-backup/"
+echo "To stop backup: ./clean.sh"
diff --git a/fuzzing/ttyd/clean.sh b/fuzzing/ttyd/clean.sh
@@ -1,6 +1,11 @@
 #!/bin/bash
 # Clean up TTYD fuzzing artifacts
-# Usage: ./clean.sh [--keep-crashes] [--corpus-only]
+#
+# Usage: ./clean.sh [OPTIONS]
+#   --keep-crashes   Save crash files before cleaning output/
+#   --corpus-only    Only clean corpus/, leave everything else
+#   --keep-source    Do not remove ttyd-src/ (the git clone)
+#   -h, --help
 
 set -e
 
@@ -8,17 +13,20 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 
 KEEP_CRASHES=false
 CORPUS_ONLY=false
+KEEP_SOURCE=false
 
 for arg in "$@"; do
     case "$arg" in
         --keep-crashes) KEEP_CRASHES=true ;;
         --corpus-only)  CORPUS_ONLY=true ;;
+        --keep-source)  KEEP_SOURCE=true ;;
         -h|--help)
             echo "Usage: $0 [OPTIONS]"
             echo
             echo "Options:"
-            echo "  --keep-crashes  Save crash files before cleaning output"
+            echo "  --keep-crashes  Save crash files before cleaning output/"
             echo "  --corpus-only   Only clean the corpus directory"
+            echo "  --keep-source   Keep ttyd-src/ git clone"
             echo "  -h, --help      Show this help"
             exit 0
             ;;
@@ -30,14 +38,30 @@ for arg in "$@"; do
     esac
 done
 
-# Stop any running fuzzers
+# ── stop 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
+# ── remove crontab entry ──────────────────────────────────────────────────────
+CRONTAB_MARKER="# ttyd-fuzz-crash-backup"
+CURRENT_CRON=$(crontab -l 2>/dev/null || true)
+if echo "$CURRENT_CRON" | grep -q "$CRONTAB_MARKER"; then
+    TMPFILE=$(mktemp)
+    echo "$CURRENT_CRON" \
+        | grep -v "$CRONTAB_MARKER" \
+        | grep -v "backup_crashes.sh" \
+        > "$TMPFILE" || true
+    crontab "$TMPFILE"
+    rm -f "$TMPFILE"
+    echo "[+] Removed crash-backup crontab entry"
+else
+    echo "[*] No crash-backup crontab entry found"
+fi
+
+# ── clean corpus ──────────────────────────────────────────────────────────────
 if [ -d "$SCRIPT_DIR/corpus" ]; then
     echo "[*] Removing corpus/"
     rm -rf "$SCRIPT_DIR/corpus"
@@ -49,24 +73,19 @@ if $CORPUS_ONLY; then
     exit 0
 fi
 
-# Clean output (fuzzing results)
+# ── clean output (optionally save crashes first) ──────────────────────────────
 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"
+                    dest="$CRASH_DIR/$(basename "$target_dir")/$(basename "$node_dir")"
                     mkdir -p "$dest"
                     cp -r "$crash_path"/* "$dest/"
                     HAS_CRASHES=true
@@ -87,7 +106,7 @@ if [ -d "$SCRIPT_DIR/output" ]; then
     echo "[+] Output removed"
 fi
 
-# Clean compiled binaries
+# ── remove compiled harness binaries ─────────────────────────────────────────
 REMOVED=0
 for binary in fuzz_auth_header fuzz_websocket_auth fuzz_http_parsing; do
     for suffix in "" "_cmplog" "_lf"; do
@@ -98,11 +117,45 @@ for binary in fuzz_auth_header fuzz_websocket_auth fuzz_http_parsing; do
         fi
     done
 done
+[ "$REMOVED" -gt 0 ] && echo "[+] Removed $REMOVED compiled binaries" \
+                      || echo "[*] No binaries to remove"
+
+# ── remove generated mock files ───────────────────────────────────────────────
+for f in fuzz_lws_mock.c fuzz_lws_mock.h mock_server_globals.c mock_pty_stubs.c backup_crashes.sh; do
+    if [ -f "$SCRIPT_DIR/$f" ]; then
+        rm -f "$SCRIPT_DIR/$f"
+        echo "[+] Removed $f"
+    fi
+done
 
-if [ "$REMOVED" -gt 0 ]; then
-    echo "[+] Removed $REMOVED compiled binaries"
-else
-    echo "[*] No binaries to remove"
+# ── remove instrumented objects and mock headers ──────────────────────────────
+if [ -d "$SCRIPT_DIR/ttyd-obj" ]; then
+    echo "[*] Removing ttyd-obj/"
+    rm -rf "$SCRIPT_DIR/ttyd-obj"
+    echo "[+] ttyd-obj/ removed"
+fi
+
+if [ -d "$SCRIPT_DIR/fuzz-include" ]; then
+    echo "[*] Removing fuzz-include/"
+    rm -rf "$SCRIPT_DIR/fuzz-include"
+    echo "[+] fuzz-include/ removed"
+fi
+
+# ── optionally remove the ttyd source clone ───────────────────────────────────
+if [ -d "$SCRIPT_DIR/ttyd-src" ]; then
+    if $KEEP_SOURCE; then
+        echo "[*] Keeping ttyd-src/ (--keep-source)"
+    else
+        echo "[*] Removing ttyd-src/ (pass --keep-source to skip)"
+        rm -rf "$SCRIPT_DIR/ttyd-src"
+        echo "[+] ttyd-src/ removed"
+    fi
+fi
+
+# ── remove crash backups log (keep archives — user's decision) ───────────────
+if [ -f "$SCRIPT_DIR/crashes-backup/backup.log" ]; then
+    rm -f "$SCRIPT_DIR/crashes-backup/backup.log"
+    echo "[+] Removed crashes-backup/backup.log"
 fi
 
 echo "[+] Clean complete"
diff --git a/fuzzing/ttyd/fuzz_auth_header.c b/fuzzing/ttyd/fuzz_auth_header.c
@@ -1,18 +1,21 @@
 /*
- * AFL++ / LibFuzzer Harness for TTYD Authentication Header Parsing
+ * AFL++ / LibFuzzer harness — TTYD HTTP Authentication Header
  *
- * Target: HTTP Authorization header parsing in check_auth()
- * Goal: Find buffer overflows, format string bugs, logic errors
+ * Target: the real check_auth() function from ttyd's http.c
+ * Technique: #include the source file directly so static functions are
+ *            accessible (standard OSS-Fuzz approach).
  *
- * Compile with AFL++:
- *   afl-clang-fast -fsanitize=address -g -O1 fuzz_auth_header.c -o fuzz_auth_header
+ * Input format: [mode:1][credential_len:2 BE][credential][auth_header_value]
+ *   mode 0 — no auth required
+ *   mode 1 — Basic auth  (credential = expected base64 value)
+ *   mode 2 — custom header auth (X-Auth-User)
  *
- * 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
+ * Build (handled by build_fuzzers.sh):
+ *   afl-clang-fast -fsanitize=address,undefined -g -O1
+ *     -I fuzz-include -I ttyd-src/src
+ *     fuzz_auth_header.c
+ *     ttyd-obj/{http,protocol,utils,fuzz_lws_mock,mock_server_globals,mock_pty_stubs}.o
+ *     -ljson-c -o fuzz_auth_header
  */
 
 #include <stdio.h>
@@ -20,186 +23,96 @@
 #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]
+/* Mock lws control state — must come before including the real ttyd source */
+#include "fuzz_lws_mock.h"
 
-    uint8_t mode = data[0] % 3;  // 0=no auth, 1=basic auth, 2=custom header
-    uint16_t cred_len = (data[1] << 8) | data[2];
+/* Pull in the real ttyd structs (server.h included transitively via http.c) */
+/* fuzz-include/libwebsockets.h and fuzz-include/uv.h shadow the real ones   */
+#include "ttyd-src/src/http.c"   /* gives us static check_auth() */
 
-    if (cred_len > size - 3) cred_len = size - 3;
-    if (cred_len > 500) cred_len = 500;  // Reasonable limit
+/* Provided by mock_server_globals.c */
+extern struct server  _fuzz_server;
+extern void           fuzz_server_reset(void);
 
-    char credential_raw[512] = {0};
-    char credential_b64[1024] = {0};
-    char auth_header_input[1024] = {0};
-    char custom_header_value[256] = {0};
+/* ── fuzz target ─────────────────────────────────────────────────────────── */
 
-    // 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';
+int fuzz_target(const uint8_t *data, size_t size) {
+    if (size < 3) return 0;
+
+    /* Parse structured input */
+    uint8_t  mode     = data[0] % 3;
+    uint16_t cred_len = (uint16_t)((data[1] << 8) | data[2]);
+    if ((size_t)cred_len > size - 3) cred_len = (uint16_t)(size - 3);
+
+    /* Reset fuzzer-controlled lws state and the server global */
+    fuzz_lws_reset();
+    fuzz_server_reset();
+
+    /* Set up the credential the server expects */
+    static char cred_buf[65536];
+    if (mode == 1 && cred_len > 0) {
+        /* Basic auth: credential bytes become the expected base64 value */
+        memcpy(cred_buf, data + 3, cred_len);
+        cred_buf[cred_len] = '\0';
+        _fuzz_server.credential  = cred_buf;
+        _fuzz_server.auth_header = NULL;
+    } else if (mode == 2) {
+        /* Custom header auth */
+        _fuzz_server.credential  = NULL;
+        _fuzz_server.auth_header = (char *)"X-Auth-User";
     }
+    /* mode 0: no auth, both remain NULL */
 
-    // 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");
-    }
+    /* Remaining bytes are the inbound Authorization header value that
+     * lws_hdr_copy() will return via the mock */
+    const uint8_t *auth_data  = data + 3 + cred_len;
+    size_t         auth_len   = size - 3 - cred_len;
+    fuzz_lws_set_auth((const char *)auth_data, (int)auth_len);
 
-    // Fuzz the authentication check
-    bool result = check_auth_fuzzing(auth_header_input, custom_header_value);
+    /* For custom-header mode, same bytes are treated as the custom header */
+    if (mode == 2)
+        fuzz_lws_set_custom((const char *)auth_data, (int)auth_len);
 
-    // Cleanup
-    if (server->credential) free(server->credential);
-    if (server->auth_header) free(server->auth_header);
-    server->credential = NULL;
-    server->auth_header = NULL;
+    /* Call the REAL check_auth() from http.c */
+    struct lws      mock_wsi = {0};
+    struct pss_http pss      = {0};
+    (void)check_auth(&mock_wsi, &pss);
 
     return 0;
 }
 
+/* ── AFL++ persistent mode ───────────────────────────────────────────────── */
 #ifdef __AFL_FUZZ_TESTCASE_LEN
-// AFL++ persistent mode
 __AFL_FUZZ_INIT();
-
 int main(void) {
-    #ifdef __AFL_HAVE_MANUAL_CONTROL
-        __AFL_INIT();
-    #endif
-
+#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);
+        fuzz_target(buf, __AFL_FUZZ_TESTCASE_LEN);
     }
-
     return 0;
 }
+
 #else
-// LibFuzzer mode
+/* LibFuzzer */
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
     return fuzz_target(data, size);
 }
 
-// Standalone mode for testing
 #ifndef LIBFUZZER
+/* Standalone file-based testing */
 int main(int argc, char **argv) {
-    if (argc != 2) {
-        fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
-        return 1;
-    }
-
+    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");
+    if (!f) { perror("fopen"); return 1; }
+    fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f);
+    uint8_t *buf = malloc(sz);
+    fread(buf, 1, sz, f); fclose(f);
+    fuzz_target(buf, sz);
+    free(buf);
     return 0;
 }
 #endif
diff --git a/fuzzing/ttyd/fuzz_websocket_auth.c b/fuzzing/ttyd/fuzz_websocket_auth.c
@@ -1,18 +1,19 @@
 /*
- * AFL++ / LibFuzzer Harness for TTYD WebSocket Authentication
+ * AFL++ / LibFuzzer harness — TTYD WebSocket AuthToken + window-size JSON
  *
- * Target: WebSocket AuthToken JSON parsing and validation
- * Goal: Find JSON parsing bugs, buffer overflows, logic errors
+ * Target: the real parse_window_size() and check_auth() from ttyd's protocol.c
+ * Technique: #include the source file so static functions are accessible.
  *
- * Compile with AFL++:
- *   afl-clang-fast -fsanitize=address -g -O1 fuzz_websocket_auth.c -ljson-c -o fuzz_websocket_auth
+ * Input format: [has_credential:1][credential_len:2 BE][credential][json_message]
+ *   has_credential odd  → server->credential is set to the credential bytes
+ *   has_credential even → no credential (open server)
  *
- * 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
+ * Build (handled by build_fuzzers.sh):
+ *   afl-clang-fast -fsanitize=address,undefined -g -O1
+ *     -I fuzz-include -I ttyd-src/src
+ *     fuzz_websocket_auth.c
+ *     ttyd-obj/{http,protocol,utils,fuzz_lws_mock,mock_server_globals,mock_pty_stubs}.o
+ *     -ljson-c -o fuzz_websocket_auth
  */
 
 #include <stdio.h>
@@ -20,169 +21,102 @@
 #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;
+/* Mock lws control state — must come before the real ttyd source */
+#include "fuzz_lws_mock.h"
 
-    uint16_t columns = 0, rows = 0;
-    struct json_object *obj = parse_window_size_fuzzing(buf, len, &columns, &rows);
-
-    if (obj == NULL) {
-        return false;
-    }
+/* Pull in real ttyd protocol code (parse_window_size, check_auth static fns) */
+#include "ttyd-src/src/protocol.c"
 
-    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;
-            }
-        }
+/* Provided by mock_server_globals.c */
+extern struct server  _fuzz_server;
+extern void           fuzz_server_reset(void);
 
-        if (!pss->authenticated) {
-            json_object_put(obj);
-            return false;
-        }
-    }
+/* ── fuzz target ─────────────────────────────────────────────────────────── */
 
-    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]
+    if (size < 3) return 0;
 
-    bool has_credential = (data[0] % 2) == 1;
-    uint8_t cred_len = data[1];
+    bool     has_credential = (data[0] & 1) == 1;
+    uint16_t cred_len       = (uint16_t)((data[1] << 8) | data[2]);
+    if ((size_t)cred_len > size - 3) cred_len = (uint16_t)(size - 3);
 
-    if (cred_len > size - 2) cred_len = size - 2;
-    if (cred_len > 200) cred_len = 200;
+    fuzz_lws_reset();
+    fuzz_server_reset();
 
-    char credential[256] = {0};
+    /* Configure server credential */
+    static char cred_buf[65536];
     if (has_credential && cred_len > 0) {
-        memcpy(credential, data + 2, cred_len);
-        credential[cred_len] = '\0';
-        server->credential = credential;
-    } else {
-        server->credential = NULL;
+        memcpy(cred_buf, data + 3, cred_len);
+        cred_buf[cred_len] = '\0';
+        _fuzz_server.credential = cred_buf;
     }
-
-    // Extract JSON message
-    size_t json_start = 2 + (has_credential ? cred_len : 0);
-    if (json_start >= size) {
-        server->credential = NULL;
-        return 0;
+    /* lws auth header: supply the same credential bytes so the WS
+     * check_auth() path (which uses lws_hdr_copy) can also be explored */
+    fuzz_lws_set_auth((const char *)cred_buf, has_credential ? cred_len : 0);
+
+    /* JSON message starts after the credential */
+    size_t json_off = 3 + cred_len;
+    if (json_off >= size) return 0;
+
+    const char *json_data = (const char *)(data + json_off);
+    size_t      json_len  = size - json_off;
+
+    /* Exercise the real parse_window_size() from protocol.c */
+    uint16_t cols = 0, rows = 0;
+    struct json_object *obj = parse_window_size(json_data, (int)json_len,
+                                                &cols, &rows);
+    if (!obj) return 0;
+
+    /* Exercise AuthToken validation when credential is configured */
+    if (_fuzz_server.credential) {
+        struct json_object *tok_obj = NULL;
+        if (json_object_object_get_ex(obj, "AuthToken", &tok_obj)) {
+            const char *token = json_object_get_string(tok_obj);
+            if (token)
+                (void)strcmp(token, _fuzz_server.credential);
+        }
     }
 
-    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);
+    /* Also exercise the WS check_auth() static function */
+    struct lws      mock_wsi = {0};
+    struct pss_tty  pss      = {0};
+    (void)check_auth(&mock_wsi, &pss);
 
-    server->credential = NULL;
+    json_object_put(obj);
     return 0;
 }
 
+/* ── AFL++ persistent mode ───────────────────────────────────────────────── */
 #ifdef __AFL_FUZZ_TESTCASE_LEN
-// AFL++ persistent mode
 __AFL_FUZZ_INIT();
-
 int main(void) {
-    #ifdef __AFL_HAVE_MANUAL_CONTROL
-        __AFL_INIT();
-    #endif
-
+#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);
+        fuzz_target(buf, __AFL_FUZZ_TESTCASE_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;
-    }
-
+    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");
+    if (!f) { perror("fopen"); return 1; }
+    fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f);
+    uint8_t *buf = malloc(sz);
+    fread(buf, 1, sz, f); fclose(f);
+    fuzz_target(buf, sz);
+    free(buf);
     return 0;
 }
 #endif
diff --git a/fuzzing/ttyd/gen-corpus.sh b/fuzzing/ttyd/gen-corpus.sh
@@ -1,346 +0,0 @@
-#!/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)"