Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 91 additions & 27 deletions src/pipeline/pass_cross_repo.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,80 @@ static bool emit_cross_route_bidirectional(
return insert_cross_edge(tgt_store, tgt_project, handler_id, tgt_route_id, edge_type, rev, ctx);
}

/* A resolved route: the matched Route QN plus its handler's name and file. */
typedef struct {
char qn[CR_QN_BUF];
char name[CBM_SZ_256];
char file[CBM_SZ_512];
} cr_route_hit_t;

/* Resolve a client call (method + canonical path) to a handled Route in
* store: exact method QN, then ANY, then segment-wise template matching,
* since a concrete client path ("/v2/orders/123") never exact-matches a
* templated route ("/v2/orders/{}") (#523). Returns the handler id, or 0. */
static int64_t resolve_route_handler(cbm_store_t *store, const char *method, const char *curl,
cr_route_hit_t *hit, bool *failed, cr_run_context_t *ctx) {
snprintf(hit->qn, sizeof(hit->qn), "__route__%s__%s", method[0] ? method : "ANY", curl);
int64_t handler_id = find_route_handler(store, hit->qn, hit->name, sizeof(hit->name), hit->file,
sizeof(hit->file), failed);
if (!*failed && handler_id == 0) {
snprintf(hit->qn, sizeof(hit->qn), "__route__ANY__%s", curl);
handler_id = find_route_handler(store, hit->qn, hit->name, sizeof(hit->name), hit->file,
sizeof(hit->file), failed);
}
if (!*failed && handler_id == 0) {
handler_id = find_route_handler_fuzzy(store, curl, method[0] ? method : NULL, hit->qn,
sizeof(hit->qn), hit->name, sizeof(hit->name),
hit->file, sizeof(hit->file), failed, ctx);
}
return *failed ? 0 : handler_id;
}

/* True when the caller's own project has a HANDLES edge into the local Route
* node its HTTP_CALLS points at. Sets *failed on a query error. (#1459) */
static bool route_node_handled(struct sqlite3 *src_db, const char *src_project, int64_t route_id,
bool *failed) {
*failed = false;
sqlite3_stmt *s = NULL;
if (sqlite3_prepare_v2(src_db,
"SELECT 1 FROM edges WHERE project = ?1 AND target_id = ?2 "
"AND type = 'HANDLES' LIMIT 1",
CBM_NOT_FOUND, &s, NULL) != SQLITE_OK) {
*failed = true;
return false;
}
if (sqlite3_bind_text(s, SKIP_ONE, src_project, CBM_NOT_FOUND, SQLITE_STATIC) != SQLITE_OK ||
sqlite3_bind_int64(s, PAIR_LEN, route_id) != SQLITE_OK) {
sqlite3_finalize(s);
*failed = true;
return false;
}
int step_rc = sqlite3_step(s);
if (step_rc != SQLITE_ROW && step_rc != SQLITE_DONE) {
*failed = true;
}
if (sqlite3_finalize(s) != SQLITE_OK) {
*failed = true;
}
return !*failed && step_rc == SQLITE_ROW;
}

/* Caller-local floor (#1459): a call the caller's own project serves is an
* in-project call — another project exposing the same path is no evidence of
* a cross-service call. "Served" is the HANDLES edge on the Route node the
* HTTP_CALLS points at, or the same method/ANY/template resolution the target
* lookup uses, applied to the caller's own store: a method-less fetch() points
* at the ANY node while the local handler sits on the GET node. */
static bool call_served_locally(cbm_store_t *src_store, struct sqlite3 *src_db,
const char *src_project, int64_t route_id, const char *method,
const char *curl, bool *failed, cr_run_context_t *ctx) {
if (route_node_handled(src_db, src_project, route_id, failed) || *failed) {
return !*failed;
}
cr_route_hit_t local;
return resolve_route_handler(src_store, method, curl, &local, failed, ctx) != 0;
}

