Skip to content

fix(sync): stop probing macOS protected folders during discovery - #1366

Merged
wesm merged 16 commits into
mainfrom
kenn-forge/issue-1364-macos-app-requests-access-to-documents-downloads-and-dropbox
Aug 10, 2026
Merged

fix(sync): stop probing macOS protected folders during discovery#1366
wesm merged 16 commits into
mainfrom
kenn-forge/issue-1364-macos-app-requests-access-to-documents-downloads-and-dropbox

Conversation

@wesm

@wesm wesm commented Aug 8, 2026

Copy link
Copy Markdown
Member

Closes #1364.

Problem

On first open, the macOS app asked for access to Documents, Downloads, and Dropbox. The app requests no special entitlements — the prompts came from background sync reading files inside those folders:

  • Every session's recorded working directory gets probed for git info (repo root, remote, branch).
  • The first sync probes every session in the archive at once.
  • macOS shows one consent prompt per protected folder that any session ever ran in.

Fix

  • New path classifier decides, before touching anything: safe, protected (Documents, Downloads, Desktop, Movies, Music, Pictures, iCloud Drive, cloud folders like Dropbox), or automount (/home, /net).
  • Protected paths are not probed. Sessions there are still ingested, listed, and searchable — they just show a path-based name with no git remote, worktree, or branch info.
  • Every probe checks the classifier first: git-root walks, gitfile targets, metadata files (HEAD, config, commondir), deleted-session recovery, and stored snapshot roots.
  • The classifier follows symlinks and .. in real traversal order, folds case, and recognizes the /System/Volumes/Data firmlink spelling — without itself entering protected folders or waking automountd.
  • Custom autofs mounts from the host's mount table (e.g. /corp/home) count as automount too.
  • Passive discovery never runs the git binary: git follows config-derived paths ([include] path) that no vetting can constrain. All git-layout resolution is filesystem-local; rare layouts only git could resolve now get path-based names.

Opting back in

  • Set scan_protected_paths = true in config.toml to restore full git info for code kept in protected folders. macOS prompts once per folder.
  • The opt-in never allows automount probing (that would cause CPU churn, not a prompt).
  • Applies to sessions parsed after the change; run agentsview sync --full to refresh existing ones.

Known limits

  • The protected-folder list is fixed; a cloud app that mounts a folder directly in $HOME (outside ~/Library/CloudStorage) would still prompt.
  • An old-style real ~/Dropbox folder (not cloud-mounted) loses git info it didn't have to; the opt-in restores it.

Where to look

  • internal/export/project_identity.go — the classifier
  • internal/sync/engine.go — identity capture gates
  • internal/parser/project.go — project-name extraction gates

🤖 Generated with Claude Code

Local project-identity discovery resolved every session's recorded
working directory and read Git metadata from it, and the source-project
probe stat-ed the same path. On macOS both reach into locations guarded
by a TCC consent prompt, so a first sync raised a prompt for every
guarded folder any session had ever run in. The desktop app requests no
file-access entitlement; the prompts came from this passive access.

Discovery now skips working directories under Desktop, Documents,
Downloads, Movies, Music, Pictures, Library/CloudStorage, Library/Mobile
Documents, and Dropbox. Sessions there keep path-only project identity
and lose only Git remote, worktree, and branch detail. The new
scan_protected_paths config option opts back in for users who keep code
in those folders and accept the prompt.

The gate lives on the engine and defaults to closed, so an engine built
without the option, including the one that drives the startup identity
backfill, cannot prompt.

Closes #1364
@roborev-ci

roborev-ci Bot commented Aug 8, 2026

Copy link
Copy Markdown

roborev: Combined Review (4417413)

High-severity issue remains: protected-path gating occurs too late, so macOS consent prompts can still be triggered during parsing.

High

  • internal/parser/project.go:130 — The gate runs after parsing, but parsers including Codex and Claude call ExtractProjectFromCwd*, which invokes findGitRepoRoot and stats the recorded working directory. Sessions under Documents, Downloads, or cloud folders can therefore still trigger the macOS consent prompts this change intends to prevent.
    • Fix: Apply the protected-path policy during parser project extraction, bypassing all filesystem-backed Git-root discovery for protected CWDs unless scan_protected_paths is enabled.

