diff options
| author | Your Name <you@example.com> | 2026-05-18 23:20:22 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-18 23:20:22 +0530 |
| commit | 980c98d2c9dbff7b4d87c867c6638637e063f984 (patch) | |
| tree | eee75aa12748c7e83794378213df13eec5d974df | |
| parent | 83967b17b234629fc0a3679296cafda08f5a9f37 (diff) | |
fix: CVM subscription #p array format + WS response + use-after-free
Critical fixes for CVM MCP roundtrip over Nostr relay:
- Fix subscription filter: #p must be array not string (relay rejected with 'bad req')
- Publish MCP responses via existing WS connection instead of opening new TLS
- Fix use-after-free: tags_str freed before nostr_event_to_json used it
- Pass esp_tls_t through process_relay_message -> handle_mcp_message chain
Verified on Board B via relay.primal.net:
- MCP initialize roundtrip: PASS
- tools/call get_config: PASS (returns device config)
- tools/call get_balance: PASS (returns balance_sats, proof_count)
- CEP-6 announcements (kinds 11316, 11317, 10002): all accepted by relay
| -rw-r--r-- | main/cvm_server.c | 78 |
1 files changed, 69 insertions, 9 deletions
diff --git a/main/cvm_server.c b/main/cvm_server.c index dd04047..96ce7d3 100644 --- a/main/cvm_server.c +++ b/main/cvm_server.c | |||
| @@ -323,6 +323,54 @@ static esp_err_t publish_event_to_relay(const char *relay_url, const char *event | |||
| 323 | return ESP_OK; | 323 | return ESP_OK; |
| 324 | } | 324 | } |
| 325 | 325 | ||
| 326 | static esp_err_t publish_kind_25910_response_ws(esp_tls_t *tls, | ||
| 327 | const char *content_json, | ||
| 328 | const char *request_event_id) | ||
| 329 | { | ||
| 330 | const tollgate_identity_t *id = identity_get(); | ||
| 331 | if (!id || !id->initialized) return ESP_FAIL; | ||
| 332 | |||
| 333 | cJSON *tags = cJSON_CreateArray(); | ||
| 334 | cJSON *e_tag = cJSON_CreateArray(); | ||
| 335 | cJSON_AddItemToArray(e_tag, cJSON_CreateString("e")); | ||
| 336 | cJSON_AddItemToArray(e_tag, cJSON_CreateString(request_event_id)); | ||
| 337 | cJSON_AddItemToArray(tags, e_tag); | ||
| 338 | |||
| 339 | char *tags_str = cJSON_PrintUnformatted(tags); | ||
| 340 | cJSON_Delete(tags); | ||
| 341 | |||
| 342 | nostr_event_t event; | ||
| 343 | nostr_event_init(&event, id->npub_hex, 25910, tags_str, content_json); | ||
| 344 | nostr_event_sign(&event, id->nsec); | ||
| 345 | |||
| 346 | char *event_json = malloc(8192); | ||
| 347 | if (!event_json) { | ||
| 348 | free(tags_str); | ||
| 349 | return ESP_ERR_NO_MEM; | ||
| 350 | } | ||
| 351 | |||
| 352 | esp_err_t ret = nostr_event_to_json(&event, event_json, 8192); | ||
| 353 | free(tags_str); | ||
| 354 | if (ret != ESP_OK) { | ||
| 355 | free(event_json); | ||
| 356 | return ret; | ||
| 357 | } | ||
| 358 | |||
| 359 | size_t msg_len = 10 + strlen(event_json) + 2; | ||
| 360 | char *msg = malloc(msg_len); | ||
| 361 | if (!msg) { | ||
| 362 | free(event_json); | ||
| 363 | return ESP_ERR_NO_MEM; | ||
| 364 | } | ||
| 365 | snprintf(msg, msg_len, "[\"EVENT\",%s]", event_json); | ||
| 366 | ESP_LOGI(TAG, "Sending WS response (%d bytes)", (int)strlen(msg)); | ||
| 367 | int rc = ws_send_text(tls, msg); | ||
| 368 | ESP_LOGI(TAG, "WS send result: %d", rc); | ||
| 369 | free(msg); | ||
| 370 | free(event_json); | ||
| 371 | return ESP_OK; | ||
| 372 | } | ||
| 373 | |||
| 326 | static esp_err_t publish_kind_25910_response(const char *relay_url, | 374 | static esp_err_t publish_kind_25910_response(const char *relay_url, |
| 327 | const char *content_json, | 375 | const char *content_json, |
| 328 | const char *request_event_id) | 376 | const char *request_event_id) |
| @@ -366,7 +414,7 @@ static bool is_owner_pubkey(const char *pubkey_hex) | |||
| 366 | return strcmp(id->npub_hex, pubkey_hex) == 0; | 414 | return strcmp(id->npub_hex, pubkey_hex) == 0; |
| 367 | } | 415 | } |
| 368 | 416 | ||
| 369 | static void handle_mcp_message(const char *relay_url, const char *sender_pubkey, | 417 | static void handle_mcp_message(esp_tls_t *tls, const char *sender_pubkey, |
| 370 | const char *event_id, const char *content) | 418 | const char *event_id, const char *content) |
| 371 | { | 419 | { |
| 372 | cJSON *msg = cJSON_Parse(content); | 420 | cJSON *msg = cJSON_Parse(content); |
| @@ -386,14 +434,20 @@ static void handle_mcp_message(const char *relay_url, const char *sender_pubkey, | |||
| 386 | if (strcmp(m, "initialize") == 0) { | 434 | if (strcmp(m, "initialize") == 0) { |
| 387 | ESP_LOGI(TAG, "MCP initialize from %s", sender_pubkey); | 435 | ESP_LOGI(TAG, "MCP initialize from %s", sender_pubkey); |
| 388 | char *resp = build_initialize_response(id_str, sender_pubkey); | 436 | char *resp = build_initialize_response(id_str, sender_pubkey); |
| 389 | publish_kind_25910_response(relay_url, resp, event_id); | 437 | if (tls) { |
| 438 | publish_kind_25910_response_ws(tls, resp, event_id); | ||
| 439 | } else { | ||
| 440 | ESP_LOGW(TAG, "No TLS for response"); | ||
| 441 | } | ||
| 390 | free(resp); | 442 | free(resp); |
| 391 | } else if (strcmp(m, "notifications/initialized") == 0) { | 443 | } else if (strcmp(m, "notifications/initialized") == 0) { |
| 392 | ESP_LOGI(TAG, "Client initialized: %s", sender_pubkey); | 444 | ESP_LOGI(TAG, "Client initialized: %s", sender_pubkey); |
| 393 | } else if (strcmp(m, "tools/list") == 0) { | 445 | } else if (strcmp(m, "tools/list") == 0) { |
| 394 | ESP_LOGI(TAG, "tools/list from %s", sender_pubkey); | 446 | ESP_LOGI(TAG, "tools/list from %s", sender_pubkey); |
| 395 | char *resp = build_tools_list_response(id_str); | 447 | char *resp = build_tools_list_response(id_str); |
| 396 | publish_kind_25910_response(relay_url, resp, event_id); | 448 | if (tls) { |
| 449 | publish_kind_25910_response_ws(tls, resp, event_id); | ||
| 450 | } | ||
| 397 | free(resp); | 451 | free(resp); |
| 398 | } else if (strcmp(m, "tools/call") == 0) { | 452 | } else if (strcmp(m, "tools/call") == 0) { |
| 399 | cJSON *params = cJSON_GetObjectItem(msg, "params"); | 453 | cJSON *params = cJSON_GetObjectItem(msg, "params"); |
| @@ -414,12 +468,16 @@ static void handle_mcp_message(const char *relay_url, const char *sender_pubkey, | |||
| 414 | 468 | ||
| 415 | mcp_response_t mcp_resp = mcp_dispatch(&req); | 469 | mcp_response_t mcp_resp = mcp_dispatch(&req); |
| 416 | char *resp = build_tool_call_response(id_str, &mcp_resp); | 470 | char *resp = build_tool_call_response(id_str, &mcp_resp); |
| 417 | publish_kind_25910_response(relay_url, resp, event_id); | 471 | if (tls) { |
| 472 | publish_kind_25910_response_ws(tls, resp, event_id); | ||
| 473 | } | ||
| 418 | free(resp); | 474 | free(resp); |
| 419 | } | 475 | } |
| 420 | } else if (strcmp(m, "ping") == 0) { | 476 | } else if (strcmp(m, "ping") == 0) { |
| 421 | char *resp = build_ping_response(id_str); | 477 | char *resp = build_ping_response(id_str); |
| 422 | publish_kind_25910_response(relay_url, resp, event_id); | 478 | if (tls) { |
| 479 | publish_kind_25910_response_ws(tls, resp, event_id); | ||
| 480 | } | ||
| 423 | free(resp); | 481 | free(resp); |
| 424 | } else { | 482 | } else { |
| 425 | ESP_LOGW(TAG, "Unknown MCP method: %s", m); | 483 | ESP_LOGW(TAG, "Unknown MCP method: %s", m); |
| @@ -433,7 +491,7 @@ static void handle_mcp_message(const char *relay_url, const char *sender_pubkey, | |||
| 433 | cJSON_Delete(msg); | 491 | cJSON_Delete(msg); |
| 434 | } | 492 | } |
| 435 | 493 | ||
| 436 | static void process_relay_message(const char *relay_url, const char *msg_str) | 494 | static void process_relay_message(esp_tls_t *tls, const char *relay_url, const char *msg_str) |
| 437 | { | 495 | { |
| 438 | cJSON *arr = cJSON_Parse(msg_str); | 496 | cJSON *arr = cJSON_Parse(msg_str); |
| 439 | if (!arr || !cJSON_IsArray(arr)) { | 497 | if (!arr || !cJSON_IsArray(arr)) { |
| @@ -492,7 +550,7 @@ static void process_relay_message(const char *relay_url, const char *msg_str) | |||
| 492 | return; | 550 | return; |
| 493 | } | 551 | } |
| 494 | 552 | ||
| 495 | handle_mcp_message(relay_url, pubkey->valuestring, event_id->valuestring, content->valuestring); | 553 | handle_mcp_message(tls, pubkey->valuestring, event_id->valuestring, content->valuestring); |
| 496 | cJSON_Delete(arr); | 554 | cJSON_Delete(arr); |
| 497 | } | 555 | } |
| 498 | 556 | ||
| @@ -505,7 +563,9 @@ static esp_err_t subscribe_to_relay(esp_tls_t *tls, const char *npub) | |||
| 505 | cJSON *kinds = cJSON_CreateArray(); | 563 | cJSON *kinds = cJSON_CreateArray(); |
| 506 | cJSON_AddItemToArray(kinds, cJSON_CreateNumber(25910)); | 564 | cJSON_AddItemToArray(kinds, cJSON_CreateNumber(25910)); |
| 507 | cJSON_AddItemToObject(filter, "kinds", kinds); | 565 | cJSON_AddItemToObject(filter, "kinds", kinds); |
| 508 | cJSON_AddStringToObject(filter, "#p", npub); | 566 | cJSON *p_tags = cJSON_CreateArray(); |
| 567 | cJSON_AddItemToArray(p_tags, cJSON_CreateString(npub)); | ||
| 568 | cJSON_AddItemToObject(filter, "#p", p_tags); | ||
| 509 | cJSON_AddNumberToObject(filter, "limit", 100); | 569 | cJSON_AddNumberToObject(filter, "limit", 100); |
| 510 | cJSON_AddItemToArray(sub, filter); | 570 | cJSON_AddItemToArray(sub, filter); |
| 511 | 571 | ||
| @@ -567,7 +627,7 @@ static void cvm_relay_task(void *arg) | |||
| 567 | char *text = parse_ws_text_frame(buf, rlen); | 627 | char *text = parse_ws_text_frame(buf, rlen); |
| 568 | if (text) { | 628 | if (text) { |
| 569 | if (strlen(text) > 0) { | 629 | if (strlen(text) > 0) { |
| 570 | process_relay_message(relay_url, text); | 630 | process_relay_message(tls, relay_url, text); |
| 571 | } | 631 | } |
| 572 | free(text); | 632 | free(text); |
| 573 | } | 633 | } |