Skip to content

Commit a109e97

Browse files
fix(store,mcp): prevent ghost .db file creation for unknown projects
- Add cbm_store_open_path_query() that opens with SQLITE_OPEN_READWRITE only (no SQLITE_OPEN_CREATE); returns NULL when file is absent - Declare cbm_store_open_path_query() in store.h - Change resolve_store() in mcp.c to call cbm_store_open_path_query so querying a nonexistent project never creates a ghost .db file - Indexing path (cbm_store_open_path) retains SQLITE_OPEN_CREATE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 172d3a6 commit a109e97

3 files changed

Lines changed: 46 additions & 2 deletions

File tree

src/mcp/mcp.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,10 +637,11 @@ static cbm_store_t *resolve_store(cbm_mcp_server_t *srv, const char *project) {
637637
srv->store = NULL;
638638
}
639639

640-
/* Open project's .db file */
640+
/* Open project's .db file — query-only open (no SQLITE_OPEN_CREATE) to
641+
* prevent ghost .db file creation for unknown/unindexed projects. */
641642
char path[1024];
642643
project_db_path(project, path, sizeof(path));
643-
srv->store = cbm_store_open_path(path);
644+
srv->store = cbm_store_open_path_query(path);
644645
srv->owns_store = true;
645646
free(srv->current_project);
646647
srv->current_project = heap_strdup(project);

src/store/store.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,45 @@ cbm_store_t *cbm_store_open_path(const char *db_path) {
365365
return store_open_internal(db_path, false);
366366
}
367367

368+
cbm_store_t *cbm_store_open_path_query(const char *db_path) {
369+
if (!db_path) {
370+
return NULL;
371+
}
372+
373+
cbm_store_t *s = calloc(1, sizeof(cbm_store_t));
374+
if (!s) {
375+
return NULL;
376+
}
377+
378+
/* Open read-write but do NOT create — returns SQLITE_CANTOPEN if absent. */
379+
int rc = sqlite3_open_v2(db_path, &s->db, SQLITE_OPEN_READWRITE, NULL);
380+
if (rc != SQLITE_OK) {
381+
/* File does not exist or cannot be opened — return NULL without creating. */
382+
free(s);
383+
return NULL;
384+
}
385+
386+
s->db_path = heap_strdup(db_path);
387+
388+
/* Security: block ATTACH/DETACH to prevent file creation via SQL injection. */
389+
sqlite3_set_authorizer(s->db, store_authorizer, NULL);
390+
391+
/* Register REGEXP functions. */
392+
sqlite3_create_function(s->db, "regexp", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL,
393+
sqlite_regexp, NULL, NULL);
394+
sqlite3_create_function(s->db, "iregexp", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL,
395+
sqlite_iregexp, NULL, NULL);
396+
397+
if (configure_pragmas(s, false) != CBM_STORE_OK) {
398+
sqlite3_close(s->db);
399+
free((void *)s->db_path);
400+
free(s);
401+
return NULL;
402+
}
403+
404+
return s;
405+
}
406+
368407
cbm_store_t *cbm_store_open(const char *project) {
369408
if (!project) {
370409
return NULL;

src/store/store.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ cbm_store_t *cbm_store_open_memory(void);
190190
/* Open a file-backed database at the given path. Creates if needed. */
191191
cbm_store_t *cbm_store_open_path(const char *db_path);
192192

193+
/* Open an existing file-backed database for querying only (no SQLITE_OPEN_CREATE).
194+
* Returns NULL if the file does not exist — never creates a new .db file. */
195+
cbm_store_t *cbm_store_open_path_query(const char *db_path);
196+
193197
/* Open database for a named project in the default cache dir. */
194198
cbm_store_t *cbm_store_open(const char *project);
195199

0 commit comments

Comments
 (0)