Skip to content
Merged
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: 0 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/main/plugins/__tests__/pluginEnvFilter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
2 changes: 1 addition & 1 deletion src/main/plugins/pluginEnvFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
]
Expand Down
24 changes: 23 additions & 1 deletion src/main/plugins/pluginManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, '/'))
}
Expand Down Expand Up @@ -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'
Expand All @@ -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
}
Expand All @@ -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'],
Expand Down
5 changes: 4 additions & 1 deletion src/main/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?<digits>c" in the current one. Strip it to avoid UI noise.
.replace(/O\?[\d;]*c/g, '')
}
/* eslint-enable no-control-regex */
1 change: 1 addition & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
Loading