Skip to content

Commit 283321f

Browse files
committed
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 reachable single-line GraphQL regression fixture and assert that emitted edge properties remain valid JSON. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: tmonestudio <tmonestudio@users.noreply.github.com>
1 parent 7f23a66 commit 283321f

3 files changed

Lines changed: 25 additions & 4 deletions

File tree

‎src/pipeline/pass_definitions.c‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum { PD_JSON_FIELD_OVERHEAD = 6 };
2424
#include "foundation/compat.h"
2525
#include "foundation/compat_fs.h"
2626
#include "foundation/limits.h"
27+
#include "foundation/str_util.h"
2728
#include "cbm.h"
2829
#include "arena.h"
2930
#include "iris_export_xml.h"
@@ -470,9 +471,10 @@ static int create_import_edges_for_file(cbm_pipeline_ctx_t *ctx, const CBMFileRe
470471
const cbm_gbuf_node_t *target =
471472
cbm_pipeline_resolve_import_node(ctx, rel, file_qn, imp, namespace_map);
472473
if (target && target->id != source_node->id) {
474+
char esc_ln[CBM_SZ_128];
475+
cbm_json_escape(esc_ln, sizeof(esc_ln), imp->local_name ? imp->local_name : "");
473476
char imp_props[CBM_SZ_256];
474-
snprintf(imp_props, sizeof(imp_props), "{\"local_name\":\"%s\"}",
475-
imp->local_name ? imp->local_name : "");
477+
snprintf(imp_props, sizeof(imp_props), "{\"local_name\":\"%s\"}", esc_ln);
476478
cbm_gbuf_insert_edge(ctx->gbuf, source_node->id, target->id, "IMPORTS", imp_props);
477479
count++;
478480
}

‎src/pipeline/pass_parallel.c‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1921,10 +1921,12 @@ static void emit_graphql_edge(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source, c
19211921
cbm_gbuf_upsert_node(gbuf, "Route", p, route_qn, "", 0, 0, "{\"source\":\"graphql\"}");
19221922

19231923
char esc_c[CBM_SZ_256];
1924+
char esc_op[CBM_SZ_512];
19241925
cbm_json_escape(esc_c, sizeof(esc_c), call->callee_name);
1926+
cbm_json_escape(esc_op, sizeof(esc_op), p);
19251927
char props[CBM_SZ_1K];
19261928
snprintf(props, sizeof(props), "{\"callee\":\"%s\",\"operation\":\"%s\",\"confidence\":%.2f}",
1927-
esc_c, p, res->confidence);
1929+
esc_c, esc_op, res->confidence);
19281930
cbm_gbuf_insert_edge(gbuf, source->id, route_id, "GRAPHQL_CALLS", props);
19291931
}
19301932

‎tests/test_lang_contract.c‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <store/store.h>
2525
#include <pipeline/pipeline.h>
2626
#include <foundation/log.h>
27+
#include <sqlite3.h>
2728

2829
#include <string.h>
2930
#include <stdlib.h>
@@ -1518,7 +1519,9 @@ TEST(contract_edge_parallel_service_edges) {
15181519
{"gql.py", "def gql(query_string):\n return query_string\n"},
15191520
{"client.py",
15201521
"from gql import gql\n\n\ndef fetch_user():\n"
1521-
" return gql(\"query GetUser { user { id name } }\")\n\n\n"
1522+
/* The embedded double quote survives extraction from this single-quoted
1523+
* Python string and breaks GRAPHQL_CALLS JSON unless operation is escaped. */
1524+
" return gql('query GetUser { user(name: \"quoted\") { id name } }')\n\n\n"
15221525
"def create_user():\n"
15231526
" return gql(\"mutation CreateUser { addUser(name: \\\"x\\\") { id } }\")\n"},
15241527
/* TRPC_CALLS: local createTRPCProxyClient (same-module resolution). */
@@ -1559,6 +1562,19 @@ TEST(contract_edge_parallel_service_edges) {
15591562
int grpc = store ? cbm_store_count_edges_by_type(store, lp.project, "GRPC_CALLS") : -1;
15601563
int trpc = store ? cbm_store_count_edges_by_type(store, lp.project, "TRPC_CALLS") : -1;
15611564
int infra = store ? cbm_store_count_edges_by_type(store, lp.project, "INFRA_MAPS") : -1;
1565+
int invalid_props = -1;
1566+
if (store) {
1567+
sqlite3_stmt *stmt = NULL;
1568+
sqlite3 *db = cbm_store_get_db(store);
1569+
if (db && sqlite3_prepare_v2(db,
1570+
"SELECT count(*) FROM edges WHERE properties IS NOT NULL "
1571+
"AND properties != '' AND json_valid(properties)=0;",
1572+
-1, &stmt, NULL) == SQLITE_OK &&
1573+
sqlite3_step(stmt) == SQLITE_ROW) {
1574+
invalid_props = sqlite3_column_int(stmt, 0);
1575+
}
1576+
sqlite3_finalize(stmt);
1577+
}
15621578
if (graphql < 1 || grpc < 1 || trpc < 1 || infra < 1) {
15631579
fprintf(stderr,
15641580
" [EDGE] parallel-service: GRAPHQL_CALLS=%d GRPC_CALLS=%d TRPC_CALLS=%d "
@@ -1571,6 +1587,7 @@ TEST(contract_edge_parallel_service_edges) {
15711587
ASSERT_TRUE(grpc >= 1);
15721588
ASSERT_TRUE(trpc >= 1);
15731589
ASSERT_TRUE(infra >= 1);
1590+
ASSERT_EQ(invalid_props, 0);
15741591
PASS();
15751592
}
15761593

0 commit comments

Comments
 (0)