static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *src_project,
cbm_store_t *tgt_store, const char *tgt_project,
cr_run_context_t *ctx) {
Expand Down Expand Up @@ -632,35 +706,26 @@ static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *s
continue;
}

/* Build the expected Route QN in the target project (authority-stripped
* and param-canonicalized so client url_path matches the server handler
* regardless of base URL and framework placeholder syntax). */
char route_qn[CR_QN_BUF];
/* Canonical client path: authority-stripped and param-canonicalized so
* it matches a server handler regardless of base URL and framework
* placeholder syntax. */
char cpath[CBM_SZ_256];
const char *curl = cbm_route_canon_path(cr_url_path(url_path), cpath, sizeof(cpath));
snprintf(route_qn, sizeof(route_qn), "__route__%s__%s", method[0] ? method : "ANY", curl);

char handler_name[CBM_SZ_256] = {0};
char handler_file[CBM_SZ_512] = {0};
bool query_failed = false;
int64_t handler_id =
find_route_handler(tgt_store, route_qn, handler_name, sizeof(handler_name),
handler_file, sizeof(handler_file), &query_failed);
if (!query_failed && handler_id == 0) {
/* Try without method (ANY) */
snprintf(route_qn, sizeof(route_qn), "__route__ANY__%s", curl);
handler_id = find_route_handler(tgt_store, route_qn, handler_name, sizeof(handler_name),
handler_file, sizeof(handler_file), &query_failed);
}
if (!query_failed && handler_id == 0) {
/* Exact QN lookup missed. A concrete client path ("/v2/orders/123")
* never exact-matches a templated route ("/v2/orders/{}"), so fall
* back to segment-wise template matching. (#523) */
handler_id =
find_route_handler_fuzzy(tgt_store, curl, method[0] ? method : NULL, route_qn,
sizeof(route_qn), handler_name, sizeof(handler_name),
handler_file, sizeof(handler_file), &query_failed, ctx);
bool served_locally = call_served_locally(src_store, src_db, src_project, route_id, method,
curl, &query_failed, ctx);
if (query_failed) {
failed = true;
break;
}
if (served_locally) {
continue;
}

cr_route_hit_t hit;
int64_t handler_id =
resolve_route_handler(tgt_store, method, curl, &hit, &query_failed, ctx);
if (query_failed) {
failed = true;
break;
Expand All @@ -674,9 +739,8 @@ static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *s
}

if (!emit_cross_route_bidirectional(src_store, src_project, src_db, caller_id, route_id,
tgt_store, tgt_project, handler_id, route_qn,
handler_name, handler_file, url_path, method,
"CROSS_HTTP_CALLS", ctx)) {
tgt_store, tgt_project, handler_id, hit.qn, hit.name,
hit.file, url_path, method, "CROSS_HTTP_CALLS", ctx)) {
failed = !cr_cancel_requested(ctx);
break;
}
Expand Down
172 changes: 172 additions & 0 deletions tests/test_cross_repo.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,84 @@ static bool cross_repo_seed_http_pair(const cross_repo_fixture_t *fixture,
return ok;
}

/* Give the source project its own handler for the local Route the seeded
* HTTP_CALLS edge points at (caller-local HANDLES), as when a monorepo's
* client code calls the API the same repo serves. (#1459) */
static bool cross_repo_seed_local_handler(const cross_repo_fixture_t *fixture,
const char *source_project, const char *suffix) {
char source_path[512];
if (!cross_repo_project_path(fixture, source_project, source_path, sizeof(source_path))) {
return false;
}
cbm_store_t *source = cbm_store_open_path(source_path);
if (!source) {
return false;
}
char local_route_qn[256];
char handler_qn[256];
snprintf(local_route_qn, sizeof(local_route_qn), "%s.local-route.%s", source_project, suffix);
snprintf(handler_qn, sizeof(handler_qn), "%s.local-handle.%s", source_project, suffix);
cbm_node_t route = {0};
bool ok =
cbm_store_find_node_by_qn(source, source_project, local_route_qn, &route) == CBM_STORE_OK;
int64_t route_id = ok ? route.id : 0;
if (ok) {
cbm_node_free_fields(&route);
}
cbm_node_t handler = {.project = source_project,
.label = "Function",
.name = "handle_local",
.qualified_name = handler_qn,
.file_path = "server.c"};
int64_t handler_id = ok ? cbm_store_upsert_node(source, &handler) : 0;
cbm_edge_t handles = {.project = source_project,
.source_id = handler_id,
.target_id = route_id,
.type = "HANDLES"};
ok = ok && route_id > 0 && handler_id > 0 && cbm_store_insert_edge(source, &handles) > 0;
cbm_store_close(source);
return ok;
}

/* Give the source project a canonical "__route__GET__<path>" Route with its
* own handler, distinct from the node the seeded HTTP_CALLS points at — the
* shape extraction produces for a method-less fetch() against a local
* app.get() route. (#1459) */
static bool cross_repo_seed_local_get_route(const cross_repo_fixture_t *fixture,
const char *source_project, const char *route_path) {
char source_path[512];
if (!cross_repo_project_path(fixture, source_project, source_path, sizeof(source_path))) {
return false;
}
cbm_store_t *source = cbm_store_open_path(source_path);
if (!source) {
return false;
}
char route_qn[256];
char handler_qn[256];
snprintf(route_qn, sizeof(route_qn), "__route__GET__%s", route_path);
snprintf(handler_qn, sizeof(handler_qn), "%s.local-get-handler", source_project);
cbm_node_t route = {.project = source_project,
.label = "Route",
.name = route_path,
.qualified_name = route_qn,
.file_path = "server.c"};
cbm_node_t handler = {.project = source_project,
.label = "Function",
.name = "handle_local_get",
.qualified_name = handler_qn,
.file_path = "server.c"};
int64_t route_id = cbm_store_upsert_node(source, &route);
int64_t handler_id = cbm_store_upsert_node(source, &handler);
cbm_edge_t handles = {.project = source_project,
.source_id = handler_id,
.target_id = route_id,
.type = "HANDLES"};
bool ok = route_id > 0 && handler_id > 0 && cbm_store_insert_edge(source, &handles) > 0;
cbm_store_close(source);
return ok;
}

