Skip to content

Commit 980b497

Browse files
committed
Fix shell injection vulnerability in LocalRepoTools.search_code()
- Convert grep command from shell=True string to list-based subprocess call - Add '--' separator to prevent query from being interpreted as grep flags - Prevents shell injection attacks via user-provided search queries - Maintains backward compatibility with regex patterns and special characters - Verified with test queries including malicious payloads Agent-Id: agent-fce47f03-d32a-4dda-b056-e23ed5a831f7
1 parent 442089f commit 980b497

1 file changed

Lines changed: 15 additions & 12 deletions

File tree

npx/python/cli/local_repo_tools.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,22 +150,25 @@ async def list_directory(self, path: str = "") -> list[dict[str, Any]]:
150150

151151
async def search_code(self, query: str) -> list[dict[str, Any]]:
152152
"""Search for code patterns in the local repo using grep.
153-
153+
154154
Returns paths + fragments. Soft-fails on error (returns []).
155155
"""
156156
if not query or not query.strip():
157157
return []
158-
158+
159159
query = query.strip()
160-
161-
# Build grep command
162-
extensions = " ".join(f"--include='*{ext}'" for ext in SEARCH_EXTENSIONS)
163-
cmd = f"grep -rn {extensions} {query} {self.root_path}"
164-
160+
161+
# Build grep command as list (no shell=True to prevent shell injection)
162+
args = ["grep", "-rn"]
163+
for ext in SEARCH_EXTENSIONS:
164+
args.append(f"--include=*{ext}")
165+
args.append("--") # End of options, prevents query from being interpreted as flag
166+
args.append(query)
167+
args.append(self.root_path)
168+
165169
try:
166170
result = subprocess.run(
167-
cmd,
168-
shell=True,
171+
args,
169172
capture_output=True,
170173
text=True,
171174
timeout=10,
@@ -174,10 +177,10 @@ async def search_code(self, query: str) -> list[dict[str, Any]]:
174177
return [] # Soft fail
175178
except Exception:
176179
return [] # Soft fail
177-
180+
178181
if result.returncode != 0:
179182
return [] # No matches or error
180-
183+
181184
results = []
182185
for line in result.stdout.splitlines()[:10]: # Limit to 10 results
183186
# Parse grep output: path:line:content
@@ -190,7 +193,7 @@ async def search_code(self, query: str) -> list[dict[str, Any]]:
190193
"path": rel_path.replace(os.sep, "/"),
191194
"fragment": fragment,
192195
})
193-
196+
194197
return results
195198

196199
async def close(self):

0 commit comments

Comments
 (0)