fix(e2b): quote grep filename filters (#6529)

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
asts 2026-10-09 17:04:22 +08:00 committed by GitHub
parent db1e0e10b4
commit 43d93ce0b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 36 additions and 1 deletions

View File

@ -2073,6 +2073,7 @@ Each task gets its own execution environment with a full filesystem view — ski
The read-before-write gate ties each read mark to that `read_file` call's result, including custom tools returning multi-message `Command` updates. An unrelated result cannot authorize a write after a failed read or hide a successful read.
The built-in `grep` tool searches either one text file or all matching text files below a directory, so an agent can search an uploaded document directly without first broadening the request to the entire uploads directory.
E2B's `glob` filter preserves spaces, quotes, and dollar signs in filename patterns, while wildcard matching and root-relative directory scoping remain unchanged.
Remote `ls` excludes ignored descendants before applying its 500-entry listing limit, so dependency and build trees do not crowd out visible files. Explicitly listing an ignored directory still lists its contents; normal depth and output limits remain in effect.

View File

@ -36,3 +36,11 @@ That fence covers cached sync/async acquisition too: recheck after serializer
waits and immediately before exposing a reused client. Signal-triggered shutdown
may report deferred cleanup, but it must still forward the process's original
SIGTERM/SIGINT/SIGHUP action while retaining the pending E2B state.
## File search
Quote the complete `grep --include=<pattern>` argument with `shlex.quote` before
passing it to the shell. The shell must not split or expand filename patterns;
grep owns basename globbing and `path_matches` owns root-relative directory
scoping. `tests/test_sandbox_search_contract.py` pins this across providers using
real temporary files and offline shell transports.

View File

@ -493,7 +493,7 @@ class E2BSandbox(Sandbox):
# pattern) and enforce the real directory scope below via
# ``path_matches``, the same helper ``glob()`` uses.
include_pattern = glob.split("/")[-1] or glob
flags.append(f"--include={include_pattern}")
flags.append(shlex.quote(f"--include={include_pattern}"))
per_file_cap = max(max_results + 1, 50)
total_cap = max(max_results * 4, max_results + 50)

View File

@ -241,6 +241,32 @@ def test_grep_glob_filter_is_root_relative(provider, tree):
assert truncated is False
@pytest.mark.parametrize(
("filename", "glob"),
[
("release notes.txt", "release *.txt"),
("author's-notes.txt", "author's-*.txt"),
('release"notes.txt', 'release"*.txt'),
("price$USD.txt", "price$USD.txt"),
("report1.txt", "report[0-9].txt"),
("plain.txt", "*.txt"),
],
ids=["space", "single-quote", "double-quote", "dollar", "character-range", "wildcard"],
)
def test_grep_glob_preserves_filename_characters(provider, tmp_path, filename, glob):
root = tmp_path / "workspace"
for directory in (root / "docs", root / "other", root / "docs" / "nested"):
directory.mkdir(parents=True)
(directory / filename).write_text("header\nneedle\n", encoding="utf-8")
(root / "docs" / "excluded.md").write_text("needle\n", encoding="utf-8")
provider.transport.env["USD"] = "expanded"
matches, truncated = provider.sandbox.grep(str(root), "needle", glob=f"docs/{glob}")
assert [(match.path, match.line_number, match.line) for match in matches] == [(str(root / "docs" / filename), 2, "needle")]
assert truncated is False
def test_no_match_is_empty_and_complete(provider, tree):
assert provider.sandbox.glob(str(tree), "**/*.missing") == ([], False)
assert provider.sandbox.grep(str(tree), "absentneedle") == ([], False)