Skip to content

fix(runtime): contain workspace glob results so brace-expanded patterns cannot list outside the session cwd - #6399

Open
ggbdpq wants to merge 2 commits into
apache:mainfrom
ggbdpq:fix/6397-glob-result-containment
Open

ggbdpq wants to merge 2 commits into
apache:mainfrom
ggbdpq:fix/6397-glob-result-containment

Conversation

@ggbdpq

@ggbdpq ggbdpq commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Glob patterns are validated lexically, but brace expansion happens inside the walker: a lexically clean pattern like {a,../*} walks above the session cwd and its results listed outside entries with no downstream filter. This fixes it at the shared reader — globFiles gains an optional containmentRoot and drops matched paths that resolve outside it via the existing isPathInside predicate (literal containment; the walk already refuses to follow symlinks and results are names, not opened content). The workspace executor passes the canonicalised session cwd (same spelling space as the walker's root — a symlinked root like /var → /private/var would otherwise reject every legitimate entry); the filesystem worker passes its search root, matching its guard's own contract. Dropped escapes do not count toward the limit. The Windows sandbox preview's non-following walker is a separate traversal and is intentionally untouched.

Fixes #6397

Verification

Check Result
New test Glob brace expansion cannot list entries outside the session cwd on unfixed base FAIL — leaks host tmp entries (sock files, other session dirs)
Same test with fix PASS
Ablation (filter reverted, test kept) FAIL again at the reverted line
builtin-tools / glob-search / filesystem-worker / file-worker / windows-glob suites pass (6 pre-existing Bash-spawn failures identical to stashed baseline by name and count; plus the plain ../*.txt lexical-guard pin stays green)
workspace-executor suite 5 failures identical to stashed baseline (pre-existing exec-spawn, unrelated)
tsc -p packages/runtime, biome per changed file, check:asf-headers all green

AI use

Implemented with GLM-5.3-Flash (ZCode); the lexical guard, the brace-expansion semantics of the pinned glob version, and the missing downstream filter were re-verified line by line before filing #6397.

Checklist

  • Result-level containment covers brace expansion and any other walker semantics that can navigate outwards; host scope keeps raw results with zero added work
  • Literal-path filtering (not realpath) — the walk already refuses symlinks and results are names, not opened content; the canonical-spelling subtlety (macOS /var → /private/var) was hit and fixed in testing
  • Dropped escapes do not count toward the result limit; Windows sandbox preview walker untouched (unverifiable on this machine, recorded in the commit message)

Glob patterns are validated lexically (`assertGlobPatternInScope` and the
worker's `assertContainedGlobPattern`): a pattern is rejected only when its
literal text carries an absolute path or a `..` segment. Brace expansion
happens later, inside the walker, so a lexically clean pattern like
`{a,../*}` walks above its search root and glob results listed entries
outside the session cwd with no downstream filter (apache#6397).

Filter at the shared reader instead: `globFiles` gains an optional
`containmentRoot` and drops matched paths that resolve outside it using the
existing `isPathInside` predicate (literal resolve-space containment — the
walk already refuses to follow symlinks, and results are names, not opened
content). Dropped escapes do not count toward the limit, so truncation
semantics still describe the returned prefix.

The workspace executor passes the canonicalised session cwd (same spelling
space as the walker's search root, or a symlinked root like /var ->
/private/var would reject every legitimate entry); host scope keeps raw
results and zero added work. The filesystem worker passes its search root,
matching its "stay inside its search root" guard contract. The Windows
sandbox preview's non-following walker (`windowsNonFollowingGlob`) is a
separate traversal implementation and is intentionally untouched.

Fixes apache#6397

Generated-by: GLM-5.3-Flash (ZCode)
@github-actions github-actions Bot added the effort/S Under 100 readable lines label Oct 27, 2026

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.

Reviewed all five changed files at 864feb721a9da770f46736314f6df6616476a531. There is one P2 remaining containment gap, described inline at glob-search.ts:86.

The brace-expansion problem is reproducible, and this patch fixes the tested outward brace branches without counting excluded matches against the returned-result limit. However, the assumption that the walker never follows directory links does not hold for explicit link-prefixed patterns. With workspace/link-out pointing to an outside directory containing secret.txt, link-out/**/*.txt still returns link-out/secret.txt through the shared reader, the production local filesystem executor, and the production filesystem worker. The lexical result check accepts that spelling even though its ancestor resolves outside the workspace. This is also reproducible on current main; it is a pre-existing gap left open by this fix, not a newly introduced regression. The demonstrated exposure is an outside filename, not a successful file-content read.

I ran the repository glob-search and filesystem-worker suites: 35 tests passed. In 18 additional real-filesystem cases, 15 passed and the three link-traversal cases failed, one for each production entry point. Current main fails nine of those cases; the patch removes six brace-related failures and retains the three link failures. The actual clean merge with main 85a1031c10d104456127f35aac1523b9ffb1a384 produces the same results. Host scope intentionally retains outside matches; a symlinked Session root still keeps legitimate in-root results.

The head's hosted CI test is successful (run 40581707421). Local execution used the actual production modules, Node's TypeScript transformation, and glob 13.0.6, without filesystem adapters. I did not run the complete builtin-tools suite, a full repository build, an OS sandbox, native Windows, or macOS validation.


Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

Comment thread packages/runtime/src/glob-search.ts Resolved
Directory symlinks escape the glob containment check: `resolve()` only
normalizes the spelling, so an explicit pattern like `link-out/**/*.txt`
with `link-out -> ../outside` returns files whose real location is
outside the containment root while staying lexically inside it.

Decide containment in real-path space instead: realpath the containment
root once, realpath each candidate match, and run the pure predicate on
those (the same idiom skills-discovery already uses). A root that cannot
be realpath'd propagates, because the walk cannot claim containment it
cannot place; a match that cannot be realpath'd (raced deletion) is
dropped and marks the walk incomplete rather than silently complete.

Fixes apache#6397

Generated-by: GLM-5.3-Flash (ZCode)

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.

Rechecked the complete five-file patch at e7fb6e1920168413bfd9b6a75bd6ec76bf30a3ad, including the two-file delta from my previous review. The remaining directory-link containment P2 is fixed: the root and each accepted candidate are now compared in real-path space. The exact link-out/**/*.txt reproduction no longer returns outside filenames through the shared reader, local filesystem executor or filesystem worker. In-root directory aliases still return legitimate matches. I found no confirmed new P0–P2 issue.

On both the head and its actual clean merge with main 85a1031c10d104456127f35aac1523b9ffb1a384, 35 repository tests and 25 additional real-filesystem checks passed. The checks cover the previous 18-case matrix plus in-root directory links, outside file links and an unavailable containment root. Replacing only the shared reader with the previously reviewed implementation makes the three original directory-link cases fail again. Excluded matches still do not consume the returned-result cap; unrestricted host scope and a symlinked Session root retain their intended behavior.

The current test CI and both Windows checks are successful. Local tests execute the actual production modules with Node's TypeScript transformation and glob 13.0.6, using real temporary files and links. I did not run the complete builtin-tools suite, a full build or typecheck, an OS sandbox, or native Windows/macOS validation. The raced-candidate-deletion branch was inspected in source, not forced during these tests.


Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

@github-actions github-actions Bot added effort/M Under 500 readable lines and removed effort/S Under 100 readable lines labels Nov 1, 2026
@ggbdpq

ggbdpq commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the recheck at e7fb6e1 — the 18-case matrix extended with in-root directory links, outside file links and an unavailable containment root, plus the swap-back ablation re-failing the three original cases, is exactly the verification loop this fix needed. No changes needed at this head.

This branch has not been deployed

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

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(runtime): brace-expanded glob patterns escape the workspace containment guard

2 participants