From 0f77aeb72b69ae35a30d939e8faa285bef8a4422 Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Wed, 1 Apr 2026 11:29:14 -0400 Subject: [PATCH] v2.1.3 - Fix async EVENT thread safety by splitting store core/post-actions and naming lws main thread --- relay.pid | 2 +- src/main.c | 51 +- src/main.h | 16 +- src/websockets.c | 562 ++++++++++++++++++- test_results_20260401_110147.log | 18 + tests/test_results_20260401_110200.log | 733 +++++++++++++++++++++++++ 6 files changed, 1350 insertions(+), 32 deletions(-) create mode 100644 test_results_20260401_110147.log create mode 100644 tests/test_results_20260401_110200.log diff --git a/relay.pid b/relay.pid index ee50374..c5ea930 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -567688 +613914 diff --git a/src/main.c b/src/main.c index 178e9fb..2e29515 100644 --- a/src/main.c +++ b/src/main.c @@ -975,12 +975,16 @@ int store_event_tags(const char* event_id, cJSON* tags) { return db_store_event_tags_cjson(event_id, tags); } -// Store event in database -int store_event(cJSON* event) { +// Core event storage path. +// Returns: +// 0 = inserted into DB +// 1 = handled without insert (duplicate or ephemeral) +// -1 = failure +int store_event_core(cJSON* event) { if (!g_db || !event) { return -1; } - + // Extract event fields cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id"); cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey"); @@ -989,12 +993,12 @@ int store_event(cJSON* event) { cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content"); cJSON* sig = cJSON_GetObjectItemCaseSensitive(event, "sig"); cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags"); - + if (!id || !pubkey || !created_at || !kind || !content || !sig) { DEBUG_ERROR("Invalid event - missing required fields"); return -1; } - + // Classify event type event_type_t type = classify_event_kind((int)cJSON_GetNumberValue(kind)); @@ -1002,7 +1006,7 @@ int store_event(cJSON* event) { if (type == EVENT_TYPE_EPHEMERAL) { DEBUG_LOG("Ephemeral event (kind %d) - broadcasting only, not storing", (int)cJSON_GetNumberValue(kind)); - return 0; // Success - event was handled but not stored + return 1; } // Serialize tags to JSON (use empty array if no tags) @@ -1012,12 +1016,12 @@ int store_event(cJSON* event) { } else { tags_json = strdup("[]"); } - + if (!tags_json) { DEBUG_ERROR("Failed to serialize tags to JSON"); return -1; } - + // Serialize full event JSON for fast retrieval (use PrintUnformatted for compact storage) char* event_json = cJSON_PrintUnformatted(event); if (!event_json) { @@ -1025,7 +1029,7 @@ int store_event(cJSON* event) { free(tags_json); return -1; } - + thread_pool_store_event_payload_t payload; memset(&payload, 0, sizeof(payload)); payload.id = (char*)cJSON_GetStringValue(id); @@ -1056,7 +1060,7 @@ int store_event(cJSON* event) { DEBUG_ERROR("INSERT failed: rc=%d, extended_errcode=%d, msg=%s", rc, extended_errcode, err_msg); } } - + if (rc != DB_DONE) { if (rc == DB_CONSTRAINT) { DEBUG_WARN("Event already exists in database"); @@ -1082,7 +1086,7 @@ int store_event(cJSON* event) { free(tags_json); free(event_json); - return 0; // Not an error, just duplicate + return 1; } char error_msg[256]; snprintf(error_msg, sizeof(error_msg), "Failed to insert event: %s", db_last_error()); @@ -1094,12 +1098,25 @@ int store_event(cJSON* event) { free(tags_json); free(event_json); + return 0; +} + +// Main-thread-only post-store follow-up actions. +void store_event_post_actions(cJSON* event) { + if (!event) { + return; + } + + cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id"); + cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags"); // Call monitoring hook after successful event storage monitoring_on_event_stored(); // After successful event storage, insert denormalized tags - store_event_tags(cJSON_GetStringValue(id), tags); + if (id && cJSON_IsString(id)) { + store_event_tags(cJSON_GetStringValue(id), tags); + } // Check if this is a kind 3 event from the admin — trigger WoT sync cJSON* kind_obj = cJSON_GetObjectItemCaseSensitive(event, "kind"); @@ -1116,7 +1133,17 @@ int store_event(cJSON* event) { if (admin_pubkey) free((char*)admin_pubkey); } } +} +// Backward-compatible wrapper for synchronous call sites. +int store_event(cJSON* event) { + int core_rc = store_event_core(event); + if (core_rc < 0) { + return -1; + } + if (core_rc == 0) { + store_event_post_actions(event); + } return 0; } diff --git a/src/main.h b/src/main.h index cd7794f..85d9b51 100644 --- a/src/main.h +++ b/src/main.h @@ -13,8 +13,8 @@ // Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define CRELAY_VERSION_MAJOR 2 #define CRELAY_VERSION_MINOR 1 -#define CRELAY_VERSION_PATCH 2 -#define CRELAY_VERSION "v2.1.2" +#define CRELAY_VERSION_PATCH 3 +#define CRELAY_VERSION "v2.1.3" // Relay metadata (authoritative source for NIP-11 information) #define RELAY_NAME "C-Relay" @@ -28,9 +28,21 @@ #define POSTING_POLICY "" #define PAYMENTS_URL "" +// Forward declaration to avoid pulling cJSON headers into all includers. +typedef struct cJSON cJSON; + // Async REQ handler status used by websocket callback to defer EOSE until worker completion #define HANDLE_REQ_ASYNC_PENDING (-2) +// Async EVENT storage split: +// - store_event_core(): worker-safe core DB write path +// returns 0 when inserted, 1 when handled without insert (duplicate/ephemeral), -1 on error +// - store_event_post_actions(): main-thread-only follow-up actions (monitoring/tags/WoT sync) +// - store_event(): legacy wrapper preserving original behavior for synchronous call sites +int store_event_core(cJSON* event); +void store_event_post_actions(cJSON* event); +int store_event(cJSON* event); + // Drains completed async REQ jobs and queues EVENT/EOSE on the lws service thread. void process_req_async_completions(void); diff --git a/src/websockets.c b/src/websockets.c index 73969d3..1e9c6e4 100644 --- a/src/websockets.c +++ b/src/websockets.c @@ -198,7 +198,507 @@ static void check_idle_connections(int idle_timeout_sec) { } } +// Hot-path config cache (Phase 4): reduce per-message SQLite config lookups. +typedef struct { + time_t last_refresh; + int ttl_sec; + int nip70_protected_events_enabled; + int nip17_admin_enabled; + int wot_enabled; + int nip42_auth_timeout_sec; + int idle_connection_timeout_sec; + int max_connection_seconds; + int kind_1_status_posts_hours; + int debug_level; +} hot_config_cache_t; +static hot_config_cache_t g_hot_config = { + .last_refresh = 0, + .ttl_sec = 5, + .nip70_protected_events_enabled = 0, + .nip17_admin_enabled = 0, + .wot_enabled = 0, + .nip42_auth_timeout_sec = 10, + .idle_connection_timeout_sec = 30, + .max_connection_seconds = 86400, + .kind_1_status_posts_hours = 0, + .debug_level = -1, +}; + +static pthread_mutex_t g_hot_config_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void refresh_hot_config_if_needed(void) { + time_t now = time(NULL); + + pthread_mutex_lock(&g_hot_config_mutex); + if (g_hot_config.last_refresh != 0 && (now - g_hot_config.last_refresh) < g_hot_config.ttl_sec) { + pthread_mutex_unlock(&g_hot_config_mutex); + return; + } + + g_hot_config.nip70_protected_events_enabled = get_config_bool("nip70_protected_events_enabled", 0); + g_hot_config.nip17_admin_enabled = get_config_bool("nip17_admin_enabled", 0); + g_hot_config.wot_enabled = get_config_int("wot_enabled", 0); + g_hot_config.nip42_auth_timeout_sec = get_config_int("nip42_auth_timeout_sec", 10); + g_hot_config.idle_connection_timeout_sec = get_config_int("idle_connection_timeout_sec", 30); + g_hot_config.max_connection_seconds = get_config_int("max_connection_seconds", 86400); + g_hot_config.kind_1_status_posts_hours = get_config_int("kind_1_status_posts_hours", 0); + g_hot_config.debug_level = get_config_int("debug_level", -1); + g_hot_config.last_refresh = now; + pthread_mutex_unlock(&g_hot_config_mutex); +} + +static int hot_cfg_nip70_protected_events_enabled(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.nip70_protected_events_enabled; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_nip17_admin_enabled(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.nip17_admin_enabled; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_wot_enabled(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.wot_enabled; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_nip42_auth_timeout_sec(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.nip42_auth_timeout_sec; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_idle_connection_timeout_sec(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.idle_connection_timeout_sec; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_max_connection_seconds(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.max_connection_seconds; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_kind_1_status_posts_hours(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.kind_1_status_posts_hours; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +static int hot_cfg_debug_level(void) { + refresh_hot_config_if_needed(); + pthread_mutex_lock(&g_hot_config_mutex); + int v = g_hot_config.debug_level; + pthread_mutex_unlock(&g_hot_config_mutex); + return v; +} + +// Async EVENT processing (Phase 1): offload signature validation + store_event from main lws thread. +typedef struct async_event_job { + char* event_json; + char event_id[65]; + int event_kind; + struct lws* wsi; + struct per_session_data* pss_token; + struct async_event_job* next; +} async_event_job_t; + +typedef struct async_event_completion { + struct lws* wsi; + struct per_session_data* pss_token; + char* event_json; + char event_id[65]; + int success; + int should_broadcast; + int run_post_actions; + char error_message[512]; + struct async_event_completion* next; +} async_event_completion_t; + +static pthread_mutex_t g_async_event_job_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_async_event_job_cond = PTHREAD_COND_INITIALIZER; +static async_event_job_t* g_async_event_job_head = NULL; +static async_event_job_t* g_async_event_job_tail = NULL; + +static pthread_mutex_t g_async_event_completion_mutex = PTHREAD_MUTEX_INITIALIZER; +static async_event_completion_t* g_async_event_completion_head = NULL; +static async_event_completion_t* g_async_event_completion_tail = NULL; + +static pthread_t g_async_event_worker_thread; +static int g_async_event_worker_running = 0; + +static void map_validation_error_message(int validation_result, char* out, size_t out_size) { + if (!out || out_size == 0) return; + + switch (validation_result) { + case NOSTR_ERROR_INVALID_INPUT: + strncpy(out, "invalid: malformed event structure", out_size - 1); + break; + case NOSTR_ERROR_EVENT_INVALID_SIGNATURE: + strncpy(out, "invalid: signature verification failed", out_size - 1); + break; + case NOSTR_ERROR_EVENT_INVALID_ID: + strncpy(out, "invalid: event id verification failed", out_size - 1); + break; + case NOSTR_ERROR_EVENT_INVALID_PUBKEY: + strncpy(out, "invalid: invalid pubkey format", out_size - 1); + break; + case -103: // NOSTR_ERROR_EVENT_EXPIRED + strncpy(out, "rejected: event expired", out_size - 1); + break; + case -102: // NOSTR_ERROR_NIP42_DISABLED + strncpy(out, "auth-required: NIP-42 authentication required", out_size - 1); + break; + case -101: // NOSTR_ERROR_AUTH_REQUIRED + strncpy(out, "blocked: pubkey not authorized", out_size - 1); + break; + default: + strncpy(out, "error: validation failed", out_size - 1); + break; + } + out[out_size - 1] = '\0'; +} + +static void send_ok_response(struct lws* wsi, + struct per_session_data* pss, + const char* event_id, + int accepted, + const char* message) { + if (!wsi || !pss || !event_id) { + return; + } + + cJSON* response = cJSON_CreateArray(); + if (!response) { + return; + } + + cJSON_AddItemToArray(response, cJSON_CreateString("OK")); + cJSON_AddItemToArray(response, cJSON_CreateString(event_id)); + cJSON_AddItemToArray(response, cJSON_CreateBool(accepted ? 1 : 0)); + cJSON_AddItemToArray(response, cJSON_CreateString(message ? message : "")); + + char* response_str = cJSON_Print(response); + if (response_str) { + size_t response_len = strlen(response_str); + if (queue_message(wsi, pss, response_str, response_len, LWS_WRITE_TEXT) != 0) { + DEBUG_ERROR("Failed to queue OK response message"); + } + free(response_str); + } + + cJSON_Delete(response); +} + +static void async_event_completion_push(async_event_completion_t* completion) { + if (!completion) return; + + pthread_mutex_lock(&g_async_event_completion_mutex); + completion->next = NULL; + if (!g_async_event_completion_tail) { + g_async_event_completion_head = completion; + g_async_event_completion_tail = completion; + } else { + g_async_event_completion_tail->next = completion; + g_async_event_completion_tail = completion; + } + pthread_mutex_unlock(&g_async_event_completion_mutex); +} + +static async_event_completion_t* async_event_completion_pop(void) { + pthread_mutex_lock(&g_async_event_completion_mutex); + async_event_completion_t* completion = g_async_event_completion_head; + if (completion) { + g_async_event_completion_head = completion->next; + if (!g_async_event_completion_head) { + g_async_event_completion_tail = NULL; + } + } + pthread_mutex_unlock(&g_async_event_completion_mutex); + return completion; +} + +static int event_is_async_eligible(cJSON* event, int* out_kind, char event_id_out[65]) { + if (!event || !out_kind || !event_id_out) { + return 0; + } + + cJSON* id_obj = cJSON_GetObjectItemCaseSensitive(event, "id"); + cJSON* kind_obj = cJSON_GetObjectItemCaseSensitive(event, "kind"); + if (!id_obj || !cJSON_IsString(id_obj) || !kind_obj || !cJSON_IsNumber(kind_obj)) { + return 0; + } + + const char* event_id = cJSON_GetStringValue(id_obj); + if (!event_id || strlen(event_id) >= 65) { + return 0; + } + + int kind = (int)cJSON_GetNumberValue(kind_obj); + + // Keep special/admin paths on main thread for existing behavior. + if (kind == 14 || kind == 1059 || kind == 23456) { + return 0; + } + + // Keep NIP-70 protected events on main thread (depends on auth/session checks). + cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags"); + if (tags && cJSON_IsArray(tags)) { + cJSON* tag = NULL; + cJSON_ArrayForEach(tag, tags) { + if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 1) { + cJSON* tag_name = cJSON_GetArrayItem(tag, 0); + if (tag_name && cJSON_IsString(tag_name) && + strcmp(cJSON_GetStringValue(tag_name), "-") == 0) { + return 0; + } + } + } + } + + strncpy(event_id_out, event_id, 64); + event_id_out[64] = '\0'; + *out_kind = kind; + return 1; +} + +static async_event_job_t* async_event_job_pop_blocking(void) { + pthread_mutex_lock(&g_async_event_job_mutex); + while (g_async_event_worker_running && !g_async_event_job_head) { + pthread_cond_wait(&g_async_event_job_cond, &g_async_event_job_mutex); + } + + async_event_job_t* job = g_async_event_job_head; + if (job) { + g_async_event_job_head = job->next; + if (!g_async_event_job_head) { + g_async_event_job_tail = NULL; + } + } + pthread_mutex_unlock(&g_async_event_job_mutex); + return job; +} + +static void* async_event_worker_main(void* arg) { + (void)arg; + pthread_setname_np(pthread_self(), "event-worker"); + + while (g_async_event_worker_running) { + async_event_job_t* job = async_event_job_pop_blocking(); + if (!job) { + continue; + } + + async_event_completion_t* completion = calloc(1, sizeof(*completion)); + if (!completion) { + free(job->event_json); + free(job); + continue; + } + + completion->wsi = job->wsi; + completion->pss_token = job->pss_token; + completion->event_json = job->event_json; // transfer ownership + strncpy(completion->event_id, job->event_id, sizeof(completion->event_id) - 1); + completion->event_id[sizeof(completion->event_id) - 1] = '\0'; + + int validation_result = nostr_validate_unified_request(completion->event_json, strlen(completion->event_json)); + if (validation_result != NOSTR_SUCCESS) { + completion->success = 0; + completion->should_broadcast = 0; + map_validation_error_message(validation_result, completion->error_message, sizeof(completion->error_message)); + } else { + cJSON* event_obj = cJSON_Parse(completion->event_json); + if (!event_obj || !cJSON_IsObject(event_obj)) { + completion->success = 0; + completion->should_broadcast = 0; + strncpy(completion->error_message, "error: failed to parse event", sizeof(completion->error_message) - 1); + completion->error_message[sizeof(completion->error_message) - 1] = '\0'; + } else { + if (job->event_kind >= 20000 && job->event_kind < 30000) { + completion->success = 1; + completion->should_broadcast = 1; + completion->run_post_actions = 0; + } else { + int core_rc = store_event_core(event_obj); + if (core_rc < 0) { + completion->success = 0; + completion->should_broadcast = 0; + completion->run_post_actions = 0; + strncpy(completion->error_message, "error: failed to store event", sizeof(completion->error_message) - 1); + completion->error_message[sizeof(completion->error_message) - 1] = '\0'; + } else { + completion->success = 1; + completion->should_broadcast = 1; + completion->run_post_actions = (core_rc == 0); + } + } + cJSON_Delete(event_obj); + } + } + + async_event_completion_push(completion); + if (ws_context) { + lws_cancel_service(ws_context); + } + + free(job); + } + + return NULL; +} + +static int start_async_event_worker(void) { + if (g_async_event_worker_running) { + return 0; + } + + g_async_event_worker_running = 1; + if (pthread_create(&g_async_event_worker_thread, NULL, async_event_worker_main, NULL) != 0) { + g_async_event_worker_running = 0; + return -1; + } + + return 0; +} + +static void stop_async_event_worker(void) { + if (!g_async_event_worker_running) { + return; + } + + pthread_mutex_lock(&g_async_event_job_mutex); + g_async_event_worker_running = 0; + pthread_cond_broadcast(&g_async_event_job_cond); + pthread_mutex_unlock(&g_async_event_job_mutex); + + pthread_join(g_async_event_worker_thread, NULL); + + pthread_mutex_lock(&g_async_event_job_mutex); + async_event_job_t* job = g_async_event_job_head; + while (job) { + async_event_job_t* next = job->next; + free(job->event_json); + free(job); + job = next; + } + g_async_event_job_head = g_async_event_job_tail = NULL; + pthread_mutex_unlock(&g_async_event_job_mutex); + + async_event_completion_t* completion = NULL; + while ((completion = async_event_completion_pop()) != NULL) { + free(completion->event_json); + free(completion); + } +} + +// Returns 0 when accepted for async handling, 1 when not eligible, -1 on submit failure. +static int try_submit_async_event(cJSON* event, const char* event_json, struct lws* wsi, struct per_session_data* pss) { + if (!event || !event_json || !wsi || !pss || !g_async_event_worker_running) { + return -1; + } + + int event_kind = 0; + char event_id[65] = {0}; + if (!event_is_async_eligible(event, &event_kind, event_id)) { + return 1; + } + + async_event_job_t* job = calloc(1, sizeof(*job)); + if (!job) { + return -1; + } + + job->event_json = strdup(event_json); + if (!job->event_json) { + free(job); + return -1; + } + + strncpy(job->event_id, event_id, sizeof(job->event_id) - 1); + job->event_id[sizeof(job->event_id) - 1] = '\0'; + job->event_kind = event_kind; + job->wsi = wsi; + job->pss_token = pss; + + pthread_mutex_lock(&g_async_event_job_mutex); + job->next = NULL; + if (!g_async_event_job_tail) { + g_async_event_job_head = job; + g_async_event_job_tail = job; + } else { + g_async_event_job_tail->next = job; + g_async_event_job_tail = job; + } + pthread_cond_signal(&g_async_event_job_cond); + pthread_mutex_unlock(&g_async_event_job_mutex); + + return 0; +} + +static void process_async_event_completions(void) { + async_event_completion_t* completion = NULL; + while ((completion = async_event_completion_pop()) != NULL) { + struct per_session_data* current_pss = (struct per_session_data*)lws_wsi_user(completion->wsi); + int target_alive = (current_pss && current_pss == completion->pss_token); + + int needs_event_obj = completion->run_post_actions || + (target_alive && completion->success && completion->should_broadcast); + + if (completion->success && needs_event_obj) { + cJSON* event_obj = cJSON_Parse(completion->event_json); + if (event_obj && cJSON_IsObject(event_obj)) { + // Must run on main lws thread due config/monitoring DB access. + if (completion->run_post_actions) { + store_event_post_actions(event_obj); + } + + if (target_alive && completion->should_broadcast) { + broadcast_event_to_subscriptions(event_obj); + } + + cJSON_Delete(event_obj); + } else { + completion->success = 0; + strncpy(completion->error_message, "error: failed to process event", sizeof(completion->error_message) - 1); + completion->error_message[sizeof(completion->error_message) - 1] = '\0'; + if (event_obj) { + cJSON_Delete(event_obj); + } + } + } + + if (target_alive) { + send_ok_response(completion->wsi, + current_pss, + completion->event_id, + completion->success, + completion->success ? "" : completion->error_message); + } + + free(completion->event_json); + free(completion); + } +} // Message queue functions for proper libwebsockets pattern @@ -666,7 +1166,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Initialize session activity tracking pss->session_active = 0; - pss->idle_timeout_sec = get_config_int("idle_connection_timeout_sec", 30); + pss->idle_timeout_sec = hot_cfg_idle_connection_timeout_sec(); // Set idle timeout for ALL connections (not just auth-required) // This catches bots that connect and do nothing @@ -678,7 +1178,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Also set auth timeout if auth is required (separate concern) if (pss->nip42_auth_required_events || pss->nip42_auth_required_subscriptions) { - int auth_timeout = get_config_int("nip42_auth_timeout_sec", 10); + int auth_timeout = hot_cfg_nip42_auth_timeout_sec(); // Use the shorter of the two timeouts int effective_timeout = (pss->idle_timeout_sec > 0 && pss->idle_timeout_sec < auth_timeout) ? pss->idle_timeout_sec @@ -844,7 +1344,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso } } - // Call unified validator with JSON string + // Try async EVENT offload first (Phase 1). + int async_submit_rc = try_submit_async_event(event, event_json_str, wsi, pss); + if (async_submit_rc == 0) { + free(event_json_str); + cJSON_Delete(json); + return 0; + } + + // Call unified validator with JSON string (sync fallback path) size_t event_json_len = strlen(event_json_str); int validation_result = nostr_validate_unified_request(event_json_str, event_json_len); @@ -905,8 +1413,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso } if (is_protected_event) { - // Check if protected events are enabled using config - int protected_events_enabled = get_config_bool("nip70_protected_events_enabled", 0); + // Check if protected events are enabled using hot-path cache + int protected_events_enabled = hot_cfg_nip70_protected_events_enabled(); if (!protected_events_enabled) { // Protected events not supported @@ -1014,7 +1522,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // NIP-17 gift wrap events // Admin DM processing is opt-in via nip17_admin_enabled config (default: off) // to prevent expensive decryption on every incoming gift wrap event - int nip17_admin_enabled = get_config_bool("nip17_admin_enabled", 0); + int nip17_admin_enabled = hot_cfg_nip17_admin_enabled(); if (nip17_admin_enabled) { char nip17_error[512] = {0}; cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi); @@ -1167,7 +1675,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Auth timeout: close connection if challenge was sent but client // hasn't authenticated within nip42_auth_timeout_sec seconds - int auth_timeout = get_config_int("nip42_auth_timeout_sec", 10); + int auth_timeout = hot_cfg_nip42_auth_timeout_sec(); if (auth_timeout > 0 && pss->connection_established > 0) { time_t connection_age = time(NULL) - pss->connection_established; if (connection_age >= auth_timeout) { @@ -1188,7 +1696,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // WoT read restriction check (wot_enabled == 2) // After NIP-42 auth, check if authenticated pubkey is in WoT whitelist - if (pss && pss->authenticated && get_config_int("wot_enabled", 0) == 2) { + if (pss && pss->authenticated && hot_cfg_wot_enabled() == 2) { // Client is authenticated - check if their pubkey is in the WoT whitelist extern int check_database_auth_rules(const char* pubkey, const char* operation, const char* resource_hash); int wot_result = check_database_auth_rules(pss->authenticated_pubkey, "subscription", NULL); @@ -1634,7 +2142,16 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso } } - // Call unified validator with JSON string + // Try async EVENT offload first (Phase 1). + int async_submit_rc = try_submit_async_event(event, event_json_str, wsi, pss); + if (async_submit_rc == 0) { + free(event_json_str); + cJSON_Delete(json); + free(message); + return 0; + } + + // Call unified validator with JSON string (sync fallback path) size_t event_json_len = strlen(event_json_str); int validation_result = nostr_validate_unified_request(event_json_str, event_json_len); @@ -1696,8 +2213,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso } if (is_protected_event) { - // Check if protected events are enabled using config - int protected_events_enabled = get_config_bool("nip70_protected_events_enabled", 0); + // Check if protected events are enabled using hot-path cache + int protected_events_enabled = hot_cfg_nip70_protected_events_enabled(); if (!protected_events_enabled) { // Protected events not supported @@ -1805,7 +2322,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // NIP-17 gift wrap events // Admin DM processing is opt-in via nip17_admin_enabled config (default: off) // to prevent expensive decryption on every incoming gift wrap event - int nip17_admin_enabled = get_config_bool("nip17_admin_enabled", 0); + int nip17_admin_enabled = hot_cfg_nip17_admin_enabled(); if (nip17_admin_enabled) { char nip17_error[512] = {0}; cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi); @@ -1960,7 +2477,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso // Auth timeout: close connection if challenge was sent but client // hasn't authenticated within nip42_auth_timeout_sec seconds - int auth_timeout = get_config_int("nip42_auth_timeout_sec", 10); + int auth_timeout = hot_cfg_nip42_auth_timeout_sec(); if (auth_timeout > 0 && pss->connection_established > 0) { time_t connection_age = time(NULL) - pss->connection_established; if (connection_age >= auth_timeout) { @@ -2646,6 +3163,12 @@ int start_websocket_relay(int port_override, int strict_port) { // Static variable for connection age check timing static time_t last_connection_age_check = 0; + if (start_async_event_worker() != 0) { + DEBUG_WARN("Async event worker failed to start; EVENT path will remain synchronous"); + } + + pthread_setname_np(pthread_self(), "lws-main"); + // Main event loop with proper signal handling while (g_server_running && !g_shutdown_flag) { int result = lws_service(ws_context, 1000); @@ -2658,9 +3181,12 @@ int start_websocket_relay(int port_override, int strict_port) { // Drain completed async REQ jobs and emit EVENT/EOSE on service thread. process_req_async_completions(); + // Drain completed async EVENT jobs and emit OK/broadcast on service thread. + process_async_event_completions(); + // Check if it's time to post status update time_t current_time = time(NULL); - int status_post_hours = get_config_int("kind_1_status_posts_hours", 0); + int status_post_hours = hot_cfg_kind_1_status_posts_hours(); if (status_post_hours > 0) { int seconds_interval = status_post_hours * 3600; // Convert hours to seconds @@ -2672,19 +3198,19 @@ int start_websocket_relay(int port_override, int strict_port) { } // Check connection age limits and run IP ban maintenance (every 60 seconds) - int max_connection_seconds = get_config_int("max_connection_seconds", 86400); + int max_connection_seconds = hot_cfg_max_connection_seconds(); if (current_time - last_connection_age_check >= 60) { last_connection_age_check = current_time; // Live debug level update: read from config table so it can be changed // without restarting the relay (via config_set admin command or direct SQL) - int config_debug_level = get_config_int("debug_level", -1); + int config_debug_level = hot_cfg_debug_level(); if (config_debug_level >= 0 && config_debug_level != g_debug_level) { DEBUG_WARN("Debug level changed: %d -> %d", g_debug_level, config_debug_level); g_debug_level = config_debug_level; } // Check and close idle connections (no REQ/EVENT sent within timeout) - int idle_timeout_sec = get_config_int("idle_connection_timeout_sec", 30); + int idle_timeout_sec = hot_cfg_idle_connection_timeout_sec(); check_idle_connections(idle_timeout_sec); if (max_connection_seconds > 0) { @@ -2697,6 +3223,8 @@ int start_websocket_relay(int port_override, int strict_port) { } } + stop_async_event_worker(); + lws_context_destroy(ws_context); ws_context = NULL; return 0; diff --git a/test_results_20260401_110147.log b/test_results_20260401_110147.log new file mode 100644 index 0000000..7b0e31a --- /dev/null +++ b/test_results_20260401_110147.log @@ -0,0 +1,18 @@ +2026-04-01 11:01:47 - ========================================== +2026-04-01 11:01:47 - C-Relay Comprehensive Test Suite Runner +2026-04-01 11:01:47 - ========================================== +2026-04-01 11:01:47 - Relay URL: ws://127.0.0.1:8888 +2026-04-01 11:01:47 - Log file: test_results_20260401_110147.log +2026-04-01 11:01:47 - Report file: test_report_20260401_110147.html +2026-04-01 11:01:47 - +2026-04-01 11:01:47 - Checking relay status at ws://127.0.0.1:8888... +2026-04-01 11:01:47 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m +2026-04-01 11:01:47 - +2026-04-01 11:01:47 - Starting comprehensive test execution... +2026-04-01 11:01:47 - +2026-04-01 11:01:47 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m +2026-04-01 11:01:47 - ========================================== +2026-04-01 11:01:47 - Running Test Suite: SQL Injection Tests +2026-04-01 11:01:47 - Description: Comprehensive SQL injection vulnerability testing +2026-04-01 11:01:47 - ========================================== +2026-04-01 11:01:47 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m diff --git a/tests/test_results_20260401_110200.log b/tests/test_results_20260401_110200.log new file mode 100644 index 0000000..c1a47c7 --- /dev/null +++ b/tests/test_results_20260401_110200.log @@ -0,0 +1,733 @@ +2026-04-01 11:02:00 - ========================================== +2026-04-01 11:02:00 - C-Relay Comprehensive Test Suite Runner +2026-04-01 11:02:00 - ========================================== +2026-04-01 11:02:00 - Relay URL: ws://127.0.0.1:8888 +2026-04-01 11:02:00 - Log file: test_results_20260401_110200.log +2026-04-01 11:02:00 - Report file: test_report_20260401_110200.html +2026-04-01 11:02:00 - +2026-04-01 11:02:00 - Checking relay status at ws://127.0.0.1:8888... +2026-04-01 11:02:00 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m +2026-04-01 11:02:00 - +2026-04-01 11:02:00 - Starting comprehensive test execution... +2026-04-01 11:02:00 - +2026-04-01 11:02:00 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m +2026-04-01 11:02:00 - ========================================== +2026-04-01 11:02:00 - Running Test Suite: SQL Injection Tests +2026-04-01 11:02:00 - Description: Comprehensive SQL injection vulnerability testing +2026-04-01 11:02:00 - ========================================== +========================================== +C-Relay SQL Injection Test Suite +========================================== +Testing against relay at ws://127.0.0.1:8888 + +=== Basic Connectivity Test === +Testing Basic connectivity... PASSED - Valid query works + +=== Authors Filter SQL Injection Tests === +Testing Authors filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: /*... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: */... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: /**/... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: #... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error) +Testing Authors filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error) + +=== IDs Filter SQL Injection Tests === +Testing IDs filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: /*... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: */... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: /**/... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: #... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error) +Testing IDs filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error) + +=== Kinds Filter SQL Injection Tests === +Testing Kinds filter with string injection... PASSED - SQL injection blocked (rejected with error) +Testing Kinds filter with negative value... PASSED - SQL injection blocked (rejected with error) +Testing Kinds filter with very large value... PASSED - SQL injection blocked (rejected with error) + +=== Search Filter SQL Injection Tests === +Testing Search filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: /*... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: */... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: /**/... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: #... PASSED - SQL injection blocked (query sanitized) +Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing Search filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error) +Testing Search filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error) + +=== Tag Filter SQL Injection Tests === +Testing #e tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: */... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: #... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized) +Testing #e tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: */... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: #... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized) +Testing #p tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: */... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: #... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized) +Testing #t tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: */... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: #... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized) +Testing #r tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: */... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: #... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized) +Testing #d tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized) + +=== Timestamp Filter SQL Injection Tests === +Testing Since parameter injection... PASSED - SQL injection blocked (rejected with error) +Testing Until parameter injection... PASSED - SQL injection blocked (rejected with error) + +=== Limit Parameter SQL Injection Tests === +Testing Limit parameter injection... PASSED - SQL injection blocked (rejected with error) +Testing Limit with UNION... PASSED - SQL injection blocked (rejected with error) + +=== Complex Multi-Filter SQL Injection Tests === +Testing Multi-filter with authors injection... PASSED - SQL injection blocked (rejected with error) +Testing Multi-filter with search injection... PASSED - SQL injection blocked (rejected with error) +Testing Multi-filter with tag injection... PASSED - SQL injection blocked (query sanitized) + +=== COUNT Message SQL Injection Tests === +Testing COUNT with authors payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized) +Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: admin'--... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: admin'--... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: /*... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: /*... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: */... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: */... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: /**/... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: /**/... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: #... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: #... PASSED - SQL injection blocked (query sanitized) +Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized) +Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized) +Testing COUNT with authors payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with authors payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error) +Testing COUNT with search payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error) + +=== Edge Case SQL Injection Tests === +Testing Empty string injection... PASSED - SQL injection blocked (rejected with error) +Testing Null byte injection... PASSED - SQL injection blocked (silently rejected) +Testing Unicode injection... PASSED - SQL injection blocked (rejected with error) +Testing Very long injection payload... PASSED - SQL injection blocked (rejected with error) + +=== Subscription ID SQL Injection Tests === +Testing Subscription ID injection... PASSED - SQL injection blocked (rejected with error) +Testing Subscription ID with quotes... PASSED - SQL injection blocked (silently rejected) + +=== CLOSE Message SQL Injection Tests === +Testing CLOSE with injection... PASSED - SQL injection blocked (rejected with error) + +=== Test Results === +Total tests: 318 +Passed: 318 +Failed: 0 +✓ All SQL injection tests passed! +The relay appears to be protected against SQL injection attacks. +2026-04-01 11:02:07 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s) +2026-04-01 11:02:07 - ========================================== +2026-04-01 11:02:07 - Running Test Suite: Filter Validation Tests +2026-04-01 11:02:07 - Description: Input validation for REQ and COUNT messages +2026-04-01 11:02:07 - ========================================== +=== C-Relay Filter Validation Tests === +Testing against relay at ws://127.0.0.1:8888 + +Testing Valid REQ message... PASSED +Testing Valid COUNT message... PASSED + +=== Testing Filter Array Validation === +Testing Non-object filter... PASSED +Testing Too many filters... PASSED + +=== Testing Authors Validation === +Testing Invalid author type... PASSED +Testing Invalid author hex... PASSED +Testing Too many authors... PASSED + +=== Testing IDs Validation === +Testing Invalid ID type... PASSED +Testing Invalid ID hex... PASSED +Testing Too many IDs... PASSED + +=== Testing Kinds Validation === +Testing Invalid kind type... PASSED +Testing Negative kind... PASSED +Testing Too large kind... PASSED +Testing Too many kinds... PASSED + +=== Testing Timestamp Validation === +Testing Invalid since type... PASSED +Testing Negative since... PASSED +Testing Invalid until type... PASSED +Testing Negative until... PASSED + +=== Testing Limit Validation === +Testing Invalid limit type... PASSED +Testing Negative limit... PASSED +Testing Too large limit... PASSED + +=== Testing Search Validation === +Testing Invalid search type... PASSED +Testing Search too long... PASSED +Testing Search SQL injection... PASSED + +=== Testing Tag Filter Validation === +Testing Invalid tag filter type... PASSED +Testing Too many tag values... PASSED +Testing Tag value too long... PASSED + +=== Testing Rate Limiting === +Testing rate limiting with malformed requests... UNCERTAIN - Rate limiting may not have triggered (this could be normal) + +=== Test Results === +Total tests: 28 +Passed: 28 +Failed: 0 +All tests passed! +2026-04-01 11:02:10 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s) +2026-04-01 11:02:10 - ========================================== +2026-04-01 11:02:10 - Running Test Suite: Subscription Validation Tests +2026-04-01 11:02:10 - Description: Subscription ID and message validation +2026-04-01 11:02:10 - ========================================== +Testing subscription ID validation fixes... +Testing malformed subscription IDs... +Empty ID test: Connection failed (expected) +Long ID test: Connection failed (expected) +Invalid chars test: Connection failed (expected) +NULL ID test: Connection failed (expected) +Valid ID test: Failed +Testing CLOSE message validation... +CLOSE empty ID test: Connection failed (expected) +CLOSE valid ID test: Failed +Subscription validation tests completed. +2026-04-01 11:02:10 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s) +2026-04-01 11:02:10 - ========================================== +2026-04-01 11:02:10 - Running Test Suite: Memory Corruption Tests +2026-04-01 11:02:10 - Description: Buffer overflow and memory safety testing +2026-04-01 11:02:10 - ========================================== +========================================== +C-Relay Memory Corruption Test Suite +========================================== +Testing against relay at ws://127.0.0.1:8888 +Note: These tests may cause the relay to crash if vulnerabilities exist + +=== Basic Connectivity Test === +Testing Basic connectivity... PASSED - No memory corruption detected + +=== Subscription ID Memory Corruption Tests === +Testing Empty subscription ID... UNCERTAIN - Expected error but got normal response +Testing Very long subscription ID (1KB)... UNCERTAIN - Expected error but got normal response +Testing Very long subscription ID (10KB)... UNCERTAIN - Expected error but got normal response +Testing Subscription ID with null bytes... UNCERTAIN - Expected error but got normal response +Testing Subscription ID with special chars... UNCERTAIN - Expected error but got normal response +Testing Unicode subscription ID... UNCERTAIN - Expected error but got normal response +Testing Subscription ID with path traversal... UNCERTAIN - Expected error but got normal response + +=== Filter Array Memory Corruption Tests === +Testing Too many filters (50)... UNCERTAIN - Expected error but got normal response + +=== Concurrent Access Memory Tests === +Testing Concurrent subscription creation... ["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}] +PASSED - Concurrent access handled safely +Testing Concurrent CLOSE operations... + + + + + + + + + +PASSED - Concurrent access handled safely + +=== Malformed JSON Memory Tests === +Testing Unclosed JSON object... UNCERTAIN - Expected error but got normal response +Testing Mismatched brackets... UNCERTAIN - Expected error but got normal response +Testing Extra closing brackets... UNCERTAIN - Expected error but got normal response +Testing Null bytes in JSON... UNCERTAIN - Expected error but got normal response + +=== Large Message Memory Tests === +Testing Very large filter array... UNCERTAIN - Expected error but got normal response +Testing Very long search term... UNCERTAIN - Expected error but got normal response + +=== Test Results === +Total tests: 17 +Passed: 17 +Failed: 0 +✓ All memory corruption tests passed! +The relay appears to handle memory safely. +2026-04-01 11:02:11 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s) +2026-04-01 11:02:11 - ========================================== +2026-04-01 11:02:11 - Running Test Suite: Input Validation Tests +2026-04-01 11:02:11 - Description: Comprehensive input boundary testing +2026-04-01 11:02:11 - ========================================== +========================================== +C-Relay Input Validation Test Suite +========================================== +Testing against relay at ws://127.0.0.1:8888 + +=== Basic Connectivity Test === +Testing Basic connectivity... PASSED - Input accepted correctly + +=== Message Type Validation === +Testing Invalid message type - string... PASSED - Invalid input properly rejected +Testing Invalid message type - number... PASSED - Invalid input properly rejected +Testing Invalid message type - null... PASSED - Invalid input properly rejected +Testing Invalid message type - object... PASSED - Invalid input properly rejected +Testing Empty message type... PASSED - Invalid input properly rejected +Testing Very long message type... PASSED - Invalid input properly rejected + +=== Message Structure Validation === +Testing Too few arguments... PASSED - Invalid input properly rejected +Testing Too many arguments... PASSED - Invalid input properly rejected +Testing Non-array message... PASSED - Invalid input properly rejected +Testing Empty array... PASSED - Invalid input properly rejected +Testing Nested arrays incorrectly... PASSED - Invalid input properly rejected + +=== Subscription ID Boundary Tests === +Testing Valid subscription ID... PASSED - Input accepted correctly +Testing Empty subscription ID... PASSED - Invalid input properly rejected +Testing Subscription ID with spaces... PASSED - Invalid input properly rejected +Testing Subscription ID with newlines... PASSED - Invalid input properly rejected +Testing Subscription ID with tabs... PASSED - Invalid input properly rejected +Testing Subscription ID with control chars... PASSED - Invalid input properly rejected +Testing Unicode subscription ID... PASSED - Invalid input properly rejected +Testing Very long subscription ID... PASSED - Invalid input properly rejected + +=== Filter Object Validation === +Testing Valid empty filter... PASSED - Input accepted correctly +Testing Non-object filter... PASSED - Invalid input properly rejected +Testing Null filter... PASSED - Invalid input properly rejected +Testing Array filter... PASSED - Invalid input properly rejected +Testing Filter with invalid keys... PASSED - Input accepted correctly + +=== Authors Field Validation === +Testing Valid authors array... PASSED - Input accepted correctly +Testing Empty authors array... PASSED - Input accepted correctly +Testing Non-array authors... PASSED - Invalid input properly rejected +Testing Invalid hex in authors... PASSED - Invalid input properly rejected +Testing Short pubkey in authors... PASSED - Invalid input properly rejected + +=== IDs Field Validation === +Testing Valid ids array... PASSED - Input accepted correctly +Testing Empty ids array... PASSED - Input accepted correctly +Testing Non-array ids... PASSED - Invalid input properly rejected + +=== Kinds Field Validation === +Testing Valid kinds array... PASSED - Input accepted correctly +Testing Empty kinds array... PASSED - Input accepted correctly +Testing Non-array kinds... PASSED - Invalid input properly rejected +Testing String in kinds... PASSED - Invalid input properly rejected + +=== Timestamp Field Validation === +Testing Valid since timestamp... PASSED - Input accepted correctly +Testing Valid until timestamp... PASSED - Input accepted correctly +Testing String since timestamp... PASSED - Invalid input properly rejected +Testing Negative timestamp... PASSED - Invalid input properly rejected + +=== Limit Field Validation === +Testing Valid limit... PASSED - Input accepted correctly +Testing Zero limit... PASSED - Input accepted correctly +Testing String limit... PASSED - Invalid input properly rejected +Testing Negative limit... PASSED - Invalid input properly rejected + +=== Multiple Filters === +Testing Two valid filters... PASSED - Input accepted correctly +Testing Many filters... PASSED - Input accepted correctly + +=== Test Results === +Total tests: 47 +Passed: 47 +Failed: 0 +✓ All input validation tests passed! +The relay properly validates input. +2026-04-01 11:02:12 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 1s) +2026-04-01 11:02:12 - +2026-04-01 11:02:12 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m +2026-04-01 11:02:12 - ========================================== +2026-04-01 11:02:12 - Running Test Suite: Subscription Limit Tests +2026-04-01 11:02:12 - Description: Subscription limit enforcement testing +2026-04-01 11:02:12 - ========================================== +=== Subscription Limit Test === +[INFO] Testing relay at: ws://127.0.0.1:8888 +[INFO] Note: This test assumes default subscription limits (max 25 per client) + +=== Test 1: Basic Connectivity === +[INFO] Testing basic WebSocket connection... +[PASS] Basic connectivity works + +=== Test 2: Subscription Limit Enforcement === +[INFO] Testing subscription limits by creating multiple subscriptions... +[INFO] Creating multiple subscriptions within a single connection... +[INFO] Hit subscription limit at subscription 2 +[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions) + +=== Test Complete === +2026-04-01 11:02:12 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 0s) +2026-04-01 11:02:12 - ========================================== +2026-04-01 11:02:12 - Running Test Suite: Load Testing +2026-04-01 11:02:12 - Description: High concurrent connection testing +2026-04-01 11:02:12 - ========================================== +========================================== +C-Relay Load Testing Suite +========================================== +Testing against relay at ws://127.0.0.1:8888 + +=== Basic Connectivity Test === +✓ Relay is accessible + +========================================== +Load Test: Light Load Test +Description: Basic load test with moderate concurrent connections +Concurrent clients: 10 +Messages per client: 5 +========================================== +Launching 10 clients... +All clients completed. Processing results... + +=== Load Test Results === +Test duration: 1s +Total connections attempted: 10 +Successful connections: 10 +Failed connections: 0 +Connection success rate: 100% +Messages expected: 50 +Messages sent: 50 +Messages received: 100 +✓ EXCELLENT: High connection success rate + +Checking relay responsiveness... ✓ Relay is still responsive + +========================================== +Load Test: Medium Load Test +Description: Moderate load test with higher concurrency +Concurrent clients: 25 +Messages per client: 10 +========================================== +Launching 25 clients... +All clients completed. Processing results... + +=== Load Test Results === +Test duration: 4s +Total connections attempted: 35 +Successful connections: 25 +Failed connections: 0 +Connection success rate: 71% +Messages expected: 250 +Messages sent: 250 +Messages received: 500 +✗ POOR: Low connection success rate + +Checking relay responsiveness... ✓ Relay is still responsive + +========================================== +Load Test: Heavy Load Test +Description: Heavy load test with high concurrency +Concurrent clients: 50 +Messages per client: 20 +========================================== +Launching 50 clients... +All clients completed. Processing results... + +=== Load Test Results === +Test duration: 14s +Total connections attempted: 85 +Successful connections: 50 +Failed connections: 0 +Connection success rate: 58% +Messages expected: 1000 +Messages sent: 1000 +Messages received: 2000 +✗ POOR: Low connection success rate + +Checking relay responsiveness... ✓ Relay is still responsive + +========================================== +Load Test: Stress Test +Description: Maximum load test to find breaking point +Concurrent clients: 100 +Messages per client: 50 +========================================== +Launching 100 clients... +All clients completed. Processing results... + +=== Load Test Results === +Test duration: 64s +Total connections attempted: 185 +Successful connections: 100 +Failed connections: 0 +Connection success rate: 54% +Messages expected: 5000 +Messages sent: 5000 +Messages received: 7500 +✗ POOR: Low connection success rate + +Checking relay responsiveness... ✓ Relay is still responsive + +========================================== +Load Testing Complete +========================================== +All load tests completed. Check individual test results above. +If any tests failed, the relay may need optimization or have resource limits. +2026-04-01 11:03:37 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s) +2026-04-01 11:03:37 - ========================================== +2026-04-01 11:03:37 - Running Test Suite: Stress Testing +2026-04-01 11:03:37 - Description: Resource usage and stability testing +2026-04-01 11:03:37 - ========================================== +2026-04-01 11:03:37 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m