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
2 changes: 1 addition & 1 deletion scripts/benchmark-search-graph.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# benchmark-search-graph.sh — Time search_graph name_pattern= queries against a
# codebase-memory-mcp binary to measure the regex / LIKE pre-filter performance.
#
Expand Down
2 changes: 1 addition & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# build.sh — Clean build of production binary (standard or with UI).
#
# Usage:
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-nolint-whitelist.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Verify NOLINT(misc-no-recursion) only appears on whitelisted functions.
WHITELIST="src/foundation/recursion_whitelist.h"
if [ ! -f "$WHITELIST" ]; then
Expand Down
2 changes: 1 addition & 1 deletion scripts/clean.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# clean.sh — Remove ALL build artifacts, caches, and generated files.
#
# Usage: scripts/clean.sh
Expand Down
2 changes: 1 addition & 1 deletion scripts/embed-frontend.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# embed-frontend.sh — Convert built frontend assets into linkable object files.
#
# Usage: scripts/embed-frontend.sh <dist_dir> <output_dir>
Expand Down
2 changes: 1 addition & 1 deletion scripts/env.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# env.sh — Shared environment detection for all build scripts.
#
# Sourced by test.sh, build.sh, lint.sh. Not meant to run standalone.
Expand Down
2 changes: 1 addition & 1 deletion scripts/lint.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# lint.sh — Run all linters (clang-tidy + cppcheck + clang-format).
#
# Usage:
Expand Down
2 changes: 1 addition & 1 deletion scripts/repro.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# repro.sh — Build + run the cumulative BUG-REPRODUCTION suite (test-repro).
#
# Unlike test.sh (the gating suite, must be GREEN), this suite is RED by design:
Expand Down
2 changes: 1 addition & 1 deletion scripts/soak-test.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# soak-test.sh — Endurance test for codebase-memory-mcp.
#
# Runs compressed workload cycles: queries, file mutations, reindexes, idle periods.
Expand Down
2 changes: 1 addition & 1 deletion scripts/test.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# test.sh — Clean build + run all C tests with ASan + UBSan.
#
# Usage:
Expand Down
2 changes: 1 addition & 1 deletion scripts/vendor-grammar.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# vendor-grammar.sh: Vendor a single tree-sitter grammar into internal/cbm/vendored/grammars/<name>/
# Usage: ./scripts/vendor-grammar.sh <repo_url> <name> [subdir]
# repo_url: GitHub repository URL (e.g., https://github.com/tree-sitter/tree-sitter-json)
Expand Down
54 changes: 27 additions & 27 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ enum {
#endif
#include <errno.h> // EEXIST
#include <fcntl.h> // open, O_WRONLY, O_CREAT, O_TRUNC
#include <limits.h> // UINT_MAX
#include <stdint.h> // uintptr_t
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -2054,7 +2055,7 @@ void cbm_install_hook_gate_script(const char *home, const char *binary_path) {
return;
}
(void)fprintf(f,
"#!/bin/bash\n"
"#!/usr/bin/env bash\n"
"# codebase-memory-mcp search augmenter (Claude Code PreToolUse).\n"
"# NOTE: the legacy filename is kept for zero-migration upgrades.\n"
"# Despite the name this NEVER blocks a tool call - it only adds\n"
Expand Down Expand Up @@ -2098,7 +2099,7 @@ static void cbm_install_session_reminder_script(const char *home) {
return;
}
(void)fprintf(
f, "#!/bin/bash\n"
f, "#!/usr/bin/env bash\n"
"# SessionStart hook: remind agent to use codebase-memory-mcp tools.\n"
"# Installed by codebase-memory-mcp. Fires on startup/resume/clear/compact.\n"
"cat << 'REMINDER'\n"
Expand Down Expand Up @@ -2186,7 +2187,7 @@ static void cbm_install_subagent_reminder_script(const char *home) {
* backslashes, or newlines, so the JSON below is valid as written — no
* runtime escaping (and no python3/jq dependency) is required. */
(void)fprintf(f,
"#!/bin/bash\n"
"#!/usr/bin/env bash\n"
"# SubagentStart hook: tell subagents to use codebase-memory-mcp tools.\n"
"# Installed by codebase-memory-mcp. Fires when any subagent is spawned.\n"
"# SubagentStart injects context via JSON additionalContext, not plain stdout.\n"
Expand Down Expand Up @@ -2485,12 +2486,22 @@ enum {
ZIP_STORED = 0,
ZIP_DEFLATE = 8
};
static const uint32_t ZIP_MAX_UNCOMP = 500U * 1024U * 1024U;
static const size_t ZIP_MAX_UNCOMP = 500U * 1024U * 1024U;

static uint16_t zip_read_u16le(const unsigned char *p) {
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << BYTE_SHIFT));
}

static uint32_t zip_read_u32le(const unsigned char *p) {
return ((uint32_t)p[0]) | ((uint32_t)p[1] << BYTE_SHIFT) |
((uint32_t)p[2] << (BYTE_SHIFT * CLI_PAIR_LEN)) |
((uint32_t)p[3] << (BYTE_SHIFT * CLI_JSON_INDENT));
}

/* Decompress a single zip entry (stored or deflated). Returns malloc'd buffer
* or NULL on failure. *out_len receives the decompressed size. */
static unsigned char *zip_extract_entry(const unsigned char *file_data, uint16_t method,
uint32_t comp_size, uint32_t uncomp_size, int *out_len) {
size_t comp_size, size_t uncomp_size, int *out_len) {
if (method == ZIP_STORED) {
if (comp_size > ZIP_MAX_UNCOMP) {
return NULL;
Expand All @@ -2507,15 +2518,18 @@ static unsigned char *zip_extract_entry(const unsigned char *file_data, uint16_t
if (uncomp_size > ZIP_MAX_UNCOMP) {
return NULL;
}
if (comp_size > UINT_MAX || uncomp_size > UINT_MAX) {
return NULL;
}
unsigned char *out = malloc(uncomp_size);
if (!out) {
return NULL;
}
z_stream strm = {0};
strm.next_in = (unsigned char *)file_data;
strm.avail_in = comp_size;
strm.avail_in = (uInt)comp_size;
strm.next_out = out;
strm.avail_out = uncomp_size;
strm.avail_out = (uInt)uncomp_size;
if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) {
free(out);
return NULL;
Expand Down Expand Up @@ -2545,28 +2559,14 @@ unsigned char *cbm_extract_binary_from_zip(const unsigned char *data, int data_l
break;
}

uint16_t method = (uint16_t)(data[pos + ZIP_OFF_METHOD] |
(data[pos + ZIP_OFF_METHOD + CLI_SKIP_ONE] << BYTE_SHIFT));
uint32_t comp_size =
(uint32_t)(data[pos + ZIP_OFF_COMP] |
(data[pos + ZIP_OFF_COMP + CLI_SKIP_ONE] << BYTE_SHIFT) |
(data[pos + ZIP_OFF_COMP + CLI_PAIR_LEN] << (BYTE_SHIFT * CLI_PAIR_LEN)) |
(data[pos + ZIP_OFF_COMP + CLI_JSON_INDENT]
<< (BYTE_SHIFT * CLI_JSON_INDENT)));
uint32_t uncomp_size =
(uint32_t)(data[pos + ZIP_OFF_UNCOMP] |
(data[pos + ZIP_OFF_UNCOMP + CLI_SKIP_ONE] << BYTE_SHIFT) |
(data[pos + ZIP_OFF_UNCOMP + CLI_PAIR_LEN] << (BYTE_SHIFT * CLI_PAIR_LEN)) |
(data[pos + ZIP_OFF_UNCOMP + CLI_JSON_INDENT]
<< (BYTE_SHIFT * CLI_JSON_INDENT)));
uint16_t name_len = (uint16_t)(data[pos + ZIP_OFF_NAMELEN] |
(data[pos + ZIP_OFF_NAMELEN + CLI_SKIP_ONE] << BYTE_SHIFT));
uint16_t extra_len =
(uint16_t)(data[pos + ZIP_OFF_EXTRALEN] |
(data[pos + ZIP_OFF_EXTRALEN + CLI_SKIP_ONE] << BYTE_SHIFT));
uint16_t method = zip_read_u16le(data + pos + ZIP_OFF_METHOD);
uint32_t comp_size = zip_read_u32le(data + pos + ZIP_OFF_COMP);
uint32_t uncomp_size = zip_read_u32le(data + pos + ZIP_OFF_UNCOMP);
uint16_t name_len = zip_read_u16le(data + pos + ZIP_OFF_NAMELEN);
uint16_t extra_len = zip_read_u16le(data + pos + ZIP_OFF_EXTRALEN);

int header_end = pos + ZIP_HDR_SZ + name_len + extra_len;
if (header_end + (int)comp_size > data_len) {
if (header_end > data_len || comp_size > (uint32_t)(data_len - header_end)) {
break;
}

Expand Down
4 changes: 2 additions & 2 deletions src/cypher/cypher.c
Original file line number Diff line number Diff line change
Expand Up @@ -2441,11 +2441,11 @@ static bool eval_condition(const cbm_condition_t *c, binding_t *b) {

/* IS NULL / IS NOT NULL */
if (strcmp(c->op, "IS NULL") == 0) {
result = (!actual || actual[0] == '\0');
result = (actual[0] == '\0');
return c->negated ? !result : result;
}
if (strcmp(c->op, "IS NOT NULL") == 0) {
result = (actual && actual[0] != '\0');
result = (actual[0] != '\0');
return c->negated ? !result : result;
}

Expand Down
2 changes: 1 addition & 1 deletion test-infrastructure/run.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Local CI — test all platforms before pushing.
#
# Coverage:
Expand Down
43 changes: 43 additions & 0 deletions tests/test_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "test_helpers.h"
#include <cli/cli.h>
#include <foundation/yaml.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
Expand Down Expand Up @@ -1361,6 +1362,47 @@ TEST(cli_extract_binary_from_zip_invalid) {
PASS();
}

TEST(cli_extract_binary_from_zip_rejects_truncated_deflate_size_over_int_max) {
const char *filename = "codebase-memory-mcp";
const unsigned char deflated[] = {0xAB, 0x00, 0x00}; /* raw DEFLATE for "x" */
size_t name_len = strlen(filename);
size_t zip_len = 30 + name_len + sizeof(deflated);
unsigned char *zip = calloc(1, zip_len);
ASSERT_NOT_NULL(zip);

uint32_t comp_size = 0xFFFF0000U;
uint32_t uncomp_size = 1U;
zip[0] = 0x50;
zip[1] = 0x4B;
zip[2] = 0x03;
zip[3] = 0x04;
zip[8] = 8;
zip[9] = 0;
zip[18] = (unsigned char)(comp_size & 0xFF);
zip[19] = (unsigned char)((comp_size >> 8) & 0xFF);
zip[20] = (unsigned char)((comp_size >> 16) & 0xFF);
zip[21] = (unsigned char)((comp_size >> 24) & 0xFF);
zip[22] = (unsigned char)(uncomp_size & 0xFF);
zip[23] = (unsigned char)((uncomp_size >> 8) & 0xFF);
zip[24] = (unsigned char)((uncomp_size >> 16) & 0xFF);
zip[25] = (unsigned char)((uncomp_size >> 24) & 0xFF);
zip[26] = (unsigned char)(name_len & 0xFF);
zip[27] = (unsigned char)((name_len >> 8) & 0xFF);
memcpy(zip + 30, filename, name_len);
memcpy(zip + 30 + name_len, deflated, sizeof(deflated));

int out_len = 0;
unsigned char *extracted = cbm_extract_binary_from_zip(zip, (int)zip_len, &out_len);
if (extracted) {
free(extracted);
free(zip);
FAIL("accepted a truncated deflated zip entry with a wrapped compressed size");
}
ASSERT_EQ(out_len, 0);
free(zip);
PASS();
}

/* ═══════════════════════════════════════════════════════════════════
* Skill dry-run tests
* ═══════════════════════════════════════════════════════════════════ */
Expand Down Expand Up @@ -2981,6 +3023,7 @@ SUITE(cli) {
RUN_TEST(cli_extract_binary_from_zip_not_found);
RUN_TEST(cli_extract_binary_from_zip_path_traversal);
RUN_TEST(cli_extract_binary_from_zip_invalid);
RUN_TEST(cli_extract_binary_from_zip_rejects_truncated_deflate_size_over_int_max);

/* Dry-run lifecycle (2 tests) */
RUN_TEST(cli_install_dry_run);
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pipeline.c
Original file line number Diff line number Diff line change
Expand Up @@ -4669,7 +4669,7 @@ TEST(envscan_secret_value_exclusion) {
write_temp_file(
tmpdir, "deploy.sh",
"#!/bin/bash\n"
"export GH_URL=\"https://ghp_abcdefghijklmnopqrstuvwxyz1234567890@github.com/repo\"\n"
"export GH_URL=\"https://ghp_FAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKE@github.com/repo\"\n"
"export NORMAL_ENDPOINT=\"https://api.example.com/orders\"\n");

cbm_env_binding_t bindings[32];
Expand Down
Loading