Skip to content

fix(approvals): scope trailing-slash directory globs to their parent - #4

Closed
Aaronontheweb wants to merge 1 commit into
devfrom
fix/approvals-trailing-slash-glob
Closed

fix(approvals): scope trailing-slash directory globs to their parent#4
Aaronontheweb wants to merge 1 commit into
devfrom
fix/approvals-trailing-slash-glob

Conversation

@Aaronontheweb

Copy link
Copy Markdown
Owner

Problem

Since 0.25.3, a directory-list glob that ends in a slash — for example
ls -d /home/netclaw/.netclaw/workspaces/immovlan/*/ — is flagged as a
"complex command — only one-shot approval available". The operator cannot grant
it persistently, so the agent re-prompts on every call. This causes approval
fatigue.

Root cause

The approval gate marks a glob as an unresolved descendant scope when a /
follows the first wildcard character (HasUnresolvedDescendantScope). A trailing
slash (foo/*/) is a directory-only type filter, not a descendant path segment —
every match is still a direct child of the covering directory foo. The predicate
did not tell the two apart, so foo/*/ failed closed to one-shot only. This
behavior arrived with the parser-based approval analysis (netclaw-dev#1753) and the
descendant-scope rule (netclaw-dev#1768); 0.25.2 did not have it.

Fix

HasUnresolvedDescendantScope now strips trailing slashes before the
"is there a / after the wildcard" test. One helper feeds all three consumers
(HasDynamicSyntax, the covering-directory guard, and IsMessy), so they cannot
drift.

  • foo/*/ normalizes to the leaf foo/*. It resolves the covering directory
    foo, so it is persistable and symlink-checked.
  • A real segment after the wildcard (foo/*/x, foo/*/*) keeps its separator
    and stays one-shot.

Behavior change (blast radius)

The predicate is used only by the approval matcher. The hard-deny /
protected-path policy does not read it and runs first regardless. Net effect for
the foo/*/ shape:

  • A read-only safe verb inside the trusted tree (session/project) now
    auto-allows, the same as the leaf glob ls foo/* today.
  • Outside the trusted tree the command prompts with a persistent grant scoped to
    the covering directory, instead of one-shot only.
  • The change applies to every command that carries a foo/*/ argument, not just
    the reported one.

Unchanged and verified: hard-deny, external and protected paths still prompt,
symlink globs still fail closed, real descendant globs stay one-shot, Windows
(legacy tokenizer path), and non-shell matchers.

Tests

  • Matcher glob corpus (ShellApprovalMatcherPathExtractionTests): trailing-slash
    globs resolve a covering directory and are not messy; real descendant segments
    and symlink children stay one-shot.
  • Disposition matrix (ShellApprovalCases): an in-project directory list
    auto-allows as a safe verb, an external scope offers a persistent grant, and
    the reported pipeline is no longer complex.
  • Hardened the pre-existing external-glob-does-not-reuse-project-grant case to
    an isolated temp subdirectory, so 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.

A glob that ends in a slash (foo/*/) matches only directories. It adds no
descendant path segment. Every match 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/*/`
degraded to a one-shot "complex command" prompt.

HasUnresolvedDescendantScope now strips 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 messy. Real descendant segments and symlink children stay one-shot.
- Disposition corpus: an in-project directory list auto-allows as a safe
  verb, an external scope offers a persistent grant, and the reported
  pipeline is no longer complex.
- Harden the external-glob case to an isolated temp subdir. A symlink child
  of shared /tmp (for example an IDE socket) no longer flakes the scan.
@Aaronontheweb

Copy link
Copy Markdown
Owner Author

Superseded by netclaw-dev#1785 (retargeted to upstream netclaw-dev/netclaw).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant