@@ -5352,6 +5352,70 @@ TEST(pipeline_parallel_rust_cross_only_macro_hidden_gets_synthetic_carrier) {
53525352 PASS();
53535353}
53545354
5355+ /* Slash-prefixed call arguments are not necessarily HTTP routes. Keep the
5356+ * parallel arg-url heuristic from minting Route nodes for filesystem paths or
5357+ * regex-replacement operands, while preserving a genuine API path. */
5358+ TEST(pipeline_arg_url_rejects_non_http_slash_arguments) {
5359+ char tmp[256];
5360+ snprintf(tmp, sizeof(tmp), "/tmp/cbm_arg_url_guard_XXXXXX");
5361+ if (!cbm_mkdtemp(tmp)) {
5362+ FAIL("tmpdir");
5363+ }
5364+
5365+ write_temp_file(tmp, "src/args.py",
5366+ "import requests\n"
5367+ "TMP = '/tmp/pgv_fuzz.bin'\n"
5368+ "def run_copy(path):\n"
5369+ " return path\n"
5370+ "def write_fixture():\n"
5371+ " return run_copy(TMP)\n"
5372+ "def load_api():\n"
5373+ " return requests.get('/api/data')\n");
5374+ write_temp_file(tmp, "src/regex.js",
5375+ "function sink(value) { return value; }\n"
5376+ "export function sanitize(template) {\n"
5377+ " sink(/<table/i);\n"
5378+ " return template.replace('/html/g', '');\n"
5379+ "}\n");
5380+ for (int i = 0; i < 52; i++) {
5381+ char name[64];
5382+ char body[128];
5383+ snprintf(name, sizeof(name), "src/filler%d.ts", i);
5384+ snprintf(body, sizeof(body), "export function filler%d(): number { return %d; }\n", i, i);
5385+ write_temp_file(tmp, name, body);
5386+ }
5387+
5388+ char *old_workers = getenv("CBM_WORKERS");
5389+ char *saved = old_workers ? strdup(old_workers) : NULL;
5390+ cbm_setenv("CBM_WORKERS", "4", 1);
5391+
5392+ char db_path[512];
5393+ snprintf(db_path, sizeof(db_path), "%s/arg_url_guard.db", tmp);
5394+ cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
5395+ ASSERT_NOT_NULL(p);
5396+ ASSERT_EQ(cbm_pipeline_run(p), 0);
5397+ const char *project = cbm_pipeline_project_name(p);
5398+
5399+ cbm_store_t *s = cbm_store_open_path(db_path);
5400+ ASSERT_NOT_NULL(s);
5401+
5402+ ASSERT_EQ(count_nodes_named(s, project, "/html/g"), 0);
5403+ ASSERT_EQ(count_nodes_named(s, project, "/<table/i"), 0);
5404+ ASSERT_EQ(count_nodes_named(s, project, "/tmp/pgv_fuzz.bin"), 0);
5405+ ASSERT_GTE(count_nodes_named(s, project, "/api/data"), 1);
5406+
5407+ cbm_store_close(s);
5408+ cbm_pipeline_free(p);
5409+ if (saved) {
5410+ cbm_setenv("CBM_WORKERS", saved, 1);
5411+ free(saved);
5412+ } else {
5413+ cbm_unsetenv("CBM_WORKERS");
5414+ }
5415+ th_rmtree(tmp);
5416+ PASS();
5417+ }
5418+
53555419/* Native `fetch()` (#856), sequential path (< 50 files → pass_calls.c). A bare
53565420 * unqualified call to the global fetch API has no import and no local
53575421 * definition anywhere in this project, so registry resolution comes back
@@ -13059,6 +13123,7 @@ SUITE(pipeline) {
1305913123 RUN_TEST(pipeline_python_receiver_parallel_suppresses_weak_method_edges);
1306013124 RUN_TEST(pipeline_parallel_python_cross_only_dunder_gets_synthetic_carrier);
1306113125 RUN_TEST(pipeline_parallel_rust_cross_only_macro_hidden_gets_synthetic_carrier);
13126+ RUN_TEST(pipeline_arg_url_rejects_non_http_slash_arguments);
1306213127 RUN_TEST(pipeline_native_fetch_classified_as_http_calls);
1306313128 RUN_TEST(pipeline_native_fetch_parallel_classified_as_http_calls);
1306413129 RUN_TEST(pipeline_local_fetch_shadow_not_classified_as_http);
0 commit comments