summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-10 11:32:05 -0500
committerGitHub <[email protected]>2026-03-10 11:32:05 -0500
commit9c4325bcf8070d84a6911ae78b898c116ebad2ac (patch)
tree3b655d28a7e8b9f69c0a860486ea7aabdce3909b
parentad08fd57df450d4371eddfdf8a693ff378c2259c (diff)
downloadopencode-9c4325bcf8070d84a6911ae78b898c116ebad2ac.tar.gz
opencode-9c4325bcf8070d84a6911ae78b898c116ebad2ac.zip
fix(core): don't permit access to system directories (#16891)
-rw-r--r--packages/opencode/src/file/index.ts6
-rw-r--r--packages/opencode/src/file/protected.ts59
-rw-r--r--packages/opencode/src/file/watcher.ts3
3 files changed, 63 insertions, 5 deletions
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 7a51ca36c..e03fc8a9f 100644
--- a/packages/opencode/src/file/index.ts
+++ b/packages/opencode/src/file/index.ts
@@ -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" })
@@ -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)
diff --git a/packages/opencode/src/file/protected.ts b/packages/opencode/src/file/protected.ts
new file mode 100644
index 000000000..d51974619
--- /dev/null
+++ b/packages/opencode/src/file/protected.ts
@@ -0,0 +1,59 @@
+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_HOME = [
+ // 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_HOME = ["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_HOME)
+ if (process.platform === "win32") return new Set(WIN32_HOME)
+ return new Set()
+ }
+
+ /** Absolute paths that should never be watched, stated, or scanned. */
+ export function paths(): string[] {
+ if (process.platform === "darwin")
+ return [
+ ...DARWIN_HOME.map((n) => path.join(home, n)),
+ ...DARWIN_LIBRARY.map((n) => path.join(home, "Library", n)),
+ ...DARWIN_ROOT,
+ ]
+ if (process.platform === "win32") return WIN32_HOME.map((n) => path.join(home, n))
+ return []
+ }
+}
diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts
index 537f52646..3797c1627 100644
--- a/packages/opencode/src/file/watcher.ts
+++ b/packages/opencode/src/file/watcher.ts
@@ -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
@@ -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) => {