Skip to content
Open
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ a [GitHub Release](https://github.com/colbymchenry/codegraph/releases) tagged
`vX.Y.Z`, which is where most people will look.

This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Security

- Database paths with `..` traversal sequences are now rejected in `DatabaseConnection.initialize` and `DatabaseConnection.open`, and non-`.db` extensions are blocked, so a caller cannot be tricked into opening an arbitrary file as a SQLite database.
- Every MCP tool call is now logged to stderr with a timestamp, process PID, and tool name, making unexpected tool invocations visible in daemon logs.
- The daemon socket `chmod 0600` failure is now surfaced as a warning to stderr instead of being silently swallowed, so permission issues on shared filesystems don't go unnoticed.
- Closed a path-traversal hole where a symbolic link inside an indexed project that pointed *outside* the project root could make CodeGraph serve that out-of-root file's contents (for example a file under your home directory) to the AI agent. CodeGraph now resolves symlinks when validating file access and refuses to read anything whose real location is outside the project, while still allowing symlinks that stay within it. Thanks @sulthonzh. (#527)
- CodeGraph now indexes Spring configuration files (`application.properties` / `application.yml`) by key only, and never includes their values in `codegraph_explore` or `codegraph_node` output. Previously a secret committed to one of these files — a database password, API key, or connection string with embedded credentials — could be surfaced to an AI agent that asked about nearby code, even though the agent never opened the file. The configuration keys are still indexed, so reference and impact analysis are unaffected; an agent that genuinely needs a value reads the file itself. Shopify Liquid `{% schema %}` blocks are likewise indexed by name only. (#383)

Expand Down
11 changes: 11 additions & 0 deletions src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ import { getCodeGraphDir } from '../directory';

export { SqliteDatabase, SqliteBackend } from './sqlite-adapter';

function validateDbPath(dbPath: string): void {
if (dbPath.includes('..')) {
throw new Error(`Invalid database path: path traversal not allowed`);
}
if (path.extname(path.resolve(dbPath)) !== '.db') {
throw new Error(`Invalid database path: must have .db extension`);
}
}

/**
* Apply connection-level PRAGMAs. Shared by `initialize` and `open` so the two
* paths can't drift.
Expand Down Expand Up @@ -55,6 +64,7 @@ export class DatabaseConnection {
* Initialize a new database at the given path
*/
static initialize(dbPath: string): DatabaseConnection {
validateDbPath(dbPath);
// Ensure parent directory exists
const dir = path.dirname(dbPath);
if (!fs.existsSync(dir)) {
Expand Down Expand Up @@ -86,6 +96,7 @@ export class DatabaseConnection {
* Open an existing database
*/
static open(dbPath: string): DatabaseConnection {
validateDbPath(dbPath);
if (!fs.existsSync(dbPath)) {
throw new Error(`Database not found: ${dbPath}`);
}
Expand Down
4 changes: 3 additions & 1 deletion src/mcp/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ export class Daemon {
// POSIX: tighten permissions to user-only — the socket lives under
// `.codegraph/`, which is git-ignored but may be on a shared FS.
if (process.platform !== 'win32') {
try { fs.chmodSync(this.socketPath, 0o600); } catch { /* best-effort */ }
try { fs.chmodSync(this.socketPath, 0o600); } catch (err) {
process.stderr.write(`[CodeGraph daemon] warning: failed to restrict socket permissions: ${err instanceof Error ? err.message : String(err)}\n`);
}
}
this.server = server;
resolve();
Expand Down
1 change: 1 addition & 0 deletions src/mcp/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export class MCPSession {

await this.retryInitIfNeeded();

process.stderr.write(`[CodeGraph MCP] ${new Date().toISOString()} tool_call pid=${process.pid} tool=${toolName}\n`);
const result = await this.engine.getToolHandler().execute(toolName, toolArgs);
this.transport.sendResult(request.id, result);
}
Expand Down