mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
fix(depindex): bound invalid automatic dependency limits
Define CBM_MAX_AUTO_DEP_LIMIT in src/depindex/depindex.h and route src/depindex/depindex.c plus src/mcp/mcp.c through cbm_dep_normalize_configured_limit. Values 1..10000 remain caps, 0 remains unlimited, and negative or oversized legacy/internal values fall back to CBM_DEFAULT_AUTO_DEP_LIMIT instead of becoming unbounded. Parse config integers through cbm_config_parse_decimal_int in src/cli/cli.c so ERANGE and values outside INT_MIN..INT_MAX return the caller default rather than narrowing to int. Reject persisted auto_dep_limit values outside 0..10000, derive the registry range and MCP schema maximum from CBM_MAX_AUTO_DEP_LIMIT, and cover the boundaries in tests/test_cli.c, tests/test_depindex.c, and tests/test_mcp.c. Verified: cli 243 passed; depindex 40 passed; mcp 273 passed; bash scripts/check-source-safety.sh passed; git diff --check passed. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
+34
-12
@@ -5346,17 +5346,28 @@ bool cbm_config_get_bool(cbm_config_t *cfg, const char *key, bool default_val) {
|
||||
return default_val;
|
||||
}
|
||||
|
||||
int cbm_config_get_int(cbm_config_t *cfg, const char *key, int default_val) {
|
||||
const char *val = cbm_config_get(cfg, key, NULL);
|
||||
if (!val) {
|
||||
return default_val;
|
||||
static bool cbm_config_parse_decimal_int(const char *value, int *out) {
|
||||
if (!value || !out) {
|
||||
return false;
|
||||
}
|
||||
char *endptr;
|
||||
long v = strtol(val, &endptr, CLI_STRTOL_BASE);
|
||||
if (endptr == val || *endptr != '\0') {
|
||||
errno = 0;
|
||||
long parsed = strtol(value, &endptr, CLI_STRTOL_BASE);
|
||||
if (errno == ERANGE || endptr == value || *endptr != '\0' ||
|
||||
parsed < INT_MIN || parsed > INT_MAX) {
|
||||
return false;
|
||||
}
|
||||
*out = (int)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
int cbm_config_get_int(cbm_config_t *cfg, const char *key, int default_val) {
|
||||
const char *val = cbm_config_get(cfg, key, NULL);
|
||||
int parsed = 0;
|
||||
if (!cbm_config_parse_decimal_int(val, &parsed)) {
|
||||
return default_val;
|
||||
}
|
||||
return (int)v;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool cbm_config_value_matches_enum(const char *range, const char *value) {
|
||||
@@ -5378,7 +5389,19 @@ static bool cbm_config_value_matches_enum(const char *range, const char *value)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool cbm_config_decimal_integer_in_range(const char *value, long minimum, long maximum) {
|
||||
int parsed = 0;
|
||||
if (!cbm_config_parse_decimal_int(value, &parsed)) {
|
||||
return false;
|
||||
}
|
||||
return parsed >= minimum && parsed <= maximum;
|
||||
}
|
||||
|
||||
static bool cbm_config_value_is_valid(const char *key, const char *value) {
|
||||
if (key && strcmp(key, CBM_CONFIG_AUTO_DEP_LIMIT) == 0 &&
|
||||
!cbm_config_decimal_integer_in_range(value, 0, CBM_MAX_AUTO_DEP_LIMIT)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; CBM_CONFIG_REGISTRY[i].key; i++) {
|
||||
if (strcmp(CBM_CONFIG_REGISTRY[i].key, key) == 0) {
|
||||
return cbm_config_value_matches_enum(CBM_CONFIG_REGISTRY[i].range, value);
|
||||
@@ -10538,7 +10561,7 @@ const cbm_config_entry_t CBM_CONFIG_REGISTRY[] = {
|
||||
"future automatic dependency indexing; it does not delete dependency projects already indexed."},
|
||||
{CBM_CONFIG_AUTO_DEP_LIMIT, CBM_STRINGIFY(CBM_DEFAULT_AUTO_DEP_LIMIT), NULL, "Dependencies",
|
||||
"Max number of packages to auto-index",
|
||||
"0-10000",
|
||||
"0-" CBM_STRINGIFY(CBM_MAX_AUTO_DEP_LIMIT),
|
||||
"When more packages are installed than this limit, the most-imported packages are selected "
|
||||
"(ranked by project import references, ties broken by name). Raise to 100+ for comprehensive "
|
||||
"dependency analysis. 0 = unlimited (may be very slow for large dependency trees)."},
|
||||
@@ -10590,12 +10613,11 @@ int cbm_config_get_effective_int(cbm_config_t *cfg, const char *key, int default
|
||||
if (!val || !val[0]) {
|
||||
return default_val;
|
||||
}
|
||||
char *endptr;
|
||||
long parsed = strtol(val, &endptr, CLI_STRTOL_BASE);
|
||||
if (endptr == val || *endptr != '\0') {
|
||||
int parsed = 0;
|
||||
if (!cbm_config_parse_decimal_int(val, &parsed)) {
|
||||
return default_val;
|
||||
}
|
||||
return (int)parsed;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* ── Config CLI subcommand ────────────────────────────────────── */
|
||||
|
||||
+14
-6
@@ -739,19 +739,27 @@ int cbm_discover_installed_deps(cbm_pkg_manager_t mgr, const char *project_root,
|
||||
|
||||
/* ── Auto-Index ────────────────────────────────────────────────── */
|
||||
|
||||
int cbm_dep_normalize_configured_limit(int limit, int default_limit) {
|
||||
if (limit == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (limit > 0 && limit <= CBM_MAX_AUTO_DEP_LIMIT) {
|
||||
return limit;
|
||||
}
|
||||
return default_limit > 0 && default_limit <= CBM_MAX_AUTO_DEP_LIMIT
|
||||
? default_limit
|
||||
: CBM_DEFAULT_AUTO_DEP_LIMIT;
|
||||
}
|
||||
|
||||
int cbm_dep_auto_index_effective_limit(cbm_config_t *cfg, int default_limit) {
|
||||
if (!cbm_config_get_bool(cfg, CBM_CONFIG_AUTO_INDEX_DEPS,
|
||||
CBM_DEFAULT_AUTO_INDEX_DEPS)) {
|
||||
if (!cbm_config_get_bool(cfg, CBM_CONFIG_AUTO_INDEX_DEPS, CBM_DEFAULT_AUTO_INDEX_DEPS)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int limit = cbm_config_get_int(cfg, CBM_CONFIG_AUTO_DEP_LIMIT, default_limit);
|
||||
/* The direct API keeps max_deps=0 as disabled. The config registry documents
|
||||
* auto_dep_limit=0 as unlimited, so map configured callers to -1 here. */
|
||||
if (limit <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return limit;
|
||||
return cbm_dep_normalize_configured_limit(limit, default_limit);
|
||||
}
|
||||
|
||||
/* Auto-detect ecosystem, discover deps, index each via flush_to_store.
|
||||
|
||||
@@ -48,6 +48,7 @@ static const char *CBM_MANIFEST_FILES[] = {
|
||||
#define CBM_DEFAULT_AUTO_INDEX_DEPS false
|
||||
#define CBM_DEFAULT_AUTO_INDEX_DEPS_STR "false"
|
||||
#define CBM_DEFAULT_AUTO_DEP_LIMIT 20
|
||||
#define CBM_MAX_AUTO_DEP_LIMIT 10000
|
||||
#define CBM_DEFAULT_DEP_MAX_FILES 1000
|
||||
|
||||
/* Config key strings */
|
||||
@@ -163,4 +164,8 @@ int cbm_dep_link_cross_edges(cbm_store_t *store, const char *project_name);
|
||||
* Return 0 to disable, -1 for unlimited, or a positive package count. */
|
||||
int cbm_dep_auto_index_effective_limit(cbm_config_t *cfg, int default_limit);
|
||||
|
||||
/* Normalize an enabled auto_dep_limit value: 0 is unlimited, valid positive
|
||||
* values are retained, and out-of-range values fall back to a bounded default. */
|
||||
int cbm_dep_normalize_configured_limit(int limit, int default_limit);
|
||||
|
||||
#endif /* CBM_DEPINDEX_H */
|
||||
|
||||
+5
-3
@@ -1052,8 +1052,10 @@ static const tool_def_t TOOLS[] = {
|
||||
"Use [\\\"*\\\"] for all indexed projects. Run list_projects to see available projects.\"},"
|
||||
"\"auto_index_deps\":{\"type\":\"boolean\",\"description\":"
|
||||
"\"Set false to skip dependency package indexing for this call. Default follows config auto_index_deps.\"},"
|
||||
"\"auto_dep_limit\":{\"type\":\"integer\",\"description\":"
|
||||
"\"Dependency package cap for this call. Default follows config auto_dep_limit; 0 means unlimited.\"},"
|
||||
"\"auto_dep_limit\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":"
|
||||
CBM_STRINGIFY(CBM_MAX_AUTO_DEP_LIMIT) ",\"description\":"
|
||||
"\"Dependency package cap for this call from 1 to " CBM_STRINGIFY(CBM_MAX_AUTO_DEP_LIMIT)
|
||||
". Default follows config auto_dep_limit; 0 means unlimited.\"},"
|
||||
"\"name\":{\"type\":\"string\",\"description\":"
|
||||
"\"Override the derived project name. Non-ASCII bytes are encoded and unsafe path characters "
|
||||
"are normalized.\"},"
|
||||
@@ -2572,7 +2574,7 @@ static int cbm_mcp_effective_auto_dep_limit(cbm_mcp_server_t *srv, const char *a
|
||||
if (cbm_mcp_has_arg(args_json, CBM_CONFIG_AUTO_DEP_LIMIT)) {
|
||||
limit = cbm_mcp_get_int_arg(args_json, CBM_CONFIG_AUTO_DEP_LIMIT, limit);
|
||||
}
|
||||
return limit <= 0 ? -1 : limit;
|
||||
return cbm_dep_normalize_configured_limit(limit, CBM_DEFAULT_AUTO_DEP_LIMIT);
|
||||
}
|
||||
|
||||
/* Query routes deliberately cache read-only stores. Mutation routes must never
|
||||
|
||||
@@ -6879,6 +6879,10 @@ TEST(cli_config_get_int) {
|
||||
cbm_config_set(cfg, "limit", "abc");
|
||||
ASSERT_EQ(cbm_config_get_int(cfg, "limit", 50000), 50000);
|
||||
|
||||
/* Values outside int range must not wrap into a valid limit. */
|
||||
cbm_config_set(cfg, "limit", "999999999999999999999999");
|
||||
ASSERT_EQ(cbm_config_get_int(cfg, "limit", 50000), 50000);
|
||||
|
||||
cbm_config_close(cfg);
|
||||
test_rmdir_r(tmpdir);
|
||||
PASS();
|
||||
@@ -6962,6 +6966,9 @@ TEST(cli_config_registry_auto_dep_limit_uses_shared_default) {
|
||||
|
||||
ASSERT_NOT_NULL(found);
|
||||
ASSERT_EQ(atoi(found->default_val), CBM_DEFAULT_AUTO_DEP_LIMIT);
|
||||
char expected_range[CBM_SZ_32];
|
||||
snprintf(expected_range, sizeof(expected_range), "0-%d", CBM_MAX_AUTO_DEP_LIMIT);
|
||||
ASSERT_STR_EQ(found->range, expected_range);
|
||||
PASS();
|
||||
}
|
||||
|
||||
|
||||
@@ -1090,6 +1090,18 @@ TEST(test_auto_index_deps_config_limit_policy) {
|
||||
ASSERT_EQ(cbm_dep_auto_index_effective_limit(cfg, CBM_DEFAULT_AUTO_DEP_LIMIT), 7);
|
||||
ASSERT_EQ(cbm_config_set(cfg, CBM_CONFIG_AUTO_DEP_LIMIT, "0"), 0);
|
||||
ASSERT_EQ(cbm_dep_auto_index_effective_limit(cfg, CBM_DEFAULT_AUTO_DEP_LIMIT), -1);
|
||||
ASSERT_EQ(
|
||||
cbm_dep_normalize_configured_limit(CBM_MAX_AUTO_DEP_LIMIT, CBM_DEFAULT_AUTO_DEP_LIMIT),
|
||||
CBM_MAX_AUTO_DEP_LIMIT);
|
||||
ASSERT_EQ(cbm_dep_normalize_configured_limit(-1, CBM_DEFAULT_AUTO_DEP_LIMIT),
|
||||
CBM_DEFAULT_AUTO_DEP_LIMIT);
|
||||
ASSERT_EQ(
|
||||
cbm_dep_normalize_configured_limit(CBM_MAX_AUTO_DEP_LIMIT + 1, CBM_MAX_AUTO_DEP_LIMIT + 1),
|
||||
CBM_DEFAULT_AUTO_DEP_LIMIT);
|
||||
ASSERT_NEQ(cbm_config_set(cfg, CBM_CONFIG_AUTO_DEP_LIMIT, "-1"), 0);
|
||||
char over_limit[CBM_SZ_32];
|
||||
snprintf(over_limit, sizeof(over_limit), "%d", CBM_MAX_AUTO_DEP_LIMIT + 1);
|
||||
ASSERT_NEQ(cbm_config_set(cfg, CBM_CONFIG_AUTO_DEP_LIMIT, over_limit), 0);
|
||||
|
||||
cbm_config_close(cfg);
|
||||
cleanup_fixture_dir(cache_tmp);
|
||||
|
||||
@@ -610,6 +610,21 @@ TEST(mcp_canonical_input_schemas_cover_implemented_format_and_verbose_options) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(mcp_index_repository_auto_dep_limit_schema_uses_shared_bounds) {
|
||||
const char *schema_json = cbm_mcp_tool_input_schema("index_repository");
|
||||
ASSERT_NOT_NULL(schema_json);
|
||||
yyjson_doc *doc = yyjson_read(schema_json, strlen(schema_json), 0);
|
||||
ASSERT_NOT_NULL(doc);
|
||||
yyjson_val *properties = yyjson_obj_get(yyjson_doc_get_root(doc), "properties");
|
||||
ASSERT_TRUE(yyjson_is_obj(properties));
|
||||
yyjson_val *limit = yyjson_obj_get(properties, CBM_CONFIG_AUTO_DEP_LIMIT);
|
||||
ASSERT_TRUE(yyjson_is_obj(limit));
|
||||
ASSERT_EQ(yyjson_get_int(yyjson_obj_get(limit, "minimum")), 0);
|
||||
ASSERT_EQ(yyjson_get_int(yyjson_obj_get(limit, "maximum")), CBM_MAX_AUTO_DEP_LIMIT);
|
||||
yyjson_doc_free(doc);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(mcp_tools_have_behavior_annotations) {
|
||||
struct {
|
||||
const char *name;
|
||||
@@ -12726,6 +12741,7 @@ SUITE(mcp) {
|
||||
RUN_TEST(mcp_tools_list_latest_metadata);
|
||||
RUN_TEST(mcp_tool_input_schemas_are_closed_in_classic_and_streamlined_modes);
|
||||
RUN_TEST(mcp_canonical_input_schemas_cover_implemented_format_and_verbose_options);
|
||||
RUN_TEST(mcp_index_repository_auto_dep_limit_schema_uses_shared_bounds);
|
||||
RUN_TEST(mcp_tools_have_behavior_annotations);
|
||||
RUN_TEST(mcp_index_repository_declares_name_override_issue571);
|
||||
RUN_TEST(mcp_tools_array_schemas_have_items);
|
||||
|
||||
Reference in New Issue
Block a user