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: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2026-07-17 - [CWD Binary Hijacking Mitigation for Electron Shell Process Spawning]
**Vulnerability:** Spawning a shell with relative binary names (e.g. `powershell.exe` or `cmd.exe`) on Windows or POSIX can lead to CWD Binary Hijacking if a workspace has a malicious executable with the same name.
**Learning:** Simply whitelisting simple shell names is not sufficient if the underlying process-spawning call resolves relative paths relative to the current working directory (`workspaceRoot`). Electron main processes must always resolve shell binaries to secure, absolute paths (e.g. in `System32` or `/bin`) before execution.
**Prevention:** Always map whitelisted shell names to absolute, safe system paths (e.g. `path.join(process.env.SystemRoot || 'C:\\Windows', 'System32\\cmd.exe')`) and spawn using those absolute paths only.

## 2026-07-17 - [IPC Command and Process Injection Prevention in Electron]
**Vulnerability:** Unsanitized parameters exposed via IPC handlers in Electron allowed the renderer process to trigger arbitrary process execution (`restart-shell` with arbitrary executable path) and git command/argument injection (`git-cmd` with custom arguments like `--ext-diff`).
**Learning:** Even if context isolation is enabled and the renderer does not expose the raw `child_process` module, exposed high-level IPC handlers (like Git commands or custom shell selection handlers) can still be abused as a bridge for command/argument injection if they do not strictly sanitize or validate their inputs on the main process side.
Expand Down
39 changes: 37 additions & 2 deletions electron.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ app.on('activate', function () {
function getShellPath() {
const platform = os.platform();
if (platform === 'win32') {
return 'powershell.exe';
const systemRoot = process.env.SystemRoot || 'C:\\Windows';
return path.join(systemRoot, 'System32\\WindowsPowerShell\\v1.0\\powershell.exe');
} else if (platform === 'darwin') {
return process.env.SHELL || '/bin/zsh';
} else {
Expand Down Expand Up @@ -678,17 +679,51 @@ function isSafeShell(shellPath) {
return ALLOWED_SHELLS.includes(normalized);
}

function getAbsoluteSafeShellPath(shellPath) {
if (typeof shellPath !== 'string') return getShellPath();
const normalized = shellPath.trim().replace(/\\/g, '/').toLowerCase();
const platform = os.platform();
const systemRoot = process.env.SystemRoot || 'C:\\Windows';

if (platform === 'win32') {
if (normalized.includes('powershell')) {
return path.join(systemRoot, 'System32\\WindowsPowerShell\\v1.0\\powershell.exe');
}
if (normalized.includes('cmd')) {
return path.join(systemRoot, 'System32\\cmd.exe');
}
if (normalized.includes('pwsh')) {
const p7 = path.join(process.env.ProgramFiles || 'C:\\Program Files', 'PowerShell\\7\\pwsh.exe');
if (fs.existsSync(p7)) return p7;
return path.join(systemRoot, 'System32\\WindowsPowerShell\\v1.0\\powershell.exe');
}
} else {
if (normalized.endsWith('bash')) {
return fs.existsSync('/bin/bash') ? '/bin/bash' : '/usr/bin/bash';
}
if (normalized.endsWith('zsh')) {
return fs.existsSync('/bin/zsh') ? '/bin/zsh' : '/usr/bin/zsh';
}
if (normalized.endsWith('sh')) {
return fs.existsSync('/bin/sh') ? '/bin/sh' : '/usr/bin/sh';
}
}
return getShellPath();
}

ipcMain.handle('restart-shell', (event, shellPath) => {
if (shellPath) {
// Secure validation to prevent process execution of arbitrary binaries (Command/Process Injection vulnerability)
if (!isSafeShell(shellPath)) {
console.warn(`Blocked unsafe shell override attempt: "${shellPath}"`);
return false;
}
// Convert to a secure absolute path to prevent CWD binary-hijacking or path-traversal bypasses
const secureShellPath = getAbsoluteSafeShellPath(shellPath);
// Override the shell for this restart
const savedShell = workspaceRoot;
workspaceRoot = savedShell;
startNewShellWith(shellPath);
startNewShellWith(secureShellPath);
} else {
startNewShell();
}
Expand Down
Loading