Skip to content

filesystem: FS_SEARCH_EXCLUDE_PREFIXES is lexical; should excludes be canonical/symlink-aware? #4208

@JonoGitty

Description

@JonoGitty

Context. The proposed fix for #4162 (currently on a fork branch, not yet opened as a PR) adds FS_SEARCH_EXCLUDE_PREFIXES as a fast lexical prefilter that refuses recursive search_files / directory_tree on configured slow roots, typically macOS CloudStorage / FileProvider paths.

Discussion on #4162 surfaced that the check is purely lexical: a symlink such as /allowed/sneaky whose target lives under an excluded prefix is not caught by the prefix check, and may still reach the slow provider path during canonicalisation / validation.

The case for keeping the #4162 fix scoped to the lexical prefilter, as discussed in that thread:

  • The bypass is pre-existing behaviour — the original searchFilesWithValidation exclude check is also lexical.
  • validatePath continues to enforce the allowed_directories escape boundary via its own realpath, so the security boundary is unaffected. This question is about whether excludes should be symlink-aware.
  • The impact of the bypass is performance / hang risk from reaching a slow provider path, not an authorization bypass.
  • An unbounded realpath-before-compare exclude check risks re-introducing the kind of hang filesystem: recursive search can hang on macOS CloudStorage / lazy provider paths #4162 is trying to avoid.

Design question. Should excludes be hardened against symlink bypass?

One possible shape is two-stage:

  1. Fast lexical check first, preserving current cheap behaviour.
  2. If not excluded lexically, optionally perform bounded canonicalisation, e.g. withFsTimeout(fs.realpath(...)), then re-check the canonical path against excluded prefixes, with an explicit fail-policy on timeout — deny, allow, or return a typed error.

This affects both search_files and directory_tree, so it seems like a design decision rather than a drop-in fix. Not gating #4162; filing this separately so the symlink-aware exclude question is not lost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions