@@ -1184,6 +1184,70 @@ TEST(pipeline_tsjs_receiver_parallel_keeps_service_edges) {
11841184 PASS ();
11851185}
11861186
1187+ /* Slash-prefixed call arguments are not necessarily HTTP routes. Keep the
1188+ * parallel arg-url heuristic from minting Route nodes for filesystem paths or
1189+ * regex-replacement operands, while preserving a genuine API path. */
1190+ TEST (pipeline_arg_url_rejects_non_http_slash_arguments ) {
1191+ char tmp [256 ];
1192+ snprintf (tmp , sizeof (tmp ), "/tmp/cbm_arg_url_guard_XXXXXX" );
1193+ if (!cbm_mkdtemp (tmp )) {
1194+ FAIL ("tmpdir" );
1195+ }
1196+
1197+ write_temp_file (tmp , "src/args.py" ,
1198+ "import requests\n"
1199+ "TMP = '/tmp/pgv_fuzz.bin'\n"
1200+ "def run_copy(path):\n"
1201+ " return path\n"
1202+ "def write_fixture():\n"
1203+ " return run_copy(TMP)\n"
1204+ "def load_api():\n"
1205+ " return requests.get('/api/data')\n" );
1206+ write_temp_file (tmp , "src/regex.js" ,
1207+ "function sink(value) { return value; }\n"
1208+ "export function sanitize(template) {\n"
1209+ " sink(/<table/i);\n"
1210+ " return template.replace('/html/g', '');\n"
1211+ "}\n" );
1212+ for (int i = 0 ; i < 52 ; i ++ ) {
1213+ char name [64 ];
1214+ char body [128 ];
1215+ snprintf (name , sizeof (name ), "src/filler%d.ts" , i );
1216+ snprintf (body , sizeof (body ), "export function filler%d(): number { return %d; }\n" , i , i );
1217+ write_temp_file (tmp , name , body );
1218+ }
1219+
1220+ char * old_workers = getenv ("CBM_WORKERS" );
1221+ char * saved = old_workers ? strdup (old_workers ) : NULL ;
1222+ cbm_setenv ("CBM_WORKERS" , "4" , 1 );
1223+
1224+ char db_path [512 ];
1225+ snprintf (db_path , sizeof (db_path ), "%s/arg_url_guard.db" , tmp );
1226+ cbm_pipeline_t * p = cbm_pipeline_new (tmp , db_path , CBM_MODE_FULL );
1227+ ASSERT_NOT_NULL (p );
1228+ ASSERT_EQ (cbm_pipeline_run (p ), 0 );
1229+ const char * project = cbm_pipeline_project_name (p );
1230+
1231+ cbm_store_t * s = cbm_store_open_path (db_path );
1232+ ASSERT_NOT_NULL (s );
1233+
1234+ ASSERT_EQ (count_nodes_named (s , project , "/html/g" ), 0 );
1235+ ASSERT_EQ (count_nodes_named (s , project , "/<table/i" ), 0 );
1236+ ASSERT_EQ (count_nodes_named (s , project , "/tmp/pgv_fuzz.bin" ), 0 );
1237+ ASSERT_GTE (count_nodes_named (s , project , "/api/data" ), 1 );
1238+
1239+ cbm_store_close (s );
1240+ cbm_pipeline_free (p );
1241+ if (saved ) {
1242+ cbm_setenv ("CBM_WORKERS" , saved , 1 );
1243+ free (saved );
1244+ } else {
1245+ cbm_unsetenv ("CBM_WORKERS" );
1246+ }
1247+ th_rmtree (tmp );
1248+ PASS ();
1249+ }
1250+
11871251/* Native `fetch()` (#856), sequential path (< 50 files → pass_calls.c). A bare
11881252 * unqualified call to the global fetch API has no import and no local
11891253 * definition anywhere in this project, so registry resolution comes back
@@ -7790,6 +7854,7 @@ SUITE(pipeline) {
77907854 RUN_TEST (pipeline_incremental_preserves_cross_file_calls );
77917855 RUN_TEST (pipeline_tsjs_receiver_suppresses_weak_method_edge );
77927856 RUN_TEST (pipeline_tsjs_receiver_parallel_keeps_service_edges );
7857+ RUN_TEST (pipeline_arg_url_rejects_non_http_slash_arguments );
77937858 RUN_TEST (pipeline_native_fetch_classified_as_http_calls );
77947859 RUN_TEST (pipeline_native_fetch_parallel_classified_as_http_calls );
77957860 RUN_TEST (pipeline_local_fetch_shadow_not_classified_as_http );
0 commit comments