Medium

  • internal/export/project_identity.go:844 — Protected-path detection checks only the lexical path. An unprotected-looking CWD symlinked into ~/Documents or ~/Library/CloudStorage passes the check; subsequent os.Stat or EvalSymlinks can follow it into the protected location and trigger a consent prompt.
    • Fix: Resolve symlink components without entering protected targets, checking each resulting path against protected roots before any Stat, EvalSymlinks, or Git metadata access.

Reviewers: 2 done | Synthesis: codex, 13s | Total: 4m55s

Review of the previous commit found two gaps. First, project extraction
itself probes the recorded working directory: findGitRepoRoot stats
every ancestor, reads .git file contents, lists sibling directories,
and execs git, all before the engine's identity gates run, so parsing a
session recorded under Documents still raised the consent prompt.
Extraction now consults the same protected-path policy and falls back
to the path basename for refused cwds. The guard is package-level
because parsers run deep inside per-format code; NewEngine enables it
when scan_protected_paths is set and never disables it.

Second, the protected-path check compared lexically, so a working
directory that reaches a protected folder only through a symlink passed
the gate and the subsequent Stat or EvalSymlinks followed the link in.
ResolvesIntoProtectedUserDataPath resolves one component at a time,
checking each candidate lexically before touching it with Lstat, so
answering the question never enters a protected location. Unresolvable
links count as protected; home is also compared in symlink-resolved
form so a home behind a linked ancestor still matches.
@roborev-ci

roborev-ci Bot commented Aug 8, 2026

Copy link
Copy Markdown

roborev: Combined Review (28e2256)

One medium-severity issue remains in the macOS path-probing safeguards.

Medium

  • internal/sync/engine.go:12528mayProbeLocalPath runs before the existing automount safeguards and invokes a resolver that calls Lstat on every path component. Paths under /home, /net, or /Network/Servers can wake automountd on every identity-cache miss, reintroducing the CPU storm these safeguards were designed to prevent.
    • Fix: Reject IsAutomountNamespacePath paths before calling ResolvesIntoProtectedUserDataPath, regardless of the protected-path opt-in.

Reviewers: 2 done | Synthesis: codex, 10s | Total: 4m32s

The symlink-aware protected-path resolver Lstats each path component,
and in the identity-cache gate it runs before the automount rejections
inside NormalizeRootPath and discoverLocalGitIdentity. A locally
attributed session with a /home/... cwd would therefore wake automountd
on every one-minute cache expiry, the CPU storm those rejections exist
to prevent.

The resolver now refuses automounter namespaces at every resolution
step, so both a literal /home/... input and a symlink hopping into the
namespace mid-walk stop before any Lstat. Automount paths are reported
unprotected: nothing there is user data, and downstream identity capture
already rejects them itself.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (b158fe3)

Review verdict: Three medium-severity issues remain in protected-path handling and opt-in refresh behavior.

Medium

  • internal/export/project_identity.go:912 — Automount namespaces return false, which callers interpret as permission to probe. A cwd symlinked into /home, /net, or /Network/Servers may therefore be followed by Stat/EvalSymlinks, while scan_protected_paths = true bypasses the resolver entirely, potentially recreating the automountd CPU storm. Represent automount paths as independently unsafe and block them regardless of the protected-path opt-in, ideally using a shared tri-state or safe-probe predicate.

  • internal/parser/project.go:170, internal/sync/engine.go:12527 — The policy validates only the cwd. An unprotected linked worktree can contain a .git file targeting a protected directory, after which Git metadata readers access that location and may trigger a consent prompt. Apply the protected-path policy to every resolved gitdir and commondir before statting or reading it, and abort Git discovery when either target is protected.

  • internal/sync/engine.go:566 — Enabling scan_protected_paths neither invalidates persisted skip state nor requeues the completed identity backfill. Previously ingested, unchanged sessions can consequently remain path-only rather than regaining full identity on the next sync. Persist policy state and refresh affected sessions when it changes, or explicitly require and initiate a full resync.


Reviewers: 2 done | Synthesis: codex, 15s | Total: 6m51s

Review of the previous commits found the protected-path policy compressed
two distinct hazards into one boolean and vetted only the cwd.

The resolver now classifies a path as safe, protected user data, or
automounter namespace. The automount class stays refused under the
scan_protected_paths opt-in — consenting to consent prompts is not
consenting to waking automountd — and a symlink hopping into /home is
refused where the lexical checks cannot see it. The parser guard keeps
one nuance: literal automount cwds already pass isForeignOSPath's
resolved-autofs probe before the guard runs, so only symlink-discovered
namespace paths are refused there.

Git discovery also vets what gitfile contents point at. A linked
worktree in an unguarded directory can name a gitdir or common directory
inside a protected folder; identity capture and the parser previously
read commondir, config, or HEAD there, and the parser could escalate to
exec git against the same target. Both now abort at the worktree with
path-only results when a target is refused.

Enabling scan_protected_paths applies to sessions parsed afterward;
docs now state that agentsview sync --full reparses existing sessions.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (83de48b)

Medium-severity issues remain in the filesystem-probing safeguards.

Medium

  • internal/parser/project.go:59defaultProbeGitRootForCwd permits lexical automount paths. Although valid for a cwd vetted by isForeignOSPath, gitFileTargetsProbeable also applies it to unvetted gitfile targets. Targets under /home or /net can therefore reach readCommonDir and repeatedly wake automountd.

    Fix: Use a stricter gitfile-target predicate that rejects LocalPathProbeAutomountNamespace, or perform the first-level autofs probe before allowing the target. Add coverage for an automount gitfile target.

  • internal/parser/project.go:599; internal/sync/engine.go:12741 — Both Git-root walkers call os.Stat on .git before classifying that exact path. A safe cwd with .git symlinked into ~/Documents can still trigger a protected-folder access prompt. The sync path likewise validates parent directories instead of the exact config, HEAD, and commondir paths before reading them.

    Fix: Classify each exact .git and metadata path before any stat or read, passing the probe callback into the root walker and checkout/config helpers. Add coverage for symlinked .git entries and metadata-file targets.


Reviewers: 2 done | Synthesis: codex, 14s | Total: 7m48s

Review of the previous commit found two remaining probe leaks.

First, gitFileTargetsProbeable reused the cwd guard, whose automount
allowance exists because isForeignOSPath vets literal cwds with the
resolved-autofs probe before the guard runs. Gitfile targets never get
that vetting, so a gitdir under /home reached readCommonDir and woke
automountd. Targets now use their own guard that refuses automount
namespaces outright.

Second, both discovery paths vetted directories but statted and read
the .git entry itself before classifying it. A .git symlink into a
protected folder — a real pre-gitfile redirection pattern — was
followed by the type probe, and the engine then read HEAD and config
through it. Both paths now vet the exact .git path first;
classification follows links, so the symlink case is refused without
touching the target.

Ancestor stats in the git-root walkers stay unvetted by choice: every
read is now behind a vet, per-level classification would cost a
quadratic Lstat walk on the hot parse path, and stat-only metadata
access is not an established TCC trigger.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (d4b5f9a)

Medium-severity path-probing gaps remain in the macOS protected-folder safeguards.

Medium

  • internal/parser/project.go:633, internal/sync/engine.go:12763 — The .git guards execute only after root walkers call os.Stat, which follows symlinks. A .git symlink into a protected folder can therefore trigger access and a consent prompt before rejection. Vet candidates before any following stat/read, or use Lstat to classify symlinks first and pass the probe policy into findLocalGitRoot.

  • internal/parser/project.go:719 — Missing-CWD recovery via repoRootFromSiblings directly reads sibling gitfiles and their commondir targets without applying gitFileTargetsProbeable. Reuse the guarded helper so .git, gitdir, and commondir targets are checked before access.

  • internal/export/project_identity.go:900ClassifyLocalPathProbe calls filepath.EvalSymlinks(home) before checking automount namespaces. Homes under /home or symlinked through /net can wake automountd on every call, recreating the CPU-storm risk. Check automount-backed paths component-by-component before resolving symlinks, and cache the safely resolved home.


Reviewers: 2 done | Synthesis: codex, 15s | Total: 9m43s

