Skip to content
Closed
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
34 changes: 29 additions & 5 deletions src/command-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class CommandManager {
commandString = commandString.trim();

// Define command separators - these are the operators that can chain commands
const separators = [';', '&&', '||', '|', '&'];
// Include newline variants to prevent newline-based blocklist bypass (#422)
const separators = ['\r\n', '\n', '\r', ';', '&&', '||', '|', '&'];

// This will store our extracted commands
const commands: string[] = [];
Expand Down Expand Up @@ -137,7 +138,8 @@ class CommandManager {
// We found a separator - extract the command before it
if (currentCmd.trim()) {
const baseCommand = this.extractBaseCommand(currentCmd.trim());
if (baseCommand) commands.push(baseCommand);
// null means glob/invalid — push sentinel so validateCommand fails closed
commands.push(baseCommand !== null ? baseCommand : '__INVALID_COMMAND__');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Treating every null from command extraction as __INVALID_COMMAND__ causes legitimate command chains to be rejected, because extractBaseCommand returns null for non-malicious segments like env-only assignments (for example FOO=bar && echo ok). Only map to an invalid sentinel when the token is explicitly unsafe (glob/brace pattern), and skip segments that simply do not represent an executable command. [logic error]

Severity Level: Major ⚠️
- ⚠️ start_process rejects commands with leading env-only assignments.
- ⚠️ Benign multi-part shell commands fail with "Command not allowed".
Steps of Reproduction ✅
1. Start the MCP server defined in `src/server.ts`, which registers the `start_process`
tool in the tools list (see `server.ts:795-868` where the `"start_process"` tool is
declared with `StartProcessArgsSchema`).

2. From an MCP client, invoke `CallToolRequest` with `name: "start_process"` so that
`server.ts:1170-1177` routes to `case "start_process":` at `server.ts:1337-1339`, calling
`handlers.handleStartProcess(args)` in `src/handlers/terminal-handlers.ts:22-25`.

3. In `handleStartProcess`, `StartProcessArgsSchema.parse(args)` (defined in
`src/tools/schemas.ts:22-27`) produces `{ command: "FOO=bar && echo ok", timeout_ms: 5000,
... }` and passes it to `startProcess(parsed)`
(`src/tools/improved-process-tools.ts:97-105`), which then calls
`commandManager.validateCommand(parsed.data.command)` at `improved-process-tools.ts:119`.

4. Inside `validateCommand` (`src/command-manager.ts:242-276`), `extractCommands(command)`
is called. For the first segment `"FOO=bar"`: `extractCommands`
(`command-manager.ts:11-167`) hits the `"&&"` separator and calls
`extractBaseCommand(currentCmd.trim())` at `command-manager.ts:140`. `extractBaseCommand`
removes env vars via `const withoutEnvVars = commandStr.replace(/\w+=\S+\s*/g,
'').trim();` at `command-manager.ts:181`, leaving an empty string and returning `null` at
`command-manager.ts:184`. Back in `extractCommands`, the null is converted to the sentinel
by `commands.push(baseCommand !== null ? baseCommand : '__INVALID_COMMAND__');` at
`command-manager.ts:142`, so the overall `allCommands` array becomes
`['__INVALID_COMMAND__', 'echo']`. `validateCommand` then executes `if
(allCommands.includes('__INVALID_COMMAND__')) { return false; }` at
`command-manager.ts:262-265`, causing `startProcess` to return `{ isError: true, content:
"Error: Command not allowed: FOO=bar && echo ok" }` (`improved-process-tools.ts:119-125`),
even though the only executable command is the benign `echo`.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/command-manager.ts
**Line:** 142:142
**Comment:**
	*Logic Error: Treating every `null` from command extraction as `__INVALID_COMMAND__` causes legitimate command chains to be rejected, because `extractBaseCommand` returns `null` for non-malicious segments like env-only assignments (for example `FOO=bar && echo ok`). Only map to an invalid sentinel when the token is explicitly unsafe (glob/brace pattern), and skip segments that simply do not represent an executable command.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}
Comment on lines 139 to 143
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail-closed sentinel is pushed for all extractBaseCommand nulls, not only glob rejection — regresses legitimate multi-segment inputs.

extractBaseCommand returns null in several non-malicious cases beyond glob detection:

  • Segment is only env-var assignments, e.g. FOO=bar → line 181 strips to empty → line 184 returns null.
  • Segment is only dollar-variable / ( tokens, e.g. $MYVAR → loop at lines 191–206 never sets firstToken → line 210 returns null.
  • $() with empty inner (line 220).
  • Any thrown exception (line 238).

Previously these were silently dropped by the if (baseCommand) guard. With the new sentinel push, benign inputs such as FOO=bar; ls or $MYVAR && ls are now blocked outright via the check at line 264, which was not an intended goal of #421/#422 and will surface as Error: Command not allowed: ... in the caller (src/tools/improved-process-tools.ts lines 119–125).

Disambiguate: only emit the sentinel when the segment actually contains glob/brace characters, and keep the silent-drop behavior for the other null causes.

🛡️ Proposed fix — gate sentinel on glob presence in the segment
                 if (commandString.startsWith(separator, i)) {
                     // We found a separator - extract the command before it
                     if (currentCmd.trim()) {
-                        const baseCommand = this.extractBaseCommand(currentCmd.trim());
-                        // null means glob/invalid — push sentinel so validateCommand fails closed
-                        commands.push(baseCommand !== null ? baseCommand : '__INVALID_COMMAND__');
+                        const trimmed = currentCmd.trim();
+                        const baseCommand = this.extractBaseCommand(trimmed);
+                        if (baseCommand !== null) {
+                            commands.push(baseCommand);
+                        } else if (/[*?\[\]{}]/.test(trimmed)) {
+                            // Segment contained a glob/brace token we could not
+                            // safely resolve — fail closed.
+                            commands.push('__INVALID_COMMAND__');
+                        }
+                        // else: env-vars-only / variable-only / empty — drop silently.
                     }

Apply the same treatment to the final-segment block at lines 159–163.

Also applies to: 159-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/command-manager.ts` around lines 139 - 143, When
extractBaseCommand(currentCmd.trim()) returns null, only push the fail-closed
sentinel '__INVALID_COMMAND__' if the original segment actually contains
glob/brace characters; otherwise keep the previous silent-drop behavior for
benign nulls (env-var only segments, bare $VARIABLE/paren tokens, empty $() or
thrown exceptions). Concretely: compute a quick check for glob/brace characters
in the segment (e.g. presence of '*', '?', '[', ']', '{', '}') and change the
block that currently does commands.push(baseCommand !== null ? baseCommand :
'__INVALID_COMMAND__') to push the sentinel only when baseCommand === null &&
segmentContainsGlob; do the same modification in the other place that handles
the final segment where baseCommand is treated similarly.


// Move past the separator
Expand All @@ -156,7 +158,8 @@ class CommandManager {
// Don't forget to add the last command
if (currentCmd.trim()) {
const baseCommand = this.extractBaseCommand(currentCmd.trim());
if (baseCommand) commands.push(baseCommand);
// null means glob/invalid — push sentinel so validateCommand fails closed
commands.push(baseCommand !== null ? baseCommand : '__INVALID_COMMAND__');
}

// Remove duplicates and return
Expand Down Expand Up @@ -217,6 +220,16 @@ class CommandManager {
return null;
}

// Strip all quote characters so that concatenated fragments like
// "r"m, r"m", 'r''m' are normalized to the actual command name (#421)
firstToken = firstToken.replace(/["']/g, '');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Stripping only quote characters is not enough to normalize shell command tokens: backslash escapes are still left intact. A blocked command can still be bypassed with input like r\m -rf /, because validation compares r\m against the blocklist while the shell resolves it to rm at execution time. Normalize shell escapes (or reject escaped command-name tokens) before blocklist comparison. [security]

Severity Level: Critical 🚨
- ❌ Blocked commands executable via backslash-escaped names.
- ❌ start_process tool can't reliably enforce command blocklist.
- ⚠️ Potential privilege escalation via sudo, su commands.
Steps of Reproduction ✅
1. Start the MCP server defined in `src/server.ts` so the `start_process` tool is
registered (terminal tools section around lines 797–816 describes `start_process` as the
primary terminal tool).

2. Note that the default config in `src/config-manager.ts:109-155` includes `"sudo"` in
`blockedCommands`, so `sudo` should always be rejected by validation.

3. From a MCP client, call the `start_process` tool (handled by `handleStartProcess` in
`src/handlers/terminal-handlers.ts:13-16`, which calls `startProcess` in
`src/tools/improved-process-tools.ts:97-127`) with a command like `s\udo id`.

4. Inside `startProcess`, the call `commandManager.validateCommand(parsed.data.command)`
at `src/tools/improved-process-tools.ts:119-125` invokes `validateCommand` in
`src/command-manager.ts:242-276`, which calls `extractCommands(command)` at line 248;
`extractCommands` builds the token `s\udo` into `currentCmd` (lines 29-45) and passes it
to `extractBaseCommand` at `src/command-manager.ts:177-235`, where `firstToken` remains
`s\udo` except for quote-stripping via `firstToken = firstToken.replace(/["']/g, '');` at
line 225; since there is no backslash normalization, `validateCommand` compares `"s\\udo"`
against `blockedCommands` (lines 268-273), does not match `"sudo"`, returns `true`, and
`startProcess` executes the original `s\udo id` via `terminalManager.executeCommand` at
`src/tools/improved-process-tools.ts:170-175`, allowing the blocked `sudo` command to run
because the shell interprets `s\udo` as `sudo`.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/command-manager.ts
**Line:** 225:225
**Comment:**
	*Security: Stripping only quote characters is not enough to normalize shell command tokens: backslash escapes are still left intact. A blocked command can still be bypassed with input like `r\m -rf /`, because validation compares `r\m` against the blocklist while the shell resolves it to `rm` at execution time. Normalize shell escapes (or reject escaped command-name tokens) before blocklist comparison.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +223 to +225
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Quote normalization is incomplete because it only removes quote characters and does not normalize shell escape characters. On POSIX shells, an attacker can still obfuscate a blocked command with backslash escaping (for example r\m), which the shell resolves to rm at execution time while the validator compares the unnormalized token and lets it pass. Normalize shell escapes before blocklist comparison (or parse using a shell-aware tokenizer). [security]

Severity Level: Critical 🚨
- ❌ Blocked commands bypassed via backslash-escaped names.
- ❌ Malicious shell commands can run on host system.
- ⚠️ BlockedCommands security guarantees undermined for escaped inputs.
Steps of Reproduction ✅
1. Invoke the MCP `start_process` tool via the server entrypoint `handleStartProcess` in
`src/handlers/terminal-handlers.ts:5-7`, passing args matching `StartProcessArgsSchema`
(`src/tools/schemas.ts:11-16`) with `command: "r\\m -rf /tmp/testdir"` and `timeout_ms`
set to any valid number.

2. `handleStartProcess` forwards the parsed args to `startProcess` in
`src/tools/improved-process-tools.ts:18-27`, which records telemetry and then calls
`commandManager.validateCommand(parsed.data.command)` at
`src/tools/improved-process-tools.ts:40`.

3. Inside `validateCommand` (`src/command-manager.ts:242-260`), `extractCommands`
(`src/command-manager.ts:11-21`) processes the string `r\\m -rf /tmp/testdir`. It never
hits a separator, so `currentCmd` becomes `r\\m -rf /tmp/testdir`. At the end of the loop
(`src/command-manager.ts:158-163`), `extractBaseCommand(currentCmd.trim())` is called,
which returns the first token `r\\m` (after env-var stripping and quote stripping at
`src/command-manager.ts:180-225`) and does not treat the backslash as special.

4. `extractCommands` returns `["r\\m"]`, so `validateCommand` sees `allCommands =
["r\\m"]` (`src/command-manager.ts:248-260`), does not trigger the `__INVALID_COMMAND__`
sentinel check (`src/command-manager.ts:262-265`), and compares `"r\\m"` against
`blockedCommands` from config (e.g. a user-configured `"rm"` entry in `blockedCommands`
loaded by `configManager.getConfig()` at `src/config-manager.ts:191-193`). Since `"r\\m"
!== "rm"`, validation passes and `terminalManager.executeCommand`
(`src/terminal-manager.ts:10-69`) runs the command via `/bin/sh -c "r\\m -rf
/tmp/testdir"` or similar; in POSIX shells (see `getShellSpawnArgs` at
`src/terminal-manager.ts:13-21,43-47`), `r\\m` is interpreted as `rm`, so the blocked `rm`
executes despite being on the blocklist.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/command-manager.ts
**Line:** 223:225
**Comment:**
	*Security: Quote normalization is incomplete because it only removes quote characters and does not normalize shell escape characters. On POSIX shells, an attacker can still obfuscate a blocked command with backslash escaping (for example `r\m`), which the shell resolves to `rm` at execution time while the validator compares the unnormalized token and lets it pass. Normalize shell escapes before blocklist comparison (or parse using a shell-aware tokenizer).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


// Reject commands containing glob/wildcard or brace-expansion characters
// to prevent blocklist bypass, e.g. /usr/bin/su*o or /usr/bin/s{udo,x} (#421)
if (/[*?\[\]{}]/.test(firstToken)) {
return null;
}

// strip path prefix so /usr/bin/sudo gets caught as "sudo"
const baseName = path.basename(firstToken);
return baseName.toLowerCase();
Expand All @@ -231,16 +244,27 @@ class CommandManager {
// Get blocked commands from config
const config = await configManager.getConfig();
const blockedCommands = config.blockedCommands || [];

// Extract all commands from the command string
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Architect Review — CRITICAL

Blocked-command validation can still be bypassed via shell variable expansion of the executable name. For a command like export X=sudo; $X whoami, extractCommands/extractBaseCommand skip the $X token and instead record export and whoami, so sudo is never compared against blockedCommands even though the shell executes sudo.

Suggestion: In extractBaseCommand/extractCommands, treat dollar-prefixed tokens used in command position as unsafe rather than skipping them: either reject commands whose effective executable is variable-based (e.g. $VAR, ${VAR}) or adopt a parsing/policy approach that only allows literal command names and fails closed when the invoked binary cannot be determined statically.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** src/command-manager.ts
**Line:** 248:271
**Comment:**
	*CRITICAL: Blocked-command validation can still be bypassed via shell variable expansion of the executable name. For a command like `export X=sudo; $X whoami`, extractCommands/extractBaseCommand skip the `$X` token and instead record `export` and `whoami`, so `sudo` is never compared against `blockedCommands` even though the shell executes `sudo`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

const allCommands = this.extractCommands(command);

// If there are no commands extracted, fall back to base command
if (allCommands.length === 0) {
const baseCommand = this.getBaseCommand(command);
// Reject if the base command contains glob/wildcard/brace characters
// to prevent bypass via e.g. /usr/bin/su*o or s{udo,x} (#421)
if (/[*?\[\]{}]/.test(baseCommand)) {
return false;
}
return !blockedCommands.includes(baseCommand);
}


// Fail closed: if any segment failed extraction (e.g. contained a glob),
// reject the entire command rather than silently ignoring the bad segment.
if (allCommands.includes('__INVALID_COMMAND__')) {
return false;
Comment on lines +264 to +265
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The new fail-closed sentinel check rejects commands whenever extractBaseCommand() returns null, but null is also used for non-malicious segments (for example, standalone environment assignments like FOO=1 && echo ok). This causes valid command chains to be denied. Differentiate "invalid/dangerous token" from "no executable command in this segment" instead of treating all null results as fatal. [logic error]

Severity Level: Major ⚠️
- ❌ start_process rejects chains with env-only command segments.
- ⚠️ Users can't run some valid shell workflows.
- ⚠️ Overzealous validation reduces terminal tool usability.
Steps of Reproduction ✅
1. Start the MCP server from `src/server.ts`, which registers the `start_process` tool
(terminal tools block around `src/server.ts:797-816`) and uses `handleStartProcess` in
`src/handlers/terminal-handlers.ts:13-16` to route requests.

2. From a MCP client, invoke the `start_process` tool with a command string like `FOO=1 &&
echo ok`, so `handleStartProcess` calls `startProcess(parsed)` in
`src/tools/improved-process-tools.ts:97-127`.

3. Inside `startProcess`, the call `const isAllowed = await
commandManager.validateCommand(parsed.data.command);` at
`src/tools/improved-process-tools.ts:119-125` routes to `validateCommand` in
`src/command-manager.ts:242-276`, which first builds `allCommands` by calling
`this.extractCommands(command)` at line 248; `extractCommands` splits on `&&` using the
`separators` array (lines 16-18, 134-151), producing segments `"FOO=1"` and `"echo ok"`
that are each passed to `extractBaseCommand`.

4. For the `"FOO=1"` segment, `extractBaseCommand` in `src/command-manager.ts:177-235`
removes env assignments via `const withoutEnvVars = commandStr.replace(/\w+=\S+\s*/g,
'').trim();` at lines 181-184, leaving an empty string and returning `null` (lines
183-184); `extractCommands` then pushes the sentinel `"__INVALID_COMMAND__"` for this
segment at line 162, so `allCommands` becomes `["__INVALID_COMMAND__","echo"]`, and back
in `validateCommand` the fail-closed check `if
(allCommands.includes('__INVALID_COMMAND__')) { return false; }` at lines 262-265 rejects
the entire command even though the second segment `"echo ok"` yields an allowed base
command `"echo"`, causing `startProcess` in `src/tools/improved-process-tools.ts:119-125`
to return `Error: Command not allowed: FOO=1 && echo ok` and blocking a valid command
chain that just uses an env-only segment.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/command-manager.ts
**Line:** 264:265
**Comment:**
	*Logic Error: The new fail-closed sentinel check rejects commands whenever `extractBaseCommand()` returns `null`, but `null` is also used for non-malicious segments (for example, standalone environment assignments like `FOO=1 && echo ok`). This causes valid command chains to be denied. Differentiate "invalid/dangerous token" from "no executable command in this segment" instead of treating all `null` results as fatal.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +262 to +265
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This fail-closed sentinel logic treats every null extraction result as malicious, but extractBaseCommand() also returns null for benign inputs such as environment-variable-only commands (for example FOO=bar). That causes valid commands to be rejected unexpectedly. Return a typed extraction result that distinguishes "no executable token" from "invalid/malicious token", and only hard-fail for the malicious case. [logic error]

Severity Level: Major ⚠️
- ⚠️ Benign env-prefixed commands rejected as disallowed.
- ⚠️ Multi-segment commands with env setup cannot run.
- ⚠️ start_process usability reduced for shell-style workflows.
Steps of Reproduction ✅
1. Call the MCP `start_process` tool through `handleStartProcess` at
`src/handlers/terminal-handlers.ts:5-7` with args matching `StartProcessArgsSchema`
(`src/tools/schemas.ts:11-16`), using a command that starts with an environment-only
segment followed by a benign command, e.g. `command: "FOO=bar; ls"` and any valid
`timeout_ms`.

2. `handleStartProcess` passes the parsed args to `startProcess`
(`src/tools/improved-process-tools.ts:18-27`), which then calls
`commandManager.validateCommand(parsed.data.command)` at
`src/tools/improved-process-tools.ts:40` before executing anything.

3. `validateCommand` (`src/command-manager.ts:242-260`) calls `extractCommands(command)`
(`src/command-manager.ts:11-21`). Inside `extractCommands`, the loop accumulates
`currentCmd = "FOO=bar"` until it hits the `;` separator (separators defined at
`src/command-manager.ts:16-18`). At `src/command-manager.ts:138-143`, it calls
`extractBaseCommand(currentCmd.trim())` with `"FOO=bar"`.

4. In `extractBaseCommand` (`src/command-manager.ts:177-185`), the env-var stripping regex
`commandStr.replace(/\w+=\S+\s*/g, '')` removes `"FOO=bar"`, leaving an empty string; the
function returns `null` at `src/command-manager.ts:183-184`. Back in `extractCommands`,
this `null` result is treated as an invalid/malicious segment and replaced with the
sentinel `'__INVALID_COMMAND__'` (`src/command-manager.ts:140-142`). The second segment
(`" ls"`) yields a valid base command `"ls"`, so `allCommands` becomes
`['__INVALID_COMMAND__', 'ls']`. In `validateCommand`, the presence of
`'__INVALID_COMMAND__'` triggers the fail-closed path at `src/command-manager.ts:262-265`,
returning `false` and causing `startProcess` (`src/tools/improved-process-tools.ts:40-45`)
to reject the entire user command `"FOO=bar; ls"` as disallowed, even though it contains
only benign operations.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/command-manager.ts
**Line:** 262:265
**Comment:**
	*Logic Error: This fail-closed sentinel logic treats every `null` extraction result as malicious, but `extractBaseCommand()` also returns `null` for benign inputs such as environment-variable-only commands (for example `FOO=bar`). That causes valid commands to be rejected unexpectedly. Return a typed extraction result that distinguishes "no executable token" from "invalid/malicious token", and only hard-fail for the malicious case.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}

// Check if any of the extracted commands are in the blocked list
for (const cmd of allCommands) {
if (blockedCommands.includes(cmd)) {
Expand Down