Root cause
Injection & unsafe execution introduced
Phase 4: Auto-Learning System - Git Commit Knowledge Capture (#13)
AI-assisted change: Claude
How AI contributed: Direct introductionPublished Aug 8, 2026 in
andreahaku/llm_memory_mcp
Command injection (CWE-74/77) in the MCP server's auto.capture tool. The MCP tool 'auto.capture' (src/index.ts, case 'auto.capture': const commitHash = (args.hash as string) || 'HEAD') forwards the user-supplied hash argument to AutoLearnManager.captureCommit (src/autolearn/AutoLearnManager.ts:253) -> AutoLearner.captureCommit (src/autolearn/AutoLearner.ts:136) -> GitHooksManager.getCommitInfo() (src/autolearn/GitHooksManager.ts). getCommitInfo builds shell commands with the hash interpolated unquoted into a template string passed to execSync: git show -s --format="..." ${commitHash}, git show --stat --format= ${commitHash}, and git show --pretty=format: ${commitHash}. A hash value containing shell metacharacters (e.g. 'HEAD; <cmd>') allows a malicious MCP client to execute arbitrary commands on the host running the MCP server. GHSA-rrf2-j3h9-99wg (alias CVE-2026-19282) rates it CWE-74, CVSS 5.3 (NVD MEDIUM / GHSA LOW), published 2026-08-08, affected version <= f11dc8bcff3ff8cf943a2945f99ff3b0bdc8a6d0. The advisory was filed via issue #21; no fix has been released and the vulnerable code is still present at HEAD. Local attack surface: the MCP server is intended to run on a user's workstation, so the injection is exploited by a malicious/compromised MCP client or by any prompt-induced command in the git-hash parameter.
Root cause
Phase 4: Auto-Learning System - Git Commit Knowledge Capture (#13)
AI-assisted change: Claude
Fix status
No minimum fix commit has been established for this finding.
Fix status unresolved
Case facts
@@ -15,6 +15,7 @@ import { import { MemoryManager } from './MemoryManager.js'; import type { MemoryScope, MemoryQuery, MemoryType } from './types/Memory.js';+import { AutoLearnManager } from './autolearn/AutoLearnManager.js'; // Import MigrationManager conditionally to avoid FFmpeg dependency issues let MigrationManagerClass: any = null;@@ -39,6 +40,7 @@ class LLMKnowledgeBaseServer { private server: Server; private memory: MemoryManager; private migration: any;+ private autoLearn: AutoLearnManager | null = null; private isShuttingDown = false; constructor() {@@ -69,6 +71,24 @@ class LLMKnowledgeBaseServer { log('🔗 MCP client connecting...'); const result = await originalConnect(transport); log('✅ MCP client connected successfully');++ // Auto-initialize auto-learning if project detected+ const projectInfo = this.memory.getProjectInfo();+ if (projectInfo && !this.autoLearn) {+ try {+ log('🎓 Auto-initializing learning system...');+ this.autoLearn = new AutoLearnManager(this.memory);+ const initResult = await this.autoLearn.initialize(projectInfo.root, true);+ if (initResult.success) {+ log(`✅ Auto-learning initialized: git hooks=${initResult.gitHooksInstalled}, agents=${initResult.agentsInstalled}`);+ } else {+ log(`⚠️ Auto-learning initialization incomplete: ${initResult.errors.join(', ')}`);+ }+ } catch (error) {+ log(`⚠️ Auto-learning initialization failed: ${error instanceof Error ? error.message : String(error)}`);+ }+ }+ return result; }; @@ -718,6 +738,55 @@ class LLMKnowledgeBaseServer { additionalProperties: false, }, },+ {+ name: 'autolearn.status',+ description: 'Get auto-learning system status and hook installation state',+ inputSchema: {+ type: 'object',+ properties: {},+ additionalProperties: false,+ },+ },+ {+ name: 'autolearn.init',+ description: 'Initialize auto-learning system for current project',+ inputSchema: {+ type: 'object',+ properties: {+ autoInstall: { type: 'boolean', description: 'Auto-install hooks if missing (default: true)' },+ },+ additionalProperties: false,+ },+ },+ {+ name: 'autolearn.processQueue',+ description: 'Process queued auto-learning events from git commits',+ inputSchema: {+ type: 'object',+ properties: {},+ additionalProperties: false,+ },+ },+ {+ name: 'autolearn.captureCommit',+ description: 'Manually capture a git commit for auto-learning',+ inputSchema: {+ type: 'object',+ properties: {+ commitHash: { type: 'string', description: 'Commit hash (default: HEAD)' },+ },+ additionalProperties: false,+ },+ },+ {+ name: 'autolearn.installGlobally',+ description: 'Install auto-learning hooks and agents globally',+ inputSchema: {+ type: 'object',@@ -0,0 +1,448 @@+/**+ * Git Hooks Manager+ *+ * Handles installation and management of git hooks for auto-learning.+ */++import { execSync } from 'child_process';+import { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync } from 'fs';+import * as path from 'path';+import type { GitCommitInfo, HookStatus, AutoLearnConfig } from '../types/AutoLearning.js';++function log(message: string, ...args: any[]) {+ console.error(`[GitHooksManager] ${new Date().toISOString()} ${message}`, ...args);+}++export class GitHooksManager {+ private projectRoot: string;+ private hooksDir: string;+ private config: AutoLearnConfig['gitHooks'];++ constructor(projectRoot: string, config: AutoLearnConfig['gitHooks']) {+ this.projectRoot = projectRoot;+ this.hooksDir = path.join(projectRoot, '.git', 'hooks');+ this.config = config;+ }++ /**+ * Install all auto-learning git hooks+ */+ async installHooks(): Promise<HookStatus[]> {+ const results: HookStatus[] = [];++ // Ensure .git/hooks directory exists+ if (!existsSync(this.hooksDir)) {+ try {+ mkdirSync(this.hooksDir, { recursive: true });+ } catch (error) {+ log('Failed to create hooks directory:', error);+ return [];+ }+ }++ // Install commit-msg hook (parse #kb tags)+ results.push(await this.installCommitMsgHook());++ // Install post-commit hook (capture diffs)+ if (this.config.captureDiffs) {+ results.push(await this.installPostCommitHook());+ }++ return results;+ }++ /**+ * Uninstall auto-learning git hooks+ */+ async uninstallHooks(): Promise<void> {+ const hooks = ['commit-msg', 'post-commit'];++ for (const hookName of hooks) {+ const hookPath = path.join(this.hooksDir, hookName);++ if (existsSync(hookPath)) {+ try {+ const content = readFileSync(hookPath, 'utf8');++ // Only remove if it's our hook (check for marker)+ if (content.includes('# LLM-MEMORY-MCP-AUTOLEARN')) {+ const fs = await import('fs/promises');+ await fs.unlink(hookPath);+ log(`Uninstalled ${hookName} hook`);+ }+ } catch (error) {+ log(`Failed to uninstall ${hookName} hook:`, error);+ }+ }+ }+ }++ /**+ * Check status of installed hooks+ */+ async checkHooksStatus(): Promise<HookStatus[]> {+ const hooks = ['commit-msg', 'post-commit'];+ const results: HookStatus[] = [];++ for (const hookName of hooks) {+ const hookPath = path.join(this.hooksDir, hookName);+ const installed = existsSync(hookPath);@@ -0,0 +1,540 @@+/**+ * Auto-Learning System+ *+ * Captures developer knowledge from IDE events and git operations,+ * materializing them into searchable memories.+ */++import { MemoryManager } from '../MemoryManager.js';+import { GitHooksManager } from './GitHooksManager.js';+import type {+ AutoLearnEvent,+ AutoLearnContext,+ AutoLearnConfig,+ AutoLearnResult,+ DetectedPattern,+ GitCommitInfo+} from '../types/AutoLearning.js';+import type { MemoryItem, MemoryType } from '../types/Memory.js';+import { ulid } from '../util/ulid.js';+import { redactSecrets } from '../utils/secretFilter.js';+import * as path from 'path';++function log(message: string, ...args: any[]) {+ console.error(`[AutoLearner] ${new Date().toISOString()} ${message}`, ...args);+}++export class AutoLearner {+ private memory: MemoryManager;+ private config: AutoLearnConfig;+ private gitHooks?: GitHooksManager;+ private projectRoot?: string;+ private patternCache: Map<string, DetectedPattern> = new Map();++ constructor(memory: MemoryManager, config: AutoLearnConfig) {+ this.memory = memory;+ this.config = config;+ }++ /**+ * Initialize git hooks (if enabled)+ */+ async initialize(projectRoot: string): Promise<void> {+ this.projectRoot = projectRoot;++ if (this.config.gitHooks.enabled) {+ this.gitHooks = new GitHooksManager(projectRoot, this.config.gitHooks);++ try {+ const statuses = await this.gitHooks.installHooks();+ const installed = statuses.filter(s => s.installed).length;+ log(`Installed ${installed}/${statuses.length} git hooks for auto-learning`);+ } catch (error) {+ log('Failed to install git hooks:', error);+ }+ }+ }++ /**+ * Capture an auto-learning event+ */+ async captureEvent(event: AutoLearnEvent): Promise<AutoLearnResult> {+ if (!this.config.enabled) {+ return { success: false, skipped: 'Auto-learning is disabled' };+ }++ // Check if this event type should be captured+ if (!this.config.captureTypes.includes(event.type)) {+ return { success: false, skipped: `Event type '${event.type}' not enabled for capture` };+ }++ // Apply filters+ const filterResult = this.applyFilters(event.context);+ if (!filterResult.passed) {+ return { success: false, skipped: filterResult.reason };+ }++ // Materialize the event into a memory+ try {+ const memory = await this.materializeMemory(event);+ const memoryId = await this.memory.upsert(memory);++ log(`Captured ${event.type} event as ${memory.type} memory: ${memoryId}`);++ // Update pattern detection if enabled+ if (this.config.insights.enabled && event.type === 'pattern') {+ await this.updatePatternDetection(event.context);+ }++ return {