Skip to content

Commit c86f1ff

Browse files
committed
fix(pipeline): reject non-http slash arguments
Signed-off-by: Yyunozor <yyunozor@icloud.com>
1 parent ae5de7f commit c86f1ff

6 files changed

Lines changed: 86 additions & 3 deletions

File tree

‎internal/cbm/service_patterns.c‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,11 @@ static bool callee_is_delimiter_or_filesystem_builder(const char *callee_name) {
678678
method = last_colon + 2;
679679
}
680680
if (strcmp(method, "split") == 0 || strcmp(method, "rsplit") == 0 ||
681-
strcmp(method, "partition") == 0 || strcmp(method, "join") == 0) {
681+
strcmp(method, "partition") == 0 || strcmp(method, "join") == 0 ||
682+
strcmp(method, "replace") == 0 || strcmp(method, "replaceAll") == 0 ||
683+
strcmp(method, "match") == 0 || strcmp(method, "matchAll") == 0 ||
684+
strcmp(method, "search") == 0 || strcmp(method, "test") == 0 ||
685+
strcmp(method, "exec") == 0) {
682686
return true;
683687
}
684688
return strstr(callee_name, "os.path.join") != NULL || strstr(callee_name, "path.join") != NULL;

‎internal/cbm/service_patterns.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ cbm_svc_kind_t cbm_service_pattern_match(const char *resolved_qn);
4444
* classified via its real resolved QN instead and never reaches this check. */
4545
bool cbm_service_pattern_is_global_fetch(const char *callee_name);
4646

47+
/* True when a string literal is a plausible HTTP route for the given callee.
48+
* Rejects filesystem paths and non-HTTP string consumers. */
49+
bool cbm_service_pattern_is_http_route_literal(const char *literal, const char *callee_name);
50+
4751
/* Per-worker TLS cache for cbm_service_pattern_match results. The
4852
* pattern matcher runs once per resolved CALL edge in emit_service_
4953
* edge — that's 6 pattern lists × ~30 patterns × strstr per call ≈

‎src/pipeline/pass_parallel.c‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,6 +1792,13 @@ static void detect_url_in_args(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source,
17921792
const CBMCall *call) {
17931793
for (int ai = 0; ai < call->arg_count; ai++) {
17941794
const CBMCallArg *ca = &call->args[ai];
1795+
/* A slash-prefixed raw expression is not a URL string. In JS/TS this
1796+
* is notably a regex literal (`/<table/i`); genuine string literals
1797+
* and propagated constants are carried in `value`, while template
1798+
* literals keep their leading backtick in `expr`. */
1799+
if (!ca->value && ca->expr && ca->expr[0] == '/') {
1800+
continue;
1801+
}
17951802
const char *url = ca->value ? ca->value : ca->expr;
17961803
if (!url || (url[0] != '/' && url[0] != '`')) {
17971804
continue;
@@ -1800,6 +1807,9 @@ static void detect_url_in_args(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source,
18001807
if (!normalize_url_arg(url, norm, (int)sizeof(norm))) {
18011808
continue;
18021809
}
1810+
if (!cbm_service_pattern_is_http_route_literal(norm, call->callee_name)) {
1811+
continue;
1812+
}
18031813
char route_qn[CBM_ROUTE_QN_SIZE];
18041814
char cpath[CBM_SZ_256];
18051815
snprintf(route_qn, sizeof(route_qn), "__route__ANY__%s",

‎src/pipeline/pass_route_nodes.c‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,11 @@ enum {
3333
#include <stdint.h>
3434
#include "graph_buffer/graph_buffer.h"
3535
#include "foundation/log.h"
36+
#include "service_patterns.h" /* cbm_service_pattern_is_http_route_literal */
3637

3738
#include <stdio.h>
3839
#include <string.h>
3940

40-
bool cbm_service_pattern_is_http_route_literal(const char *literal, const char *callee_name);
41-
4241
/* True for characters that may appear in a ":name" route parameter. */
4342
static inline bool is_route_ident_char(char c) {
4443
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';

‎tests/test_infrascan.c‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ TEST(infrascan_http_route_literal_guard_rejects_filesystem_paths) {
2626
ASSERT_FALSE(cbm_service_pattern_is_http_route_literal("/var/run/app.json", "requests.get"));
2727
ASSERT_FALSE(cbm_service_pattern_is_http_route_literal("/locations/", "str.split"));
2828
ASSERT_FALSE(cbm_service_pattern_is_http_route_literal("/api", "os.path.join"));
29+
ASSERT_FALSE(cbm_service_pattern_is_http_route_literal("/html/g", "template.replace"));
2930
ASSERT_FALSE(cbm_service_pattern_is_http_route_literal(NULL, "requests.get"));
3031
ASSERT_FALSE(cbm_service_pattern_is_http_route_literal("", "requests.get"));
3132
ASSERT_TRUE(cbm_service_pattern_is_http_route_literal("/api/orders", "requests.get"));

‎tests/test_pipeline.c‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)