Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/opencode/src/file/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Ripgrep } from "./ripgrep"
import fuzzysort from "fuzzysort"
import { Global } from "../global"
import { git } from "@/util/git"
import { Protected } from "./protected"

export namespace File {
const log = Log.create({ service: "file" })
Expand Down Expand Up @@ -345,10 +346,7 @@ export namespace File {

if (isGlobalHome) {
const dirs = new Set<string>()
const ignore = new Set<string>()

if (process.platform === "darwin") ignore.add("Library")
if (process.platform === "win32") ignore.add("AppData")
const ignore = Protected.names()

const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
const shouldIgnore = (name: string) => name.startsWith(".") || ignore.has(name)
Expand Down Expand Up @@ -587,6 +585,9 @@ export namespace File {
if (!Instance.containsPath(resolved)) {
throw new Error(`Access denied: path escapes project directory`)
}
if (Protected.contains(resolved)) {
throw new Error(`Access denied: path is a system-protected directory`)
}

const nodes: Node[] = []
for (const entry of await fs.promises
Expand Down
68 changes: 68 additions & 0 deletions packages/opencode/src/file/protected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import path from "path"
import os from "os"

const home = os.homedir()

// macOS directories that trigger TCC (Transparency, Consent, and Control)
// permission prompts when accessed by a non-sandboxed process.
const DARWIN_NAMES = [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This name could be a little more descriptive, something like DARWIN_HOME or something but not a big deal

// Media
"Music",
"Pictures",
"Movies",
// User-managed folders synced via iCloud / subject to TCC
"Downloads",
"Desktop",
"Documents",
// Other system-managed
"Public",
"Applications",
"Library",
]

const DARWIN_LIBRARY = [
"Application Support/AddressBook",
"Calendars",
"Mail",
"Messages",
"Safari",
"Cookies",
"Application Support/com.apple.TCC",
"PersonalizationPortrait",
"Metadata/CoreSpotlight",
"Suggestions",
]

const DARWIN_ROOT = ["/.DocumentRevisions-V100", "/.Spotlight-V100", "/.Trashes", "/.fseventsd"]

const WIN32_NAMES = ["AppData", "Downloads", "Desktop", "Documents", "Pictures", "Music", "Videos", "OneDrive"]

export namespace Protected {
/** Directory basenames to skip when scanning the home directory. */
export function names(): ReadonlySet<string> {
if (process.platform === "darwin") return new Set(DARWIN_NAMES)
if (process.platform === "win32") return new Set(WIN32_NAMES)
return new Set()
}

/** Absolute paths that should never be watched, stated, or scanned. */
export function paths(): string[] {
if (process.platform === "darwin")
return [
...DARWIN_NAMES.map((n) => path.join(home, n)),
...DARWIN_LIBRARY.map((n) => path.join(home, "Library", n)),
...DARWIN_ROOT,
]
if (process.platform === "win32") return WIN32_NAMES.map((n) => path.join(home, n))
return []
}

/** Returns true if `target` is inside (or equal to) any protected path. */
export function contains(target: string): boolean {
const resolved = path.resolve(target)
for (const p of paths()) {
if (resolved === p || resolved.startsWith(p + path.sep)) return true
}
return false
}
}
3 changes: 2 additions & 1 deletion packages/opencode/src/file/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type ParcelWatcher from "@parcel/watcher"
import { Flag } from "@/flag/flag"
import { readdir } from "fs/promises"
import { git } from "@/util/git"
import { Protected } from "./protected"

const SUBSCRIBE_TIMEOUT_MS = 10_000

Expand Down Expand Up @@ -76,7 +77,7 @@ export namespace FileWatcher {

if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
const pending = w.subscribe(Instance.directory, subscribe, {
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores, ...Protected.paths()],
backend,
})
const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => {
Expand Down
Loading