Review of the previous commit found three remaining probe paths, and
Windows CI failed on tests that drive the darwin classifier with POSIX
fixtures.

Missing-cwd sibling recovery read sibling gitfiles and their commondir
targets without the gitfile-target vetting the upward walk applies, and
verified deleted worktrees by listing a .git/worktrees directory derived
from those targets. Sibling .git entries now go through the same
Lstat-first typing and target vetting, so a refused sibling is skipped
instead of recovering the protected main repository's name.

Classification resolved the home directory with EvalSymlinks before any
automount check, so a home under /home, or linked through /net, woke
automountd on every call. Home resolution now walks component-by-
component, aborts on any automounter candidate, and is memoized per
process since home never changes.

Both git-root walkers statted .git entries with a following stat before
any vet. They now Lstat first and follow only symlinks whose target
passes the guard; a refused link marks a repo boundary without a
conservative result, so the parser cannot escalate to exec git against
the same target.

The four tests that exercise the darwin classifier's component walk
with symlinks or literal /home paths now skip on Windows, where those
fixtures are not absolute paths; production Windows behavior is
unchanged because the classifier is inert off darwin.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (1135e4a)

Code review found two medium-severity gaps in protected-folder gating.

Medium

  • internal/parser/project.go:711 — Missing-CWD recovery calls osStat on dir/.git before the guarded sibling scan. A .git symlink targeting Documents or cloud storage can still trigger a protected-folder prompt. Use statGitEntry, treat a refused target as a repository boundary without following it, and add coverage for a deleted CWD whose existing ancestor has a protected .git symlink.

  • internal/sync/engine.go:12803, internal/parser/project.go:931 — Although the Git directory is vetted, reads of commondir, HEAD, and config may follow individual file symlinks into protected folders. Classify each metadata-file path before opening it and thread the probe callback into the relevant readers.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 8m16s

The lint CI job failed on nilaway: findLocalGitRoot mixed Lstat and
Stat results in one flow, and statGitEntry could return a nil info with
a nil error. The engine walker now types the entry in a helper where
every dereference sits under its own error check, and statGitEntry
signals a refused symlink with a sentinel error so info is non-nil
exactly when err is nil. This worktree also had no prek hooks
installed, which is how the failing commit got pushed; hooks are now
installed so lint gates commits again.

Review of the previous commit found two remaining gaps, both fixed.
The ancestor boundary check in missing-cwd sibling recovery statted
dir/.git with a following stat before any vet; it now types the entry
through statGitEntry so a refused symlink counts as a boundary without
being followed. And the exact metadata-file paths - HEAD, config, and
commondir - are now vetted before reading: they sit inside vetted
directories, but as symlinks they can lead into a protected folder,
and reading through one would raise the prompt every directory-level
vet already prevented.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (bb5addf)

Medium-severity path-probing issue remains despite otherwise improved macOS protected-path gating.

Medium

  • internal/parser/project.go:742 — Missing-CWD recovery stats .git beneath every sibling before applying the protected-path policy. If the first existing ancestor is the user’s home directory, this can probe paths such as ~/Documents/.git and trigger the consent prompts the change is intended to prevent.

    Suggested fix: Vet each sibling gitPath with probeGitfileTarget before calling statGitEntry, and add coverage for recovery from a deleted direct child of the home directory.


Reviewers: 2 done | Synthesis: codex, 10s | Total: 10m48s

Review of the previous commit found that missing-cwd sibling recovery
typed each sibling's .git entry before any vet. When the first existing
ancestor is the home directory, the siblings include Documents and the
other guarded folders, so typing them Lstats inside a guarded folder -
and a guarded sibling holding a real .git directory flowed into
deletedChildIsWorktree, whose ReadDir of the worktrees list is exactly
the enumeration macOS gates behind a consent prompt.

Each sibling's .git path is now vetted before statGitEntry touches it.
For guarded siblings the lexical check answers without any filesystem
access, so recovery from a deleted direct child of home skips Documents
entirely instead of probing it.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (63ea2d7)

Medium-severity path-classification gap remains.

Medium

  • internal/export/project_identity.go:923 — Protected-path classification misses macOS’s /System/Volumes/Data APFS firmlink namespace. Paths such as /System/Volumes/Data/Users/me/Documents/repo can reach the same TCC-protected location as ~/Documents but are classified as safe; equivalent automount paths are also missed. Lexically canonicalize the /System/Volumes/Data prefix before protected-directory and automount checks, without probing the path, and add coverage for the physical namespace.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 12m7s

Review found that classification missed the physical spelling of user
data paths. Since Catalina the writable system firmlinks user data
under /System/Volumes/Data, so /System/Volumes/Data/Users/me/Documents
is the same TCC-protected folder as ~/Documents and
/System/Volumes/Data/home is the autofs home map. Firmlinks are not
symlinks - Lstat reports a plain directory - so the classifier's link
walk could not equate the two spellings and classified physical paths
as safe.

Both predicates now strip the data-volume prefix lexically before
comparing, on the path and on home, without touching the filesystem.
Every caller inherits the fix: classification entry checks, per-
candidate walk checks, automount-safe home resolution, and the
engine's pre-existing automount rejections.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (4574e4f)

Medium-severity path-resolution and case-sensitivity issues remain in the macOS filesystem guards.

Medium

  • internal/export/project_identity.go:1044 (same issue at line 985): Symlink targets rebuilt with filepath.Join normalize .. before earlier symlinks are resolved. A chained relative symlink can therefore be classified as safe while filesystem resolution reaches a protected or automount path.

    • Fix: Resolve targets component by component, preserving filesystem traversal order and checking each resolved component before access. Add tests for chained relative symlinks containing ...
  • internal/export/project_identity.go:802: APFS data-volume prefix and automount namespace checks are case-sensitive, despite typical macOS filesystems being case-insensitive. Mixed-case paths can bypass classification and trigger protected-folder or automount probes.

    • Fix: Use case-insensitive, component-boundary comparisons while preserving the original suffix. Add mixed-case path coverage.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 9m21s

Review found two gaps in the probe classifier.

Symlink splices and the input splitter collapsed ".." lexically via
filepath.Join and filepath.Clean, before earlier components were
resolved. The kernel resolves components in order, so home/q/../x with
q linked into Documents classified as home/x while real resolution
reaches Documents/x; a chained relative target like "l2/../safe" hid
the same divergence. The walks now process raw components and resolve
".." as Dir(current) - correct because current never contains a
symlink - and splices concatenate without cleaning so the restarted
walk sees dotdot in traversal order. A dotdot path through a guarded
midpoint now classifies as that midpoint, which is the conservative
side of the traversal question.

The automount namespace check and the APFS data-volume prefix trim
compared case-sensitively, but the startup volume is case-insensitive
by default, so /HOME/x or /system/volumes/data spellings bypassed
them. Both now fold case; the protected-folder predicate already did.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (ec28805)

The protected-path hardening is incomplete: two medium-severity paths can still access protected storage.

Medium

  • internal/parser/project.go:851 — Missing-CWD sibling recovery vets <repo>/.git, but deletedChildIsWorktree then calls os.ReadDir on <repo>/.git/worktrees. If worktrees is a symlink into Documents or CloudStorage, discovery accesses the protected target despite scan_protected_paths = false. Check the exact wtDir with probeGitfileTarget before reading it, and add coverage for a safe repository whose worktrees directory links into protected storage.

  • internal/sync/engine.go:11287 — The probe gate checks only the current session CWD. When that CWD is unavailable, pathContains unconditionally calls filepath.EvalSymlinks on the durable snapshot’s root. A stale snapshot may reference a protected root, causing reparsing to trigger a consent prompt. Apply e.mayProbeLocalPath to snapshot.RootPath before filesystem-based containment resolution, then fall back to lexical comparison or skip the snapshot when probing is disallowed.


Reviewers: 2 done | Synthesis: codex, 15s | Total: 10m41s

Review found two reads that could still enter guarded storage.

Deleted-worktree verification vets the sibling's .git entry, but the
worktrees directory inside a real .git can itself be a symlink, and
ReadDir through it is exactly the enumeration macOS gates behind a
consent prompt. The exact worktrees path is now vetted before
enumerating, matching the policy every other metadata read follows.

Source-project reconciliation gates the session cwd, but when the cwd
is unavailable it resolved the durable snapshot's root with
EvalSymlinks unconditionally. Snapshot roots are stored data that can
predate protected-path gating and name a guarded folder, so a refused
root now falls back to lexical containment instead of filesystem
resolution. EvalSymlinks in prefix resolution moved behind a seam so
the test can pin that a guarded root is never walked.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (a325f92)

The changes improve macOS protected-path handling, but two medium-severity gaps can still bypass the intended safeguards.

Medium

  • internal/export/project_identity.go:1040 — Classification stops when a path is lexically protected, so it can miss a later symlink into /home, /net, or /Network/Servers. With scan_protected_paths = true, parser and sync callers may probe an automount namespace despite the invariant that opt-in never permits automount probing. Continue resolving path components through protected prefixes while still rejecting automount targets, and add coverage for a symlink from ~/Documents into an automount namespace.

  • internal/parser/project.go:941 — A gitfile target without commondir, as used by submodules, is considered probeable without vetting gitDir/config. The subsequent gitMainRoot call reads repository configuration, allowing a config symlink into a protected folder to bypass the guard and trigger a consent prompt. When commondir is absent, treat gitDir as the effective common directory and vet its config path before permitting the Git fallback.


Reviewers: 2 done | Synthesis: codex, 9s | Total: 9m1s

Review found two remaining bypasses.

Classification stopped at the first protected candidate, so a symlink
inside ~/Documents leading into /home classified as protected, which
the scan_protected_paths opt-in maps to probeable - violating the
invariant that the opt-in lifts consent prompts, never automountd
wakeups. The classifier now takes the caller's opt-in: with it set,
the walk keeps resolving through protected prefixes (Lstat there is
what the caller is about to do anyway) and still refuses automount
targets; without it, the walk stops at the protected prefix untouched
as before.

A gitfile target without a commondir - the submodule layout - was
considered probeable without vetting the gitdir's own config, and the
conservative result escalates to gitMainRoot, whose git exec reads
config and HEAD. Both are now vetted exactly when commondir is absent,
treating the gitdir as the effective common directory.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (c2d5e8f)

Overall verdict: One medium-severity path-normalization issue remains in macOS protected-path handling.

Medium

  • internal/parser/project.go:120 — The guard assumes every path recognized as an automount namespace was already vetted by isForeignOSPath, but that function matches canonical prefixes case-sensitively. Alternate forms now recognized by IsAutomountNamespacePath, such as /System/Volumes/Data/home/... and /HOME/..., can bypass the probe and enter the Git walk, potentially waking automountd repeatedly.
    • Fix: Canonicalize paths consistently before prefix matching, or require explicit proof that the autofs probe succeeded before allowing an automount-classified working directory. Add coverage for data-volume and case-folded spellings.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 7m51s

Review found that the cwd guard's automount allowance drifted out from
under its justification. The allowance defers to isForeignOSPath, whose
resolved-autofs probe matches the mount table's canonical prefixes
case-sensitively with no data-volume trimming - but the allowance
tested the broad classifier predicate, which since gained the
/System/Volumes/Data spelling and case folding. Those alternate
spellings bypassed the autofs probe entirely yet inherited its
clearance, letting the git walk stat them and wake automountd.

The allowance now uses IsCanonicalAutomountNamespacePath, which
accepts only the exact spellings isForeignOSPath examines; alternate
spellings and symlink-smuggled paths stay refused.
@roborev-ci

roborev-ci Bot commented Aug 9, 2026

Copy link
Copy Markdown

roborev: Combined Review (a4f6d79)

Medium-severity path-validation issue found; no Critical or High findings.

Medium

  • internal/parser/project.go:119 — Canonically spelled automount paths are allowed solely based on spelling, bypassing protected-folder gating for network homes such as /home/user/Documents. Exact namespace roots such as /home also avoid the assumed first-level autofs vetting.
    • Fix: Require an explicit successful autofs-vetting result, reject namespace roots, and independently enforce protected-home checks before allowing the Git walk. Add coverage for autofs-backed protected children and exact namespace roots.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 8m29s

