diff --git a/CHANGELOG.md b/CHANGELOG.md index c6bb692..bb393ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,17 +14,12 @@ All notable changes to Claude Code Studio will be documented in this file. - Composer UX improvements — expand toggle, drag handle, line/char count (84e2296) - Notes pad replaces Inbox tab in right pane (7354643) - Collapsible left sidebar with Ctrl+B shortcut (21f7363) -- Plugin system with MCP-based architecture (e14a91c) - Drag-and-drop between panes via toolbar handle (757984a) ### Fixed - Preserve claudeSessionId across app restarts for session recovery (f4a3a57) - Japanese input and multiline text with bracketed paste (21ed2f1) -- SSH+tmux session reconnection with claude lifecycle management (8575494) - Remove bottom gap in ActivityMap/ConfigMap, responsive stats cards (a886cf2) -- Security hardening, lint fixes, remove bundled Aurelius plugin (3302f5a) -- TitleBar platform-aware padding, Linux repaint cleanup (751917f) -- Linux GPU stability — SIGSEGV fix, titlebar overlay crash fix (5e01a4c) ### Documentation - Screenshots added to README (be103b8) diff --git a/src/main/plugins/__tests__/pluginEnvFilter.test.ts b/src/main/plugins/__tests__/pluginEnvFilter.test.ts index f65e86a..2ee6753 100644 --- a/src/main/plugins/__tests__/pluginEnvFilter.test.ts +++ b/src/main/plugins/__tests__/pluginEnvFilter.test.ts @@ -142,4 +142,18 @@ describe('filterEnvForPlugin', () => { const result = filterEnvForPlugin({}) expect(Object.keys(result)).toHaveLength(0) }) + + it('blocks API_KEY variants without $ anchor (e.g. SOME_API_KEY_FILE)', () => { + const env: NodeJS.ProcessEnv = { + SOME_API_KEY_FILE: '/path/to/key', + MY_API_KEY_PATH: '/another/path', + API_KEY_ROTATION_DATE: '2026-01-01', + PATH: '/usr/bin' + } + const result = filterEnvForPlugin(env) + expect(result.SOME_API_KEY_FILE).toBeUndefined() + expect(result.MY_API_KEY_PATH).toBeUndefined() + expect(result.API_KEY_ROTATION_DATE).toBeUndefined() + expect(result.PATH).toBe('/usr/bin') + }) }) diff --git a/src/main/plugins/pluginEnvFilter.ts b/src/main/plugins/pluginEnvFilter.ts index 08e3ccb..5e64506 100644 --- a/src/main/plugins/pluginEnvFilter.ts +++ b/src/main/plugins/pluginEnvFilter.ts @@ -66,7 +66,7 @@ const SENSITIVE_PATTERNS = [ /password/i, /private.?key/i, /auth.?token/i, - /api.?key$/i, + /api.?key/i, /credentials/i, /access.?key/i ] diff --git a/src/main/plugins/pluginManager.ts b/src/main/plugins/pluginManager.ts index 16cb665..12695b3 100644 --- a/src/main/plugins/pluginManager.ts +++ b/src/main/plugins/pluginManager.ts @@ -42,6 +42,11 @@ export class PluginManager { paths.push(join(home, 'AppData', 'Local', 'Programs')) paths.push(join(home, 'AppData', 'Local', 'claude-code-studio', 'plugins')) } + if (process.platform === 'darwin') { + paths.push('/usr/local/bin') + paths.push('/opt/homebrew/bin') + paths.push(join(home, 'Library', 'Application Support', 'claude-code-studio', 'plugins')) + } // Normalize separators for consistent startsWith comparison return paths.map((p) => p.replace(/\\/g, '/')) } @@ -137,6 +142,14 @@ export class PluginManager { const localBin = join(homedir(), '.local', 'bin', cmd) if (existsSync(localBin)) return true + // Check macOS-specific paths (Homebrew) + if (process.platform === 'darwin') { + const homebrewBin = join('/opt/homebrew/bin', cmd) + if (existsSync(homebrewBin)) return true + const usrLocalBin = join('/usr/local/bin', cmd) + if (existsSync(usrLocalBin)) return true + } + // Check PATH (cross-platform) try { const whichCmd = process.platform === 'win32' ? 'where' : 'which' @@ -162,6 +175,14 @@ export class PluginManager { const localBin = join(homedir(), '.local', 'bin', cmd) if (existsSync(localBin)) return localBin + // Check macOS-specific paths (Homebrew) + if (process.platform === 'darwin') { + const homebrewBin = join('/opt/homebrew/bin', cmd) + if (existsSync(homebrewBin)) return homebrewBin + const usrLocalBin = join('/usr/local/bin', cmd) + if (existsSync(usrLocalBin)) return usrLocalBin + } + // Fall back to PATH return cmd } @@ -177,7 +198,8 @@ export class PluginManager { const args = entry.manifest.mcp.args const filteredEnv = filterEnvForPlugin(process.env) - const pluginEnv = extraEnvVars ? { ...filteredEnv, ...extraEnvVars } : filteredEnv + const filteredExtra = extraEnvVars ? filterEnvForPlugin(extraEnvVars as NodeJS.ProcessEnv) : undefined + const pluginEnv = filteredExtra ? { ...filteredEnv, ...filteredExtra } : filteredEnv const proc = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], diff --git a/src/main/utils.ts b/src/main/utils.ts index fbe3b0c..850239b 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -5,6 +5,9 @@ export function stripAnsiCodes(str: string): string { .replace(/\x1B\][^\x07]*\x07/g, '') // OSC sequences .replace(/\x1B[()][0-9A-B]/g, '') // Character set selection .replace(/\x1B[\x20-\x2F]*[\x30-\x7E]/g, '') // Other ESC sequences - .replace(/O\?[\d;]*c/g, '') // Orphaned DA responses (ESC lost at chunk boundary) + // Orphaned Device Attributes (DA) response: terminals reply with ESC[?1;2c etc. + // When output is chunked, the leading ESC may land in a previous chunk, leaving + // only the tail "O?c" in the current one. Strip it to avoid UI noise. + .replace(/O\?[\d;]*c/g, '') } /* eslint-enable no-control-regex */ diff --git a/src/shared/types.ts b/src/shared/types.ts index 116bfd0..4ca4ff0 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -593,6 +593,7 @@ export interface PluginManifest { toolbarButtons: { id: string; tool: string; icon: string; prompt: string }[] contextTab?: { id: string; label: string; icon: string; component: string } } + permissions?: PluginPermissions install?: { check: string steps: string[]