static bool cross_repo_exec(const cross_repo_fixture_t *fixture, const char *project,
const char *sql) {
char path[512];
Expand Down Expand Up @@ -519,7 +597,101 @@ TEST(cross_repo_accepts_project_with_missed_shadow_row_issue1609) {
PASS();
}

/* #1459 floor: a caller whose own project handles the route it calls is
* calling itself — another project exposing the same path is not evidence of
* a cross-service call. Neither direction of the pass may link it. */
TEST(cross_repo_caller_local_handler_blocks_cross_http_issue1459) {
cross_repo_fixture_t fixture;
bool setup =
cross_repo_fixture_begin(&fixture) &&
cross_repo_seed_http_pair(&fixture, "mono-1459", "api-1459", "/api/users", "mono") &&
cross_repo_seed_local_handler(&fixture, "mono-1459", "mono");
if (!setup) {
cross_repo_fixture_end(&fixture);
FAIL("failed to seed caller-local route fixture");
}
const char *target = "api-1459";
cbm_cross_repo_result_t fwd = cbm_cross_repo_match("mono-1459", &target, 1);
const char *consumer = "mono-1459";
cbm_cross_repo_result_t rev = cbm_cross_repo_match("api-1459", &consumer, 1);
int mono_edges = cross_repo_count_edges(&fixture, "mono-1459", "CROSS_HTTP_CALLS");
int api_edges = cross_repo_count_edges(&fixture, "api-1459", "CROSS_HTTP_CALLS");
cross_repo_fixture_end(&fixture);

ASSERT_FALSE(fwd.failed);
ASSERT_FALSE(rev.failed);
ASSERT_EQ(fwd.http_edges, 0);
ASSERT_EQ(rev.http_edges, 0);
ASSERT_EQ(mono_edges, 0);
ASSERT_EQ(api_edges, 0);
PASS();
}

/* #1459 floor, real-world shape: a method-less client call points at the ANY
* Route node, while the caller's own handler sits on the GET node for the same
* path. The call is still served in-project and must not cross. */
TEST(cross_repo_methodless_call_to_local_get_route_blocks_cross_http_issue1459) {
cross_repo_fixture_t fixture;
bool setup =
cross_repo_fixture_begin(&fixture) &&
cross_repo_seed_http_pair(&fixture, "mono2-1459", "api3-1459", "/api/users", "mono2") &&
cross_repo_exec(&fixture, "mono2-1459",
"UPDATE edges SET properties = '{\"url_path\":\"/api/users\"}' "
"WHERE type = 'HTTP_CALLS';") &&
cross_repo_seed_local_get_route(&fixture, "mono2-1459", "/api/users");
if (!setup) {
cross_repo_fixture_end(&fixture);
FAIL("failed to seed method-less caller-local fixture");
}
const char *target = "api3-1459";
cbm_cross_repo_result_t fwd = cbm_cross_repo_match("mono2-1459", &target, 1);
const char *consumer = "mono2-1459";
cbm_cross_repo_result_t rev = cbm_cross_repo_match("api3-1459", &consumer, 1);
int mono_edges = cross_repo_count_edges(&fixture, "mono2-1459", "CROSS_HTTP_CALLS");
int api_edges = cross_repo_count_edges(&fixture, "api3-1459", "CROSS_HTTP_CALLS");
cross_repo_fixture_end(&fixture);

ASSERT_FALSE(fwd.failed);
ASSERT_FALSE(rev.failed);
ASSERT_EQ(fwd.http_edges, 0);
ASSERT_EQ(rev.http_edges, 0);
ASSERT_EQ(mono_edges, 0);
ASSERT_EQ(api_edges, 0);
PASS();
}

/* Control for the #1459 floor: without a caller-local handler the path match
* still links the caller to the remote handler, in both run directions. */
TEST(cross_repo_caller_without_local_handler_keeps_cross_http_issue1459) {
cross_repo_fixture_t fixture;
bool setup =
cross_repo_fixture_begin(&fixture) &&
cross_repo_seed_http_pair(&fixture, "client-1459", "api2-1459", "/api/users", "client");
if (!setup) {
cross_repo_fixture_end(&fixture);
FAIL("failed to seed remote-only route fixture");
}
const char *target = "api2-1459";
cbm_cross_repo_result_t fwd = cbm_cross_repo_match("client-1459", &target, 1);
int client_edges = cross_repo_count_edges(&fixture, "client-1459", "CROSS_HTTP_CALLS");
int api_edges = cross_repo_count_edges(&fixture, "api2-1459", "CROSS_HTTP_CALLS");
const char *consumer = "client-1459";
cbm_cross_repo_result_t rev = cbm_cross_repo_match("api2-1459", &consumer, 1);
cross_repo_fixture_end(&fixture);

ASSERT_FALSE(fwd.failed);
ASSERT_FALSE(rev.failed);
ASSERT_EQ(fwd.http_edges, 1);
ASSERT_EQ(rev.http_edges, 1);
ASSERT_EQ(client_edges, 1);
ASSERT_EQ(api_edges, 1);
PASS();
}

SUITE(cross_repo) {
RUN_TEST(cross_repo_caller_local_handler_blocks_cross_http_issue1459);
RUN_TEST(cross_repo_methodless_call_to_local_get_route_blocks_cross_http_issue1459);
RUN_TEST(cross_repo_caller_without_local_handler_keeps_cross_http_issue1459);
RUN_TEST(cross_repo_accepts_project_with_missed_shadow_row_issue1609);
RUN_TEST(cross_repo_null_target_fails_without_dereference);
RUN_TEST(cross_repo_wildcard_keeps_projects_containing_internal_tokens);
Expand Down
Loading