A search tool needs a pattern and a file type. Giving it a free-form command string creates a much larger interface than the task requires.
That gap appears repeatedly in agent tooling. The advisory set includes Continue's MCP transport, mcp-ripgrep, and Token Optimizer MCP. Their exact affected versions and fixes are in the linked advisories.
The design problem is easy to reproduce in a small example.
Before: let text become syntax
This is an illustrative unsafe wrapper, not code copied from those projects:
command = f"rg --type {file_type} {pattern} /workspace"
subprocess.run(command, shell=True)
The shell gets a single string containing both the application's intended command and caller-controlled text. Separators, substitutions, quoting, and redirection can change how that string is interpreted.
Adding an escaping function may address one shell and one interpolation context. It leaves a difficult interface to maintain.
After: expose the operation directly
The next example demonstrates the narrower process interface:
import subprocess
ALLOWED_TYPES = {"py": "py", "ts": "ts", "md": "md"}
def search(pattern: str, file_type: str) -> subprocess.CompletedProcess:
kind = ALLOWED_TYPES[file_type]
return subprocess.run(
["/usr/bin/rg", "--type", kind, "--", pattern, "/workspace"],
shell=False,
timeout=5,
check=False,
)
The executable path is fixed for this example deployment. The file type comes from an allowlist. The argument array prevents shell interpretation, while -- marks the end of options for this utility.
This snippet illustrates argument construction only. Production code also needs error handling, pattern and input limits, restricted output, and an account or sandbox with access only to the intended files. A timeout alone is not a memory or output budget.
The remaining risk is capability
An argument array does not make an arbitrary executable safe. Some tools can load plugins, execute helper commands, or write to caller-selected paths through perfectly valid options.
Review the semantics of every exposed argument. Keep those options out of the public tool schema unless the operation truly needs them.
Test shell metacharacters, dash-prefixed patterns, unexpected file types, and long-running searches. Verify the resolved argument vector rather than relying on the model's explanation of what it intended.
A narrow tool is also easier for a human to review: “search Markdown files for this pattern” tells an approver much more than “run this command.”