20 KiB
Implementation Plan: Enhanced Admin Event API Structure
Current Issue
The current admin event routing at main.c:3248-3268 has a security vulnerability:
if (event_kind == 23455 || event_kind == 23456) {
// Admin event processing
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
} else {
// Regular event storage and broadcasting
}
Problem: Any event with these kinds gets routed to admin processing, regardless of authorization. This allows unauthorized users to send admin events that could be processed as legitimate admin commands.
Note: Event kinds 33334 and 33335 are no longer used and have been removed from the admin event routing.
Required Security Enhancement
Admin events must be validated for proper authorization BEFORE routing to admin processing:
- Relay Public Key Check: Event must have a
ptag equal to the relay's public key - Admin Signature Check: Event must be signed by an authorized admin private key
- Fallback to Regular Processing: If authorization fails, treat as regular event (not admin event)
Implementation Plan
Phase 1: Add Admin Authorization Validation
1.1 Create Consolidated Admin Authorization Function
Location: src/main.c or src/config.c
/**
* Consolidated admin event authorization validator
* Implements defense-in-depth security for admin events
*
* @param event - The event to validate for admin authorization
* @param error_message - Buffer for detailed error messages
* @param error_size - Size of error message buffer
* @return 0 if authorized, -1 if unauthorized, -2 if validation error
*/
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size) {
if (!event) {
snprintf(error_message, error_size, "admin_auth: null event");
return -2;
}
// Extract event components
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
if (!kind_obj || !pubkey_obj || !tags_obj) {
snprintf(error_message, error_size, "admin_auth: missing required fields");
return -2;
}
// Validation Layer 1: Kind Check
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
if (event_kind != 23455 && event_kind != 23456) {
snprintf(error_message, error_size, "admin_auth: not an admin event kind");
return -1;
}
// Validation Layer 2: Relay Targeting Check
const char* relay_pubkey = get_config_value("relay_pubkey");
if (!relay_pubkey) {
snprintf(error_message, error_size, "admin_auth: relay pubkey not configured");
return -2;
}
// Check for 'p' tag targeting this relay
int has_relay_target = 0;
if (cJSON_IsArray(tags_obj)) {
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags_obj) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
const char* name = cJSON_GetStringValue(tag_name);
const char* value = cJSON_GetStringValue(tag_value);
if (strcmp(name, "p") == 0 && strcmp(value, relay_pubkey) == 0) {
has_relay_target = 1;
break;
}
}
}
}
}
if (!has_relay_target) {
// Admin event for different relay - not unauthorized, just not for us
snprintf(error_message, error_size, "admin_auth: admin event for different relay");
return -1;
}
// Validation Layer 3: Admin Signature Check (only if targeting this relay)
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
if (!event_pubkey) {
snprintf(error_message, error_size, "admin_auth: invalid pubkey format");
return -2;
}
const char* admin_pubkey = get_config_value("admin_pubkey");
if (!admin_pubkey || strcmp(event_pubkey, admin_pubkey) != 0) {
// This is the ONLY case where we log as "Unauthorized admin event attempt"
// because it's targeting THIS relay but from wrong admin
snprintf(error_message, error_size, "admin_auth: unauthorized admin for this relay");
log_warning("SECURITY: Unauthorized admin event attempt for this relay");
return -1;
}
// All validation layers passed
log_info("ADMIN: Admin event authorized");
return 0;
}
1.2 Update Event Routing Logic
Location: main.c:3248
// Current problematic code:
if (event_kind == 23455 || event_kind == 23456) {
// Admin event processing
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
} else {
// Regular event storage and broadcasting
}
// Enhanced secure code with consolidated authorization:
if (result == 0) {
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
if (kind_obj && cJSON_IsNumber(kind_obj)) {
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
// Check if this is an admin event
if (event_kind == 23455 || event_kind == 23456) {
// Use consolidated authorization check
char auth_error[512] = {0};
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
if (auth_result == 0) {
// Authorized admin event - process through admin API
char admin_error[512] = {0};
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
if (admin_result != 0) {
result = -1;
strncpy(error_message, admin_error, sizeof(error_message) - 1);
}
// Admin events are NOT broadcast to subscriptions
} else {
// Unauthorized admin event - treat as regular event
log_warning("Unauthorized admin event treated as regular event");
if (store_event(event) != 0) {
result = -1;
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
} else {
broadcast_event_to_subscriptions(event);
}
}
} else {
// Regular event - normal processing
if (store_event(event) != 0) {
result = -1;
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
} else {
broadcast_event_to_subscriptions(event);
}
}
}
}
Phase 2: Enhanced Admin Event Processing
2.1 Admin Event Validation in Config System
Location: src/config.c - process_admin_event_in_config()
Add additional validation within the admin processing function:
int process_admin_event_in_config(cJSON* event, char* error_buffer, size_t error_buffer_size, struct lws* wsi) {
// Double-check authorization (defense in depth)
if (!is_authorized_admin_event(event)) {
snprintf(error_buffer, error_buffer_size, "unauthorized: not a valid admin event");
return -1;
}
// Continue with existing admin event processing...
// ... rest of function unchanged
}
2.2 Logging and Monitoring
Add comprehensive logging for admin event attempts:
// In the routing logic - enhanced logging
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
int event_kind = kind_obj ? cJSON_GetNumberValue(kind_obj) : -1;
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
if (is_authorized_admin_event(event)) {
char log_msg[256];
snprintf(log_msg, sizeof(log_msg),
"ADMIN EVENT: Authorized admin event (kind=%d) from pubkey=%.16s...",
event_kind, event_pubkey);
log_info(log_msg);
} else if (event_kind == 23455 || event_kind == 23456) {
// This catches unauthorized admin event attempts
char log_msg[256];
snprintf(log_msg, sizeof(log_msg),
"SECURITY: Unauthorized admin event attempt (kind=%d) from pubkey=%.16s...",
event_kind, event_pubkey);
log_warning(log_msg);
}
Phase 3: Unified Output Flow Architecture
3.1 Current Output Flow Analysis
After analyzing both main.c and config.c, the admin event responses already flow through the standard WebSocket output pipeline. This is the correct architecture and requires no changes.
Standard WebSocket Output Pipeline
Regular Events (main.c:2978-2996):
// Database query responses
unsigned char* buf = malloc(LWS_PRE + msg_len);
memcpy(buf + LWS_PRE, msg_str, msg_len);
lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
free(buf);
OK Responses (main.c:3342-3375):
// Event processing results: ["OK", event_id, success_boolean, message]
unsigned char *buf = malloc(LWS_PRE + response_len);
memcpy(buf + LWS_PRE, response_str, response_len);
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
free(buf);
Admin Event Output Pipeline (Already Unified)
Admin Responses (config.c:2363-2414):
// Admin query responses use IDENTICAL pattern
int send_websocket_response_data(struct lws* wsi, cJSON* response_data) {
unsigned char* buf = malloc(LWS_PRE + response_len);
memcpy(buf + LWS_PRE, response_str, response_len);
// Same lws_write() call as regular events
int result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
free(buf);
return result;
}
3.2 Unified Output Flow Confirmation
✅ Admin responses already use the same WebSocket transmission mechanism as regular events
✅ Both admin and regular events use identical buffer allocation patterns
✅ Both admin and regular events use the same lws_write() function
✅ Both admin and regular events follow the same cleanup patterns
3.3 Output Flow Integration Points
The admin event processing in config.c:2436 already integrates correctly with the unified output system:
-
Admin Query Processing (
config.c:2568-2583):- Auth queries return structured JSON via
send_websocket_response_data() - System commands return status data via
send_websocket_response_data()
- Auth queries return structured JSON via
-
Response Format Consistency:
- Admin responses use standard JSON format
- Regular events use standard Nostr event format
- Both transmitted through same WebSocket pipeline
-
Error Handling Consistency:
- Admin errors returned via same WebSocket connection
- Regular event errors returned via OK messages
- Both use identical transmission mechanism
3.4 Key Architectural Benefits
No Changes Required: The output flow is already unified and correctly implemented.
Security Separation: Admin events are processed separately but responses flow through the same secure WebSocket channel.
Performance Consistency: Both admin and regular responses use the same optimized transmission path.
Maintenance Simplicity: Single WebSocket output pipeline reduces complexity and potential bugs.
3.5 Admin Event Flow Summary
Admin Event Input → Authorization Check → Admin Processing → Unified WebSocket Output
Regular Event Input → Validation → Storage + Broadcast → Unified WebSocket Output
Both flows converge at the Unified WebSocket Output stage, which is already correctly implemented.
Phase 4: Integration Points for Secure Admin Event Routing
4.1 Configuration System Integration
Required Configuration Values:
admin_pubkey- Public key of authorized administratorrelay_pubkey- Public key of this relay instance
Integration Points:
get_config_value()- Used by authorization functionget_relay_pubkey_cached()- Used for relay targeting validation- Configuration loading during startup - Must ensure admin/relay pubkeys are available
4.3 Forward Declarations Required
Location: src/main.c - Add near other forward declarations (around line 230)
// Forward declarations for enhanced admin event authorization
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
4.4 Error Handling Integration
Enhanced Error Response System:
// In main.c event processing - enhanced error handling for admin events
if (auth_result != 0) {
// Admin authorization failed - send detailed OK response
cJSON* event_id = cJSON_GetObjectItem(event, "id");
if (event_id && cJSON_IsString(event_id)) {
cJSON* response = cJSON_CreateArray();
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // Failed
cJSON_AddItemToArray(response, cJSON_CreateString(auth_error));
// Send via standard WebSocket output pipeline
char *response_str = cJSON_Print(response);
if (response_str) {
size_t response_len = strlen(response_str);
unsigned char *buf = malloc(LWS_PRE + response_len);
if (buf) {
memcpy(buf + LWS_PRE, response_str, response_len);
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
free(buf);
}
free(response_str);
}
cJSON_Delete(response);
}
}
4.5 Logging Integration Points
Console Logging: Uses existing log_warning(), log_info() functions
Security Event Categories:
- Admin authorization success logged via
log_info() - Admin authorization failures logged via
log_warning() - Admin event processing logged via existing admin logging
Phase 5: Detailed Function Specifications
5.1 Core Authorization Function
Function: is_authorized_admin_event()
Location: src/main.c or src/config.c
Dependencies:
get_config_value()for admin/relay pubkeyslog_warning()andlog_info()for loggingcJSONlibrary for event parsing
Return Values:
0- Event is authorized for admin processing-1- Event is unauthorized (treat as regular event)-2- Validation error (malformed event)
Error Handling: Detailed error messages in provided buffer for client feedback
5.2 Enhanced Event Routing
Location: main.c:3248-3340
Integration: Replaces existing admin event routing logic
Dependencies:
is_authorized_admin_event()for authorizationprocess_admin_event_in_config()for admin processingstore_event()andbroadcast_event_to_subscriptions()for regular events
Security Features:
- Graceful degradation for unauthorized admin events
- Comprehensive logging of authorization attempts
- No broadcast of admin events to subscriptions
- Detailed error responses for failed authorization
5.4 Defense-in-Depth Validation
Primary Validation: In main event routing logic
Secondary Validation: In process_admin_event_in_config() function
Tertiary Validation: In individual admin command handlers
Validation Layers:
- Kind Check - Must be admin event kind (23455/23456)
- Relay Targeting Check - Must have 'p' tag with this relay's pubkey
- Admin Signature Check - Must be signed by authorized admin (only if targeting this relay)
- Processing Check - Additional validation in admin handlers
Security Logic:
- If no 'p' tag for this relay → Admin event for different relay (not unauthorized)
- If 'p' tag for this relay + wrong admin signature → "Unauthorized admin event attempt"
Phase 6: Event Flow Documentation
6.1 Complete Event Processing Flow
┌─────────────────┐
│ WebSocket Input │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Unified │
│ Validation │ ← nostr_validate_unified_request()
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Kind-Based │
│ Routing Check │ ← Check if kind 23455/23456
└─────────┬───────┘
│
┌────▼────┐
│ Admin? │
└────┬────┘
│
┌─────▼─────┐ ┌─────────────┐
│ YES │ │ NO │
│ │ │ │
▼ │ ▼ │
┌─────────────┐ │ ┌─────────────┐ │
│ Admin │ │ │ Regular │ │
│ Authorization│ │ │ Event │ │
│ Check │ │ │ Processing │ │
└─────┬───────┘ │ └─────┬───────┘ │
│ │ │ │
┌────▼────┐ │ ▼ │
│Authorized?│ │ ┌─────────────┐ │
└────┬────┘ │ │ store_event()│ │
│ │ │ + │ │
┌─────▼─────┐ │ │ broadcast() │ │
│ YES NO │ │ └─────┬───────┘ │
│ │ │ │ │ │ │
│ ▼ ▼ │ │ ▼ │
│┌─────┐┌───┴┐ │ ┌─────────────┐ │
││Admin││Treat│ │ │ WebSocket │ │
││API ││as │ │ │ OK Response │ │
││ ││Reg │ │ └─────────────┘ │
│└──┬──┘└───┬┘ │ │
│ │ │ │ │
│ ▼ │ │ │
│┌─────────┐│ │ │
││WebSocket││ │ │
││Response ││ │ │
│└─────────┘│ │ │
└───────────┴───┘ │
│ │
└───────────────────────────┘
│
▼
┌─────────────┐
│ Unified │
│ WebSocket │
│ Output │
└─────────────┘
6.2 Security Decision Points
- Event Kind Check - Identifies potential admin events
- Authorization Validation - Three-layer security check
- Routing Decision - Admin API vs Regular processing
- Response Generation - Unified output pipeline
- Audit Logging - Security event tracking
6.3 Error Handling Paths
Validation Errors: Return detailed error messages via OK response Authorization Failures: Log security event + treat as regular event Processing Errors: Return admin-specific error responses System Errors: Fallback to standard error handling
This completes the comprehensive implementation plan for the enhanced admin event API structure with unified output flow architecture.