From 67433fcb615617337f2e9e21413606212b242152 Mon Sep 17 00:00:00 2001 From: tmonestudio Date: Sun, 12 Jul 2026 19:46:02 -0300 Subject: [PATCH] fix(pipeline): escape dynamic edge properties as JSON Escape import aliases and GraphQL operation strings before serializing edge properties, preventing raw control characters and quotes from producing malformed JSON. Add a multiline GraphQL regression fixture and assert that emitted edge properties remain valid JSON. Co-Authored-By: Claude Opus 4.8 Signed-off-by: tmonestudio --- src/pipeline/pass_definitions.c | 6 ++++-- src/pipeline/pass_parallel.c | 4 +++- tests/test_lang_contract.c | 17 ++++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index d1f506b5c..0e0050afb 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -24,6 +24,7 @@ enum { PD_JSON_FIELD_OVERHEAD = 6 }; #include "foundation/compat.h" #include "foundation/compat_fs.h" #include "foundation/limits.h" +#include "foundation/str_util.h" #include "cbm.h" #include "simhash/minhash.h" #include "semantic/ast_profile.h" @@ -468,9 +469,10 @@ static int create_import_edges_for_file(cbm_pipeline_ctx_t *ctx, const CBMFileRe const cbm_gbuf_node_t *target = cbm_pipeline_resolve_import_node(ctx, rel, file_qn, imp, namespace_map); if (target && target->id != source_node->id) { + char esc_ln[CBM_SZ_128]; + cbm_json_escape(esc_ln, sizeof(esc_ln), imp->local_name ? imp->local_name : ""); char imp_props[CBM_SZ_256]; - snprintf(imp_props, sizeof(imp_props), "{\"local_name\":\"%s\"}", - imp->local_name ? imp->local_name : ""); + snprintf(imp_props, sizeof(imp_props), "{\"local_name\":\"%s\"}", esc_ln); cbm_gbuf_insert_edge(ctx->gbuf, source_node->id, target->id, "IMPORTS", imp_props); count++; } diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 18db122be..e5e6d26b1 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -1945,10 +1945,12 @@ static void emit_graphql_edge(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source, c cbm_gbuf_upsert_node(gbuf, "Route", p, route_qn, "", 0, 0, "{\"source\":\"graphql\"}"); char esc_c[CBM_SZ_256]; + char esc_op[CBM_SZ_512]; cbm_json_escape(esc_c, sizeof(esc_c), call->callee_name); + cbm_json_escape(esc_op, sizeof(esc_op), p); char props[CBM_SZ_1K]; snprintf(props, sizeof(props), "{\"callee\":\"%s\",\"operation\":\"%s\",\"confidence\":%.2f}", - esc_c, p, res->confidence); + esc_c, esc_op, res->confidence); cbm_gbuf_insert_edge(gbuf, source->id, route_id, "GRAPHQL_CALLS", props); } diff --git a/tests/test_lang_contract.c b/tests/test_lang_contract.c index b71a39279..0f1125a2f 100644 --- a/tests/test_lang_contract.c +++ b/tests/test_lang_contract.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -1312,7 +1313,7 @@ TEST(contract_edge_parallel_service_edges) { {"gql.py", "def gql(query_string):\n return query_string\n"}, {"client.py", "from gql import gql\n\n\ndef fetch_user():\n" - " return gql(\"query GetUser { user { id name } }\")\n\n\n" + " return gql(\"\"\"query GetUser {\n user { id name }\n}\"\"\")\n\n\n" "def create_user():\n" " return gql(\"mutation CreateUser { addUser(name: \\\"x\\\") { id } }\")\n"}, /* TRPC_CALLS: local createTRPCProxyClient (same-module resolution). */ @@ -1353,6 +1354,19 @@ TEST(contract_edge_parallel_service_edges) { int grpc = store ? cbm_store_count_edges_by_type(store, lp.project, "GRPC_CALLS") : -1; int trpc = store ? cbm_store_count_edges_by_type(store, lp.project, "TRPC_CALLS") : -1; int infra = store ? cbm_store_count_edges_by_type(store, lp.project, "INFRA_MAPS") : -1; + int invalid_props = -1; + if (store) { + sqlite3_stmt *stmt = NULL; + sqlite3 *db = cbm_store_get_db(store); + if (db && sqlite3_prepare_v2(db, + "SELECT count(*) FROM edges WHERE properties IS NOT NULL " + "AND properties != '' AND json_valid(properties)=0;", + -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { + invalid_props = sqlite3_column_int(stmt, 0); + } + sqlite3_finalize(stmt); + } if (graphql < 1 || grpc < 1 || trpc < 1 || infra < 1) { fprintf(stderr, " [EDGE] parallel-service: GRAPHQL_CALLS=%d GRPC_CALLS=%d TRPC_CALLS=%d " @@ -1365,6 +1379,7 @@ TEST(contract_edge_parallel_service_edges) { ASSERT_TRUE(grpc >= 1); ASSERT_TRUE(trpc >= 1); ASSERT_TRUE(infra >= 1); + ASSERT_EQ(invalid_props, 0); PASS(); }