Review found the automount allowance still trusted spelling over
evidence. A canonically spelled cwd was admitted on the assumption
that isForeignOSPath's probe had vetted it, but an exact namespace
root like /home matches no trailing-separator prefix and was never
probed, and a network home under /home carries the user's own guarded
folders - Documents inside an autofs home bypassed protected-folder
gating entirely because automount classification wins before the
protected check.

The allowance is now automountCwdProbeAllowed, which requires
everything explicitly: canonical spelling, a path outside the network
home's guarded folders unless scan_protected_paths is set, a non-root
path, and - for autofs-managed prefixes - a first-level probe that
actually resolved, consulted directly through the memoized probe cache
shared with isForeignOSPath instead of assumed from call ordering.
Namespaces not in the mount table stay probeable: there is no
automountd behind them to wake.
@roborev-ci

roborev-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (f568cc8)

Medium-severity issue remains: custom autofs mounts can still be probed despite the new protected-path handling.

Medium

  • internal/parser/project.go:82 — The probe classifier recognizes only /home, /net, and /Network/Servers, while the parser discovers arbitrary autofs mounts such as /corp/home. A symlinked cwd, sibling, or gitfile target entering a custom mount can bypass isForeignOSPath, causing Lstat calls inside the mount and potentially recreating automountd CPU/wakeup issues.
    • Fix: Incorporate all detected autofsPrefixes into component-wise classification, rejecting unvetted symlink and gitfile targets while retaining the existing first-level resolution check for direct working directories. Add coverage for a custom autofs prefix.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 14m17s

Review found the classifier only knew the fixed namespaces (/home,
/net, /Network/Servers) while the parser discovers arbitrary autofs
mounts such as /corp/home from the live mount table. A symlinked cwd,
sibling, or gitfile target landing in a custom mount classified as
safe, so Lstat walked inside it - the automountd wakeup the fixed
namespaces already prevent. The engine's automount rejections had the
same fixed-list gap.

The parser now registers discovered prefixes with the classifier at
detection time, since only the parser can discover them. Both
predicates consult the registered set: the broad one with case folding
and data-volume trimming, the canonical one against the mount table's
exact form. Clearance for direct working directories is unchanged -
automountCwdProbeAllowed already iterates the parser's prefix list, so
a resolving first-level entry in a custom mount stays probeable and
the mount root stays refused.
@roborev-ci

roborev-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (abeb2c0)

Medium-severity issue found: passive project discovery can still access unvetted paths through Git configuration.

Medium

  • internal/parser/project.go:679 — The conservative gitfile path still invokes gitMainRoot, allowing Git to follow unvetted configuration paths such as [include] path or core.worktree, potentially entering protected folders and triggering macOS consent prompts even when the session CWD is safe. Avoid spawning Git during passive project discovery, or use a resolver that validates every content-derived path before access.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 16m32s

Review found the last unvettable access path: the conservative gitfile
fallback escalated to gitMainRoot, which execs git, and git follows
config-derived paths - [include] path, includeIf, core.worktree - into
locations no probe policy examined. That cannot be closed by vetting
short of reimplementing git's config resolution, so passive discovery
no longer runs the git binary at all.

The pure-file logic already resolves every real layout: linked
worktrees via commondir, bare-backed worktrees via core.bare, and the
worktrees marker fallback. What the exec uniquely rescued - virtual
repos via GIT_DIR environment setups and external gitdirs without
commondir - now falls back to the directory basename, which is the
path-only naming the protected-path work already established for
refused locations.

The two tests that pinned the git fallback now pin its absence: the
shim would resolve the repository, so a reintroduced exec is caught by
both the extracted name and the invocation log. The context parameter
of ExtractProjectFromCwdWithBranchContext is retained for
compatibility but no longer used.
@roborev-ci

roborev-ci Bot commented Aug 10, 2026

Copy link
Copy Markdown

roborev: Combined Review (8c19569)

No issues found.


Reviewers: 2 done | Synthesis: codex | Total: 9m5s

@wesm
wesm merged commit 001dd6b into main Aug 10, 2026
21 checks passed
@wesm
wesm deleted the kenn-forge/issue-1364-macos-app-requests-access-to-documents-downloads-and-dropbox branch August 10, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

macos app requests access to Documents, Downloads and Dropbox

1 participant