bx-mac 0.11.0 โ 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -18
- package/dist/bx.js +97 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ AI-powered coding tools like Claude Code, Copilot, or Cline run with **broad fil
|
|
|
17
17
|
bx ~/work/my-project
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
That's it. ๐ VSCode opens with full access to `~/work/my-project` and nothing else.
|
|
20
|
+
That's it. ๐ VSCode opens with full access to `~/work/my-project` and nothing else. Read [the blog post](https://holtwick.de/blog/bx-sandbox) for more background on the motivation behind bx.
|
|
21
21
|
|
|
22
22
|
Need multiple directories? No problem:
|
|
23
23
|
|
|
@@ -30,6 +30,7 @@ bx ~/work/my-project ~/work/shared-lib
|
|
|
30
30
|
- ๐ Blocks `~/Documents`, `~/Desktop`, `~/Downloads`, and all other personal folders
|
|
31
31
|
- ๐ง Blocks sibling projects โ only the directory you specify is accessible
|
|
32
32
|
- ๐ก๏ธ Protects sensitive dotdirs like `~/.ssh`, `~/.gnupg`, `~/.docker`, `~/.cargo`
|
|
33
|
+
- ๐๏ธ Opinionated protection for `~/Library` โ blocks privacy-sensitive subdirectories (Mail, Messages, Photos, Safari, Contacts, โฆ) and containers of password managers/finance apps, while keeping tooling-relevant paths accessible
|
|
33
34
|
- โ๏ธ Keeps VSCode, extensions, shell, Node.js, and other tooling fully functional
|
|
34
35
|
- ๐ Generates sandbox rules dynamically based on your actual `$HOME` contents
|
|
35
36
|
- ๐ Supports `.bxignore` files (searched recursively) to hide secrets like `.env` files within a project
|
|
@@ -81,7 +82,7 @@ For app modes, values before `--` define the sandbox scope (`workdir...`). Value
|
|
|
81
82
|
|
|
82
83
|
For `xcode`, this distinction is important: the sandbox workdir is **not** passed as an Xcode open argument. Use `--` if you want to open a specific `.xcworkspace` or `.xcodeproj`.
|
|
83
84
|
|
|
84
|
-
This behavior is configurable per app via `
|
|
85
|
+
This behavior is configurable per app via `passPaths` in `~/.bxconfig.toml` (default: `true`, built-in `xcode` default: `false`).
|
|
85
86
|
|
|
86
87
|
GUI app modes are activated in the foreground on launch (best effort), so the opened app should become the frontmost app.
|
|
87
88
|
|
|
@@ -113,6 +114,9 @@ bx zed ~/work/my-project
|
|
|
113
114
|
# โก Run a script in a sandbox
|
|
114
115
|
bx exec ~/work/my-project -- python train.py
|
|
115
116
|
|
|
117
|
+
# ๐ Run in the background (terminal stays free)
|
|
118
|
+
bx --background code ~/work/my-project
|
|
119
|
+
|
|
116
120
|
# ๐ Preview what will be protected (no launch)
|
|
117
121
|
bx --dry ~/work/my-project
|
|
118
122
|
|
|
@@ -126,6 +130,7 @@ bx --verbose ~/work/my-project
|
|
|
126
130
|
| --- | --- |
|
|
127
131
|
| `--dry` | Show a tree of all protected, read-only, and accessible paths โ don't launch anything |
|
|
128
132
|
| `--verbose` | Print the generated sandbox profile plus launch details (binary, arguments, cwd, focus command) |
|
|
133
|
+
| `--background` | Run the app detached in the background (like `nohup &`), output goes to `/tmp/bx-<pid>.log` |
|
|
129
134
|
| `--profile-sandbox` | Use an isolated VSCode profile (separate extensions/settings, `code` mode only) |
|
|
130
135
|
|
|
131
136
|
On normal runs, bx also prints a short policy summary (number of workdirs, blocked directories, hidden paths, and read-only directories).
|
|
@@ -154,43 +159,44 @@ path = "/usr/local/bin/code"
|
|
|
154
159
|
|
|
155
160
|
| Field | Description |
|
|
156
161
|
| --- | --- |
|
|
157
|
-
| `mode` | Inherit from another app (e.g. `"code"`, `"cursor"`) โ only `
|
|
162
|
+
| `mode` | Inherit from another app (e.g. `"code"`, `"cursor"`) โ only `paths` / overrides needed |
|
|
158
163
|
| `bundle` | macOS bundle identifier โ used with `mdfind` to find the app automatically |
|
|
159
164
|
| `binary` | Relative path to the executable inside the `.app` bundle |
|
|
160
165
|
| `path` | Absolute path to the executable **or** `.app` bundle (highest priority, skips discovery) |
|
|
161
166
|
| `fallback` | Absolute fallback path if `mdfind` discovery fails |
|
|
162
167
|
| `args` | Extra arguments always passed to the app |
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
168
|
+
| `passPaths` | Paths passed as app launch args (`true`/`false`/`N`/`["~/p1", "~/p2"]`) |
|
|
169
|
+
| `paths` | Default working directories when none are given on the CLI (supports `~/` paths) |
|
|
170
|
+
| `background` | Run the app detached in the background by default (`true`/`false`) |
|
|
165
171
|
|
|
166
172
|
**Resolution order:** `path` โ `mdfind` by `bundle` + `binary` โ `fallback`
|
|
167
173
|
|
|
168
|
-
`
|
|
174
|
+
`passPaths` controls launch argument behavior and is independent of sandbox scope. Even with `passPaths = false`, the provided `workdir...` still defines what the sandbox can access. Use `passPaths = 1` to pass only the first path as a launch argument, or `passPaths = ["~/specific/path"]` to pass explicit paths instead of workdirs.
|
|
169
175
|
|
|
170
|
-
**Workdir shortcuts with `mode`** let you create named entries that inherit everything from an existing app โ just set `mode` and `
|
|
176
|
+
**Workdir shortcuts with `mode`** let you create named entries that inherit everything from an existing app โ just set `mode` and `paths`:
|
|
171
177
|
|
|
172
178
|
```toml
|
|
173
179
|
# "bx myproject" opens VSCode with these directories
|
|
174
180
|
[myproject]
|
|
175
181
|
mode = "code"
|
|
176
|
-
|
|
182
|
+
paths = ["~/work/my-project", "~/work/shared-lib"]
|
|
177
183
|
|
|
178
184
|
# "bx ios" opens Xcode with this directory
|
|
179
185
|
[ios]
|
|
180
186
|
mode = "xcode"
|
|
181
|
-
|
|
187
|
+
paths = ["~/work/my-ios-app"]
|
|
182
188
|
```
|
|
183
189
|
|
|
184
190
|
Running `bx myproject` inherits VSCode's bundle, binary, args, and everything else โ no need to repeat the full app configuration. Own fields override inherited ones, so you can still customize specific settings. Chaining is supported (e.g. `myproject` โ `cursor` โ `code`).
|
|
185
191
|
|
|
186
|
-
**Preconfigured
|
|
192
|
+
**Preconfigured paths** also work directly on app definitions:
|
|
187
193
|
|
|
188
194
|
```toml
|
|
189
195
|
[code]
|
|
190
|
-
|
|
196
|
+
paths = ["~/work/my-project", "~/work/shared-lib"]
|
|
191
197
|
```
|
|
192
198
|
|
|
193
|
-
Running `bx code` (without arguments) will then open VSCode with both directories sandboxed. CLI arguments always override configured
|
|
199
|
+
Running `bx code` (without arguments) will then open VSCode with both directories sandboxed. CLI arguments always override configured paths.
|
|
194
200
|
|
|
195
201
|
When overriding a built-in app, only the specified fields are replaced โ unset fields keep their defaults. See [`bxconfig.example.toml`](bxconfig.example.toml) for a complete reference.
|
|
196
202
|
|
|
@@ -216,9 +222,11 @@ ro:reference/docs
|
|
|
216
222
|
ro:shared/toolchain
|
|
217
223
|
```
|
|
218
224
|
|
|
219
|
-
Deny rules are applied **in addition** to the built-in protected
|
|
225
|
+
Deny rules are applied **in addition** to the built-in protected lists:
|
|
220
226
|
|
|
221
|
-
> ๐ `.ssh` `.gnupg` `.docker` `.zsh_sessions` `.cargo` `.gradle` `.gem`
|
|
227
|
+
> ๐ **Dotdirs:** `.ssh` `.gnupg` `.docker` `.zsh_sessions` `.cargo` `.gradle` `.gem`
|
|
228
|
+
>
|
|
229
|
+
> ๐๏ธ **Library (opinionated):** `Accounts` `Calendars` `Contacts` `Cookies` `Finance` `Mail` `Messages` `Mobile Documents` `Photos` `Safari` and [others (see full list)](src/profile.ts) โ plus containers of password managers & finance apps
|
|
222
230
|
|
|
223
231
|
### `<project>/.bxignore`
|
|
224
232
|
|
|
@@ -288,9 +296,10 @@ bx generates a macOS sandbox profile at launch time:
|
|
|
288
296
|
2. **Block** each one individually with `(deny file* (subpath ...))`
|
|
289
297
|
3. **Skip** all working directories, `~/Library`, dotfiles, and `rw:`/`ro:` paths from `~/.bxignore`
|
|
290
298
|
4. **Descend** into parent directories of allowed paths to block only siblings (because SBPL deny rules always override allow rules)
|
|
291
|
-
5. **
|
|
292
|
-
6. **
|
|
293
|
-
7. **
|
|
299
|
+
5. **Protect** an opinionated set of `~/Library` subdirectories (Mail, Messages, Photos, Safari, Contacts, Calendars, โฆ) and app containers matching known password managers and finance apps (1Password, Bitwarden, MoneyMoney, โฆ)
|
|
300
|
+
6. **Append** deny rules for protected dotdirs, plain entries in `~/.bxignore`, and `.bxignore` files found recursively in each working directory
|
|
301
|
+
7. **Apply** `(deny file-write*)` rules for `ro:` directories (read allowed, write blocked)
|
|
302
|
+
8. **Write** the profile to `/tmp`, launch the app via `sandbox-exec`, clean up on exit
|
|
294
303
|
|
|
295
304
|
### Why not a simple deny-all + allow?
|
|
296
305
|
|
|
@@ -330,7 +339,7 @@ Or preconfigure them in `~/.bxconfig.toml`:
|
|
|
330
339
|
|
|
331
340
|
```toml
|
|
332
341
|
[code]
|
|
333
|
-
|
|
342
|
+
paths = ["~/work/project-a", "~/work/project-b"]
|
|
334
343
|
```
|
|
335
344
|
|
|
336
345
|
For VSCode specifically, `--profile-sandbox` forces a separate Electron process via an isolated `--user-data-dir`, but this means separate extensions and settings.
|
|
@@ -363,6 +372,10 @@ Some AI coding tools ship with their own sandboxing. bx complements these by pro
|
|
|
363
372
|
|
|
364
373
|
These are great when available, but they only protect within their own tool. bx wraps the entire process โ so even if a tool's built-in sandbox is misconfigured, disabled, or absent, your files stay protected.
|
|
365
374
|
|
|
375
|
+
## ๐ Alternatives
|
|
376
|
+
|
|
377
|
+
- [Agent Safehouse](https://agent-safehouse.dev/) โ macOS kernel-level sandboxing for LLM coding agents via `sandbox-exec`. Deny-first model that blocks write access outside the project directory.
|
|
378
|
+
|
|
366
379
|
## ๐ License
|
|
367
380
|
|
|
368
381
|
MIT โ see [LICENSE](LICENSE).
|
package/dist/bx.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { accessSync, constants, cpSync, existsSync, globSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { accessSync, constants, cpSync, existsSync, globSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { execFileSync, spawn } from "node:child_process";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
@@ -797,7 +797,7 @@ const BUILTIN_APPS = {
|
|
|
797
797
|
bundle: "com.apple.dt.Xcode",
|
|
798
798
|
binary: "Contents/MacOS/Xcode",
|
|
799
799
|
fallback: "/Applications/Xcode.app/Contents/MacOS/Xcode",
|
|
800
|
-
|
|
800
|
+
passPaths: false
|
|
801
801
|
}
|
|
802
802
|
};
|
|
803
803
|
/** Shell-only built-in modes that are not app definitions */
|
|
@@ -806,6 +806,20 @@ const BUILTIN_MODES = [
|
|
|
806
806
|
"claude",
|
|
807
807
|
"exec"
|
|
808
808
|
];
|
|
809
|
+
function parsePassPaths(val) {
|
|
810
|
+
if (typeof val === "boolean") return val;
|
|
811
|
+
if (typeof val === "number" && Number.isInteger(val) && val > 0) return val;
|
|
812
|
+
if (Array.isArray(val)) {
|
|
813
|
+
const paths = val.filter((a) => typeof a === "string");
|
|
814
|
+
if (paths.length > 0) return paths;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function parseStringArray(val) {
|
|
818
|
+
if (Array.isArray(val)) {
|
|
819
|
+
const items = val.filter((a) => typeof a === "string");
|
|
820
|
+
if (items.length > 0) return items;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
809
823
|
function parseAppDef(def) {
|
|
810
824
|
return {
|
|
811
825
|
mode: typeof def.mode === "string" ? def.mode : void 0,
|
|
@@ -813,9 +827,10 @@ function parseAppDef(def) {
|
|
|
813
827
|
binary: typeof def.binary === "string" ? def.binary : void 0,
|
|
814
828
|
path: typeof def.path === "string" ? def.path : void 0,
|
|
815
829
|
fallback: typeof def.fallback === "string" ? def.fallback : void 0,
|
|
816
|
-
args:
|
|
817
|
-
|
|
818
|
-
|
|
830
|
+
args: parseStringArray(def.args),
|
|
831
|
+
passPaths: parsePassPaths(def.passPaths ?? def.passWorkPaths ?? def.passWorkdirs),
|
|
832
|
+
paths: parseStringArray(def.paths ?? def.workdirs),
|
|
833
|
+
background: typeof def.background === "boolean" ? def.background : void 0
|
|
819
834
|
};
|
|
820
835
|
}
|
|
821
836
|
/**
|
|
@@ -835,8 +850,12 @@ function loadConfig(home) {
|
|
|
835
850
|
"path",
|
|
836
851
|
"fallback",
|
|
837
852
|
"args",
|
|
853
|
+
"passPaths",
|
|
854
|
+
"passWorkPaths",
|
|
838
855
|
"passWorkdirs",
|
|
839
|
-
"
|
|
856
|
+
"paths",
|
|
857
|
+
"workdirs",
|
|
858
|
+
"background"
|
|
840
859
|
]);
|
|
841
860
|
for (const [key, val] of Object.entries(doc)) {
|
|
842
861
|
if (key === "apps") continue;
|
|
@@ -1032,14 +1051,6 @@ const ACCESS_PREFIX_RE = /^(RW|RO):(.+)$/i;
|
|
|
1032
1051
|
function parseHomeConfig(home, workDirs) {
|
|
1033
1052
|
const allowed = new Set(workDirs);
|
|
1034
1053
|
const readOnly = /* @__PURE__ */ new Set();
|
|
1035
|
-
const bxallowPath = join(home, ".bxallow");
|
|
1036
|
-
if (existsSync(bxallowPath)) {
|
|
1037
|
-
console.error("sandbox: WARNING โ ~/.bxallow is deprecated. Move entries to ~/.bxignore with RW: prefix.");
|
|
1038
|
-
for (const line of parseLines(bxallowPath)) {
|
|
1039
|
-
const absolute = resolve(home, line);
|
|
1040
|
-
if (existsSync(absolute) && statSync(absolute).isDirectory()) allowed.add(absolute);
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
1054
|
for (const line of parseLines(join(home, ".bxignore"))) {
|
|
1044
1055
|
const match = line.match(ACCESS_PREFIX_RE);
|
|
1045
1056
|
if (!match) continue;
|
|
@@ -1265,6 +1276,7 @@ function parseArgs(validModes) {
|
|
|
1265
1276
|
const verbose = rawArgs.includes("--verbose");
|
|
1266
1277
|
const dry = rawArgs.includes("--dry");
|
|
1267
1278
|
const profileSandbox = rawArgs.includes("--profile-sandbox");
|
|
1279
|
+
const background = rawArgs.includes("--background");
|
|
1268
1280
|
const positional = rawArgs.filter((a) => !a.startsWith("--"));
|
|
1269
1281
|
const doubleDashIdx = rawArgs.indexOf("--");
|
|
1270
1282
|
const appArgs = doubleDashIdx >= 0 ? rawArgs.slice(doubleDashIdx + 1) : [];
|
|
@@ -1291,6 +1303,7 @@ function parseArgs(validModes) {
|
|
|
1291
1303
|
verbose,
|
|
1292
1304
|
dry,
|
|
1293
1305
|
profileSandbox,
|
|
1306
|
+
background,
|
|
1294
1307
|
appArgs,
|
|
1295
1308
|
implicit: implicitWorkdirs
|
|
1296
1309
|
};
|
|
@@ -1300,8 +1313,12 @@ function parseArgs(validModes) {
|
|
|
1300
1313
|
function isBuiltinMode(mode) {
|
|
1301
1314
|
return BUILTIN_MODES.includes(mode);
|
|
1302
1315
|
}
|
|
1303
|
-
function
|
|
1304
|
-
|
|
1316
|
+
function getPassPaths(app, workDirs, home) {
|
|
1317
|
+
const val = app.passPaths;
|
|
1318
|
+
if (val === false) return [];
|
|
1319
|
+
if (typeof val === "number") return workDirs.slice(0, val);
|
|
1320
|
+
if (Array.isArray(val)) return val.map((p) => p.replace(/^~\//, home + "/"));
|
|
1321
|
+
return workDirs;
|
|
1305
1322
|
}
|
|
1306
1323
|
function appBundleFromPath(path) {
|
|
1307
1324
|
if (path.endsWith(".app")) return path;
|
|
@@ -1372,7 +1389,7 @@ function buildAppCommand(mode, workDirs, home, profileSandbox, appArgs, apps) {
|
|
|
1372
1389
|
}
|
|
1373
1390
|
if (app.args) args.push(...app.args);
|
|
1374
1391
|
if (appArgs.length > 0) args.push(...appArgs);
|
|
1375
|
-
|
|
1392
|
+
args.push(...getPassPaths(app, workDirs, home));
|
|
1376
1393
|
return {
|
|
1377
1394
|
bin,
|
|
1378
1395
|
args
|
|
@@ -1462,6 +1479,7 @@ const OPTIONS_TEXT = `
|
|
|
1462
1479
|
Options:
|
|
1463
1480
|
--dry show what will be protected, don't launch
|
|
1464
1481
|
--verbose print the generated sandbox profile
|
|
1482
|
+
--background run in background, log output to /tmp/bx-<pid>.log
|
|
1465
1483
|
--profile-sandbox use an isolated VSCode profile (code mode only)
|
|
1466
1484
|
-v, --version show version
|
|
1467
1485
|
-h, --help show this help
|
|
@@ -1474,7 +1492,8 @@ Configuration:
|
|
|
1474
1492
|
binary = "..." relative path in .app bundle
|
|
1475
1493
|
path = "..." explicit executable path
|
|
1476
1494
|
args = ["..."] extra arguments
|
|
1477
|
-
|
|
1495
|
+
passPaths = true|false|N|[...] paths passed as launch args
|
|
1496
|
+
background = true run in background by default
|
|
1478
1497
|
built-in apps (code, xcode) can be overridden
|
|
1479
1498
|
~/.bxignore sandbox rules (one per line):
|
|
1480
1499
|
path block access (deny)
|
|
@@ -1555,7 +1574,7 @@ function printDryRunTree({ home, blockedDirs, ignoredPaths, readOnlyDirs, workDi
|
|
|
1555
1574
|
}
|
|
1556
1575
|
//#endregion
|
|
1557
1576
|
//#region package.json
|
|
1558
|
-
var version = "0.
|
|
1577
|
+
var version = "1.0.0";
|
|
1559
1578
|
//#endregion
|
|
1560
1579
|
//#region src/index.ts
|
|
1561
1580
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1575,16 +1594,16 @@ if (!process$1.env.HOME) {
|
|
|
1575
1594
|
const HOME = process$1.env.HOME;
|
|
1576
1595
|
async function main() {
|
|
1577
1596
|
const apps = getAvailableApps(loadConfig(HOME));
|
|
1578
|
-
const { mode, workArgs, verbose, dry, profileSandbox, appArgs, implicit } = parseArgs(getValidModes(apps));
|
|
1597
|
+
const { mode, workArgs, verbose, dry, profileSandbox, background: backgroundFlag, appArgs, implicit } = parseArgs(getValidModes(apps));
|
|
1579
1598
|
const app = apps[mode];
|
|
1580
|
-
const workDirs = (implicit && app?.
|
|
1581
|
-
if (implicit && !app?.
|
|
1599
|
+
const workDirs = (implicit && app?.paths?.length ? app.paths : workArgs).map((a) => realpathSync(resolve(a.replace(/^~\//, HOME + "/"))));
|
|
1600
|
+
if (implicit && !app?.paths?.length) {
|
|
1582
1601
|
if (workDirs.some((d) => d === HOME)) {
|
|
1583
1602
|
console.error(`\n${fmt.error("no working directory specified and current directory is $HOME")}\n`);
|
|
1584
1603
|
console.error(fmt.detail(`Usage: bx ${mode} <workdir>`));
|
|
1585
|
-
console.error(fmt.detail(`Config: set default
|
|
1604
|
+
console.error(fmt.detail(`Config: set default paths in ~/.bxconfig.toml:\n`));
|
|
1586
1605
|
console.error(fmt.detail(`[${mode}]`));
|
|
1587
|
-
console.error(fmt.detail(`
|
|
1606
|
+
console.error(fmt.detail(`paths = ["~/work/my-project"]\n`));
|
|
1588
1607
|
process$1.exit(1);
|
|
1589
1608
|
}
|
|
1590
1609
|
if (!dry) await confirmLaunch(workDirs[0], mode);
|
|
@@ -1618,13 +1637,54 @@ async function main() {
|
|
|
1618
1637
|
});
|
|
1619
1638
|
process$1.exit(0);
|
|
1620
1639
|
}
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1640
|
+
const tmpDir = mkdtempSync(join("/tmp", "bx-"));
|
|
1641
|
+
const profilePath = join(tmpDir, "profile.sb");
|
|
1642
|
+
writeFileSync(profilePath, profile, { mode: 384 });
|
|
1623
1643
|
const cmd = buildCommand(mode, workDirs, HOME, profileSandbox, appArgs, apps);
|
|
1644
|
+
const background = backgroundFlag || app?.background === true;
|
|
1624
1645
|
const nestedSandboxWarning = getNestedSandboxWarning(mode, apps);
|
|
1625
1646
|
if (nestedSandboxWarning) console.error(fmt.detail(nestedSandboxWarning));
|
|
1626
|
-
|
|
1647
|
+
printLaunchDetails(cmd, workDirs[0]);
|
|
1648
|
+
if (verbose) {
|
|
1649
|
+
const activationCmd = getActivationCommand(mode, apps);
|
|
1650
|
+
if (activationCmd) {
|
|
1651
|
+
const quote = (a) => JSON.stringify(a);
|
|
1652
|
+
console.error(fmt.detail(`focus: ${activationCmd.bin} ${activationCmd.args.map(quote).join(" ")}`));
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1627
1655
|
console.error("");
|
|
1656
|
+
if (background) {
|
|
1657
|
+
const logPath = join(tmpDir, "bx.log");
|
|
1658
|
+
const logFd = openSync(logPath, "a", 384);
|
|
1659
|
+
const child = spawn("sandbox-exec", [
|
|
1660
|
+
"-f",
|
|
1661
|
+
profilePath,
|
|
1662
|
+
"-D",
|
|
1663
|
+
`HOME=${HOME}`,
|
|
1664
|
+
"-D",
|
|
1665
|
+
`WORK=${workDirs[0]}`,
|
|
1666
|
+
cmd.bin,
|
|
1667
|
+
...cmd.args
|
|
1668
|
+
], {
|
|
1669
|
+
cwd: workDirs[0],
|
|
1670
|
+
stdio: [
|
|
1671
|
+
"ignore",
|
|
1672
|
+
logFd,
|
|
1673
|
+
logFd
|
|
1674
|
+
],
|
|
1675
|
+
detached: true,
|
|
1676
|
+
env: {
|
|
1677
|
+
...process$1.env,
|
|
1678
|
+
CODEBOX_SANDBOX: "1"
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
child.unref();
|
|
1682
|
+
bringAppToFront(mode, apps);
|
|
1683
|
+
console.error(fmt.info(`running in background (pid ${child.pid})`));
|
|
1684
|
+
console.error(fmt.detail(`log: ${logPath}`));
|
|
1685
|
+
console.error(fmt.detail(`sandbox profile: ${profilePath} (kept until process exits)`));
|
|
1686
|
+
process$1.exit(0);
|
|
1687
|
+
}
|
|
1628
1688
|
const child = spawn("sandbox-exec", [
|
|
1629
1689
|
"-f",
|
|
1630
1690
|
profilePath,
|
|
@@ -1643,8 +1703,16 @@ async function main() {
|
|
|
1643
1703
|
}
|
|
1644
1704
|
});
|
|
1645
1705
|
bringAppToFront(mode, apps);
|
|
1706
|
+
const cleanup = () => {
|
|
1707
|
+
try {
|
|
1708
|
+
rmSync(tmpDir, {
|
|
1709
|
+
recursive: true,
|
|
1710
|
+
force: true
|
|
1711
|
+
});
|
|
1712
|
+
} catch {}
|
|
1713
|
+
};
|
|
1714
|
+
process$1.on("exit", cleanup);
|
|
1646
1715
|
child.on("close", (code) => {
|
|
1647
|
-
rmSync(profilePath, { force: true });
|
|
1648
1716
|
process$1.exit(code ?? 0);
|
|
1649
1717
|
});
|
|
1650
1718
|
}
|
|
@@ -1668,12 +1736,11 @@ function printPolicySummary(mode, workDirs, blockedDirs, ignoredPaths, readOnly)
|
|
|
1668
1736
|
if (extraIgnored > 0) parts.push(`${extraIgnored} from .bxignore`);
|
|
1669
1737
|
console.error(fmt.detail(parts.join(" ยท ")));
|
|
1670
1738
|
}
|
|
1671
|
-
function printLaunchDetails(cmd, cwd
|
|
1739
|
+
function printLaunchDetails(cmd, cwd) {
|
|
1672
1740
|
const quote = (a) => JSON.stringify(a);
|
|
1673
1741
|
console.error(fmt.detail(`bin: ${cmd.bin}`));
|
|
1674
1742
|
console.error(fmt.detail(`args: ${cmd.args.map(quote).join(" ") || "(none)"}`));
|
|
1675
1743
|
console.error(fmt.detail(`cwd: ${cwd}`));
|
|
1676
|
-
if (activationCmd) console.error(fmt.detail(`focus: ${activationCmd.bin} ${activationCmd.args.map(quote).join(" ")}`));
|
|
1677
1744
|
}
|
|
1678
1745
|
main();
|
|
1679
1746
|
//#endregion
|