fix: only strip valid env assignments in command parsing (#229)
Browse files## What changed
This updates the command parsing helpers to recognize leading
environment assignments only when they match shell-style variable syntax
like `FOO=bar`. The previous implementation treated any leading token
containing `=` as an env assignment, which could misclassify real
commands or file paths that happen to include `=`. Both
`_strip_env_assignments()` and `extract_command_prefix()` now share the
same validation helper so their behavior stays consistent.
## Why it matters
These helpers are used by fast-path request optimizations, so incorrect
prefix detection can bypass normal handling with the wrong result.
Restricting env stripping to valid assignment tokens fixes false
positives without changing the public API or the response format.
## How it was tested
I validated the updated logic against representative inputs such as
`FOO=bar git status`, plain commands like `git status`, and malformed or
path-like tokens containing `=` that should not be treated as env
assignments. The change is isolated to parsing logic and does not
introduce new dependencies.
---------
Co-authored-by: genoshide <genoshide@users.noreply.github.com>
- api/command_utils.py +16 -6
|
@@ -1,13 +1,21 @@
|
|
| 1 |
"""Command parsing utilities for API optimizations."""
|
| 2 |
|
|
|
|
| 3 |
import shlex
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
def _strip_env_assignments(parts: list[str]) -> list[str]:
|
| 7 |
"""Return command parts after leading shell-style env assignments."""
|
| 8 |
cmd_start = 0
|
| 9 |
for i, part in enumerate(parts):
|
| 10 |
-
if
|
| 11 |
cmd_start = i + 1
|
| 12 |
else:
|
| 13 |
break
|
|
@@ -36,7 +44,7 @@ def extract_command_prefix(command: str) -> str:
|
|
| 36 |
env_prefix = []
|
| 37 |
cmd_start = 0
|
| 38 |
for i, part in enumerate(parts):
|
| 39 |
-
if
|
| 40 |
env_prefix.append(part)
|
| 41 |
cmd_start = i + 1
|
| 42 |
else:
|
|
@@ -69,7 +77,11 @@ def extract_command_prefix(command: str) -> str:
|
|
| 69 |
return first_word if not env_prefix else " ".join(env_prefix) + " " + first_word
|
| 70 |
|
| 71 |
except ValueError:
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
|
| 75 |
def extract_filepaths_from_command(command: str, output: str) -> str:
|
|
@@ -125,21 +137,19 @@ def extract_filepaths_from_command(command: str, output: str) -> str:
|
|
| 125 |
if base_cmd == "grep":
|
| 126 |
flags_with_args = {"-e", "-f", "-m", "-A", "-B", "-C"}
|
| 127 |
pattern_provided_via_flag = False
|
| 128 |
-
positional
|
| 129 |
|
| 130 |
skip_next = False
|
| 131 |
for part in cmd_parts[1:]:
|
| 132 |
if skip_next:
|
| 133 |
skip_next = False
|
| 134 |
continue
|
| 135 |
-
|
| 136 |
if part.startswith("-"):
|
| 137 |
if part in flags_with_args:
|
| 138 |
if part in {"-e", "-f"}:
|
| 139 |
pattern_provided_via_flag = True
|
| 140 |
skip_next = True
|
| 141 |
continue
|
| 142 |
-
|
| 143 |
positional.append(part)
|
| 144 |
|
| 145 |
filepaths = positional if pattern_provided_via_flag else positional[1:]
|
|
|
|
| 1 |
"""Command parsing utilities for API optimizations."""
|
| 2 |
|
| 3 |
+
import re
|
| 4 |
import shlex
|
| 5 |
|
| 6 |
+
_ENV_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _is_env_assignment(part: str) -> bool:
|
| 10 |
+
"""Return True when a token is a shell-style env assignment."""
|
| 11 |
+
return bool(_ENV_ASSIGNMENT_RE.match(part))
|
| 12 |
+
|
| 13 |
|
| 14 |
def _strip_env_assignments(parts: list[str]) -> list[str]:
|
| 15 |
"""Return command parts after leading shell-style env assignments."""
|
| 16 |
cmd_start = 0
|
| 17 |
for i, part in enumerate(parts):
|
| 18 |
+
if _is_env_assignment(part):
|
| 19 |
cmd_start = i + 1
|
| 20 |
else:
|
| 21 |
break
|
|
|
|
| 44 |
env_prefix = []
|
| 45 |
cmd_start = 0
|
| 46 |
for i, part in enumerate(parts):
|
| 47 |
+
if _is_env_assignment(part):
|
| 48 |
env_prefix.append(part)
|
| 49 |
cmd_start = i + 1
|
| 50 |
else:
|
|
|
|
| 77 |
return first_word if not env_prefix else " ".join(env_prefix) + " " + first_word
|
| 78 |
|
| 79 |
except ValueError:
|
| 80 |
+
parts = command.split()
|
| 81 |
+
if not parts:
|
| 82 |
+
return "none"
|
| 83 |
+
cmd_parts = _strip_env_assignments(parts)
|
| 84 |
+
return cmd_parts[0] if cmd_parts else "none"
|
| 85 |
|
| 86 |
|
| 87 |
def extract_filepaths_from_command(command: str, output: str) -> str:
|
|
|
|
| 137 |
if base_cmd == "grep":
|
| 138 |
flags_with_args = {"-e", "-f", "-m", "-A", "-B", "-C"}
|
| 139 |
pattern_provided_via_flag = False
|
| 140 |
+
positional = []
|
| 141 |
|
| 142 |
skip_next = False
|
| 143 |
for part in cmd_parts[1:]:
|
| 144 |
if skip_next:
|
| 145 |
skip_next = False
|
| 146 |
continue
|
|
|
|
| 147 |
if part.startswith("-"):
|
| 148 |
if part in flags_with_args:
|
| 149 |
if part in {"-e", "-f"}:
|
| 150 |
pattern_provided_via_flag = True
|
| 151 |
skip_next = True
|
| 152 |
continue
|
|
|
|
| 153 |
positional.append(part)
|
| 154 |
|
| 155 |
filepaths = positional if pattern_provided_via_flag else positional[1:]
|