fix(approvals): scope trailing-slash directory globs to their parent - #1785
Open
Aaronontheweb wants to merge 5 commits into
Open
fix(approvals): scope trailing-slash directory globs to their parent#1785Aaronontheweb wants to merge 5 commits into
Aaronontheweb wants to merge 5 commits into
Conversation
A glob that ends in a slash (foo/*/) matches only directories. It adds no descendant path segment. Every path that the wildcard expands is still a direct child of the covering directory foo. Since 0.25.3 the approval gate read the trailing slash as an unresolved descendant scope. So a command like `ls -d .../immovlan/*/` failed closed to a one-shot "complex command" prompt. HasUnresolvedDescendantScope now removes trailing slashes before the descendant-segment test. A real segment after the wildcard (foo/*/x, foo/*/*) still fails closed. The covering-directory symlink scan does not change. So foo/*/ gets the same persistable, symlink-checked scope that the leaf glob foo/* already has. Tests: - Matcher corpus: trailing-slash globs resolve a covering directory and are not complex. Real descendant segments and symlink children stay one-shot. - Disposition corpus: an in-project directory command auto-allows as a safe verb, an external scope offers a persistent grant, and the reported pipeline is not complex. - Harden the external-glob case to an isolated temp subdirectory. A symlink child of shared /tmp (for example an IDE socket) no longer flakes the scan.
Aaronontheweb
force-pushed
the
fix/approvals-trailing-slash-glob
branch
from
August 6, 2026 15:25
a69479e to
9da8939
Compare
Aaronontheweb
enabled auto-merge (squash)
August 6, 2026 15:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Netclaw 0.25.3 changed how the approval gate reads a glob. A command like
ls -d /home/netclaw/.netclaw/workspaces/immovlan/*/now shows the prompt"complex command — only one-shot approval available". The operator cannot keep
the grant. So the agent asks for approval on every call. This causes approval
fatigue.
Root cause
The approval gate marks a glob as an unresolved descendant scope. It applies the
mark when a
/follows the first wildcard character. The helper isHasUnresolvedDescendantScope.A trailing slash (
foo/*/) is a type filter for directories. It is not adescendant path segment. Every path that the wildcard expands is still a direct
child of the covering directory
foo.The gate did not tell these two shapes apart. So it failed
foo/*/closed toone-shot approval. Two prior changes added the behavior: the parser-based
analysis (#1753) and the descendant-scope rule (#1768). Version 0.25.2 did not
have the behavior.
Fix
HasUnresolvedDescendantScopenow removes trailing slashes first. Then it runsthe same test for a
/after the wildcard. One helper feeds three consumers:HasDynamicSyntax, the covering-directory guard, andIsMessy. So the threecannot drift apart.
foo/*/becomes the leaf globfoo/*. The gate resolves the coveringdirectory
foo. The grant is persistable, and the symlink scan still runs.foo/*/xandfoo/*/*stay one-shot.
Corpus examples
These cases are safe to evaluate. The gate can resolve a scope and offer a
persistent grant:
These cases are not safe to evaluate. The gate keeps them one-shot:
Why the safe cases are safe
A glob is safe to evaluate for a persistent grant when the covering directory
bounds every expanded path. For
foo/*/:foo.foo. The directoryfoois atrue upper bound.
foofor a symlink child. A symlink child fails the commandclosed. So a matched directory cannot escape
foothrough a link.A glob is not safe when a real segment follows the wildcard. For
foo/*/x:directory.
foochecks only direct children. It cannot bound a deeper linksuch as
foo/dir/x.Blast radius
The predicate serves only the approval matcher. The hard-deny policy does not
read it, and hard-deny runs first. So the fix cannot bypass a deny rule. The net
effect for
foo/*/:ls foo/*today.scoped to the covering directory, not one-shot only.
foo/*/argument, not onlythe reported one.
These paths do not change (and the tests verify it):
Tests
ShellApprovalMatcherPathExtractionTests): trailing-slashglobs resolve a covering directory and are not complex. Real descendant
segments and symlink children stay one-shot.
ShellApprovalCases): an in-project directory commandauto-allows as a safe verb, an external scope offers a persistent grant, and
the reported pipeline is not complex.
external-glob-does-not-reuse-project-grant. It now uses anisolated temp subdirectory. A symlink child of the shared system temp (for
example an IDE socket) no longer flakes the covering-directory scan.
dotnet slopwatch analyze: 0 issues. Copyright headers verified.