Repository navigation
fix(sdk): sandbox not-found contract and glob deny over-blocking on delete #4321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
b275de5
6651d59
e1766b8
2591045
06ce20f
19930cf
11e6af4
9a81dab
5dc87bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,7 @@ | |
| execute_accepts_timeout, | ||
| ) | ||
| from deepagents.backends.utils import ( | ||
| _GLOB_WILDCARD_CHARS, | ||
| _get_file_type, | ||
| _glob_anchor, | ||
| _paths_overlap, | ||
|
|
@@ -162,7 +163,47 @@ def _find_delete_deny_patterns(rules: list[FilesystemPermission], target: str) - | |
| if rule.mode != "deny" or "write" not in rule.operations: | ||
| continue | ||
| for pattern in rule.paths: | ||
| if pattern not in seen and _paths_overlap(target, _glob_anchor(pattern)): | ||
| if pattern in seen: | ||
| continue | ||
| anchor = _glob_anchor(pattern) | ||
| if any(c in _GLOB_WILDCARD_CHARS for c in pattern): | ||
| # Wildcard pattern — three cases in priority order: | ||
| # 1. Root anchor ("/**/x"): pattern can match anywhere, block all. | ||
| # 2. Target directly matches the glob: block. | ||
| # 3. Anchor is inside the delete subtree (anchor.is_relative_to(target)): | ||
| # recursive delete would remove matching descendants — block. | ||
| # 4. Target is below the anchor (target.is_relative_to(anchor)): | ||
| # safe to allow ONLY when the pattern suffix is a single, | ||
| # non-** component (fixed depth). "/work/*.log" can never match | ||
| # anything under "/work/notes.txt". Patterns with directory | ||
| # wildcards after the anchor ("/work/*/secrets", | ||
| # "/work/**/secrets") could match descendants of the target, | ||
| # so we fail closed for those. | ||
| if ( | ||
| anchor == "/" | ||
| or wcglob.globmatch(target, pattern, flags=_FS_WCMATCH_FLAGS) | ||
| or PurePosixPath(anchor).is_relative_to(PurePosixPath(target)) | ||
|
imnishitha marked this conversation as resolved.
Resolved
|
||
| ): | ||
| overlaps = True | ||
| elif PurePosixPath(target).is_relative_to(PurePosixPath(anchor)): | ||
| # Target is below anchor. Safe to allow only when the pattern | ||
| # suffix is a single non-** component (fixed depth): a file glob | ||
| # like "*.log" can never match anything under a sibling target. | ||
| # Patterns with directory wildcards ("/work/*/secrets") could | ||
| # match descendants of target, so fail closed for those. | ||
| anchor_parts = PurePosixPath(anchor).parts | ||
| pattern_parts = PurePosixPath(pattern).parts | ||
| suffix = pattern_parts[len(anchor_parts) :] | ||
| single_filename_glob = len(suffix) == 1 and "**" not in suffix[0] | ||
| overlaps = not single_filename_glob | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new single-component wildcard exemption only checks whether elif PurePosixPath(target).is_relative_to(PurePosixPath(anchor)):
anchor_parts = PurePosixPath(anchor).parts
pattern_parts = PurePosixPath(pattern).parts
suffix = pattern_parts[len(anchor_parts) :]
single_filename_glob = len(suffix) == 1 and "**" not in suffix[0]
overlaps = not single_filename_globThis bypasses deny rules for wildcard-matched directories. For example, with a deny rule Remediation: Before allowing the single-component wildcard case, check whether the immediate child under the anchor matches the deny pattern. If an ancestor such as For more details, see the finding in Corridor. Provide feedback: Reply with whether this is a valid vulnerability or false positive to help improve Corridor's accuracy. |
||
| else: | ||
| # No subtree relationship at all — unrelated paths. | ||
| overlaps = False | ||
| else: | ||
| # Literal pattern (no wildcards): keep the original subtree-overlap | ||
| # check so that a deny on "/work" blocks deletes of "/work/sub". | ||
| overlaps = _paths_overlap(target, anchor) | ||
| if overlaps: | ||
| seen.add(pattern) | ||
| denying.append(pattern) | ||
| return denying | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.