Skip to content

Commit d612dc4

Browse files
committed
Fix update command: replace system(), add macOS signing, kill stale servers
Replace system("curl ...") with cbm_exec_no_shell in both download call sites (archive + checksums.txt) to eliminate shell injection risk. Add cbm_macos_adhoc_sign() that removes quarantine xattr and ad-hoc signs the binary after replacement — fixes SIGKILL (exit 137) on macOS arm64 where unsigned binaries are killed by the kernel. Add cbm_kill_other_instances() that finds and terminates running MCP server processes before binary replacement, so stale servers don't continue serving old code. Uses pgrep on POSIX, taskkill on Windows. Skips own PID to avoid self-termination.
1 parent 178bea2 commit d612dc4

1 file changed

Lines changed: 79 additions & 12 deletions

File tree

src/cli/cli.c

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
// the correct standard headers are included below but clang-tidy doesn't map them.
1313
#include <ctype.h>
14+
#ifndef _WIN32
15+
#include <signal.h>
16+
#endif
1417
#include "foundation/compat_fs.h"
1518

1619
#ifndef CBM_VERSION
@@ -2097,20 +2100,72 @@ static int sha256_file(const char *path, char *out, size_t out_size) {
20972100
return -1;
20982101
}
20992102

2103+
/* ── Download helper (replaces system("curl ...")) ────────────── */
2104+
2105+
static int cbm_download_to_file(const char *url, const char *dest) {
2106+
const char *argv[] = {"curl", "-fSL", "--progress-bar", "-o", dest, url, NULL};
2107+
return cbm_exec_no_shell(argv);
2108+
}
2109+
2110+
static int cbm_download_to_file_quiet(const char *url, const char *dest) {
2111+
const char *argv[] = {"curl", "-fsSL", "-o", dest, url, NULL};
2112+
return cbm_exec_no_shell(argv);
2113+
}
2114+
2115+
/* ── macOS ad-hoc signing ─────────────────────────────────────── */
2116+
2117+
static int cbm_macos_adhoc_sign(const char *binary_path) {
2118+
#ifdef __APPLE__
2119+
/* Remove quarantine xattr (best effort — may not exist) */
2120+
const char *xattr_argv[] = {"xattr", "-d", "com.apple.quarantine", binary_path, NULL};
2121+
(void)cbm_exec_no_shell(xattr_argv);
2122+
2123+
/* Ad-hoc sign (required for arm64, harmless for x86_64) */
2124+
const char *sign_argv[] = {"codesign", "--sign", "-", "--force", binary_path, NULL};
2125+
return cbm_exec_no_shell(sign_argv);
2126+
#else
2127+
(void)binary_path;
2128+
return 0;
2129+
#endif
2130+
}
2131+
2132+
/* ── Kill other MCP server instances ──────────────────────────── */
2133+
2134+
static int cbm_kill_other_instances(void) {
2135+
#ifdef _WIN32
2136+
const char *argv[] = {"taskkill", "/IM", "codebase-memory-mcp.exe", "/F", NULL};
2137+
(void)cbm_exec_no_shell(argv);
2138+
return 0;
2139+
#else
2140+
int killed = 0;
2141+
pid_t self = getpid();
2142+
FILE *fp = cbm_popen("pgrep -x codebase-memory-mcp", "r");
2143+
if (!fp) {
2144+
return 0;
2145+
}
2146+
char line[32];
2147+
while (fgets(line, sizeof(line), fp)) {
2148+
pid_t pid = (pid_t)strtol(line, NULL, 10);
2149+
if (pid > 0 && pid != self) {
2150+
if (kill(pid, SIGTERM) == 0) {
2151+
killed++;
2152+
}
2153+
}
2154+
}
2155+
cbm_pclose(fp);
2156+
return killed;
2157+
#endif
2158+
}
2159+
21002160
/* Download checksums.txt and verify the archive integrity.
21012161
* Returns: 0 = verified OK, 1 = mismatch (FAIL), -1 = could not verify (warning). */
21022162
static int verify_download_checksum(const char *archive_path, const char *archive_name) {
21032163
char checksum_file[256];
21042164
snprintf(checksum_file, sizeof(checksum_file), "%s/cbm-checksums.txt", cbm_tmpdir());
21052165

2106-
char cmd[1024];
2107-
snprintf(cmd, sizeof(cmd),
2108-
"curl -fsSL -o '%s' "
2109-
"'https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/"
2110-
"checksums.txt' 2>/dev/null",
2111-
checksum_file);
2112-
// NOLINTNEXTLINE(cert-env33-c) — intentional CLI subprocess for download
2113-
int rc = system(cmd);
2166+
int rc = cbm_download_to_file_quiet(
2167+
"https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/checksums.txt",
2168+
checksum_file);
21142169
if (rc != 0) {
21152170
fprintf(stderr, "warning: could not download checksums.txt — skipping verification\n");
21162171
cbm_unlink(checksum_file);
@@ -2840,10 +2895,7 @@ int cbm_cmd_update(int argc, char **argv) {
28402895
char tmp_archive[256];
28412896
snprintf(tmp_archive, sizeof(tmp_archive), "%s/cbm-update.%s", cbm_tmpdir(), ext);
28422897

2843-
char cmd[1024];
2844-
snprintf(cmd, sizeof(cmd), "curl -fSL --progress-bar -o '%s' '%s'", tmp_archive, url);
2845-
// NOLINTNEXTLINE(cert-env33-c) — intentional CLI subprocess for download
2846-
int rc = system(cmd);
2898+
int rc = cbm_download_to_file(url, tmp_archive);
28472899
if (rc != 0) {
28482900
fprintf(stderr, "error: download failed (exit %d)\n", rc);
28492901
cbm_unlink(tmp_archive);
@@ -2870,6 +2922,14 @@ int cbm_cmd_update(int argc, char **argv) {
28702922
/* crc == -1: could not verify (warning only), crc == 0: verified OK */
28712923
}
28722924

2925+
/* Step 4c: Kill running MCP server instances before replacement */
2926+
{
2927+
int killed = cbm_kill_other_instances();
2928+
if (killed > 0) {
2929+
printf("Stopped %d running MCP server instance(s).\n", killed);
2930+
}
2931+
}
2932+
28732933
/* Step 5: Extract binary */
28742934
char bin_dest[1024];
28752935
snprintf(bin_dest, sizeof(bin_dest), "%s/.local/bin/codebase-memory-mcp", home);
@@ -2936,6 +2996,13 @@ int cbm_cmd_update(int argc, char **argv) {
29362996
}
29372997
}
29382998

2999+
/* Step 5b: macOS ad-hoc signing (required for arm64, harmless for x86_64) */
3000+
#ifdef __APPLE__
3001+
if (cbm_macos_adhoc_sign(bin_dest) != 0) {
3002+
fprintf(stderr, "warning: ad-hoc signing failed — binary may not run on macOS arm64\n");
3003+
}
3004+
#endif
3005+
29393006
/* Step 6: Reinstall skills (force to pick up new content) */
29403007
char skills_dir[1024];
29413008
snprintf(skills_dir, sizeof(skills_dir), "%s/.claude/skills", home);

0 commit comments

Comments
 (0)