本篇讲解
src/runner.ts------沙箱命令的统一执行入口。它把 Seatbelt runner、shell fallback、能力检测、danger-full-access 回退等逻辑组合在一起。
1. Runner 解决什么问题?
不同平台的沙箱能力不同:
| 平台 | 原生沙箱 | 可用模式 |
|---|---|---|
| macOS | Seatbelt(sandbox-exec) | read-only / workspace-write |
| Linux | 无原生实现 | 需要 bubblewrap 等 |
| Windows | 无原生实现 | --- |
当 OS 沙箱不可用时(比如在 Linux 上),怎么办?
- 直接报错(fail-closed,最安全)
- 问用户要不要回退到 danger-full-access(用户说了算)
- 自动回退到 danger-full-access(最不安全,不推荐)
runner.ts 就是来处理这些决策的。
2. runSandboxedCommand------统一入口
typescript
export async function runSandboxedCommand(options: {
commandSpec: SandboxCommandSpec;
mode: SandboxMode;
timeoutMs: number;
executionRoot: string;
profilesDir: string;
allowDangerousFallback?: boolean;
spawnEnv?: NodeJS.ProcessEnv;
io?: RunSandboxedIoHooks;
onOsSandboxUnavailable?: (ctx: OsSandboxUnavailableContext) => Promise<OsSandboxFallbackDecision>;
}): Promise<{ exitCode: number; effectiveMode: SandboxMode }> {
输入 :命令规格 + 沙箱配置 输出:退出码 + 实际执行模式
2.1 核心流程
sql
请求的模式
│
▼
resolveEffectiveMode()
│
├── danger-full-access → 直接 spawn
│
├── read-only / workspace-write 且 OS 沙箱可用 → Seatbelt runner
│
├── read-only / workspace-write 且 OS 沙箱不可用 → 问回调
│ │
│ ├── 回调返回 danger-full-access → 直接 spawn
│ ├── 回调返回 abort → 退出码 1
│ └── 无回调 → 抛错(fail-closed)
│
└── null(abort)→ 退出码 1
3. resolveEffectiveMode------决定实际执行模式
typescript
async function resolveEffectiveMode(options: {
requestedMode: SandboxMode;
profilesDir: string;
allowDangerousFallback?: boolean;
onOsSandboxUnavailable?: (ctx: OsSandboxUnavailableContext) => Promise<OsSandboxFallbackDecision>;
}): Promise<SandboxMode | null> {
// 1. danger-full-access 直接返回
if (options.requestedMode === 'danger-full-access') {
return 'danger-full-access';
}
// 2. 检查 OS 沙箱能力
const capability = checkOsSandboxCapability(options.requestedMode, options.profilesDir);
if (capability.available) {
return options.requestedMode; // OS 沙箱可用,用请求的模式
}
// 3. OS 沙箱不可用,没回调 → 抛错
if (!options.onOsSandboxUnavailable) {
throw new Error(
`${capability.reason} (fail-closed: set onOsSandboxUnavailable to explicitly fallback or abort)`,
);
}
// 4. 问回调
const decision = await options.onOsSandboxUnavailable({
reason: capability.reason,
requestedMode: options.requestedMode,
platform: process.platform,
});
// 5. 回调想回退但配置不允许 → 抛错
if (decision === 'danger-full-access' && !options.allowDangerousFallback) {
throw new Error(
`${capability.reason} (danger-full-access fallback requested but disabled by configuration)`,
);
}
// 6. 返回最终决策
return decision === 'abort' ? null : 'danger-full-access';
}
返回值:
SandboxMode→ 用这个模式执行null→ 用户选择 abort,不执行
4. checkOsSandboxCapability------能力检测
typescript
export function checkOsSandboxCapability(
mode: Exclude<SandboxMode, 'danger-full-access'>,
profilesDir: string,
): OsSandboxCapability {
if (process.platform === 'darwin') {
return { ...checkSeatbeltCapability(mode, profilesDir), platform: process.platform };
}
return {
available: false,
platform: process.platform,
reason: `OS sandbox is not implemented on ${process.platform} for mode "${mode}".`,
};
}
目前只支持 macOS(Seatbelt)。其他平台返回 available: false。
5. runWithOsSandbox------通过 OS 沙箱执行
typescript
function runWithOsSandbox(options: {
commandSpec: SandboxCommandSpec;
mode: Exclude<SandboxMode, 'danger-full-access'>;
profilesDir: string;
workspace: string;
timeoutMs: number;
spawnEnv?: NodeJS.ProcessEnv;
io?: RunSandboxedIoHooks;
}): Promise<number> {
switch (process.platform) {
case 'darwin': {
const spec = buildSeatbeltSpawnSpec({
commandSpec: options.commandSpec,
mode: options.mode,
profilesDir: options.profilesDir,
workspace: options.workspace,
});
return spawnAndWait(
spec.command,
spec.args,
options.timeoutMs,
{ cwd: options.workspace, env: options.spawnEnv },
options.io,
);
}
default:
return Promise.reject(new Error('OS sandbox not implemented on this platform.'));
}
}
6. 完整执行流程
typescript
export async function runSandboxedCommand(options) {
// 1. 确保执行根目录存在
const executionRoot = path.resolve(options.executionRoot);
if (!fs.existsSync(executionRoot)) {
fs.mkdirSync(executionRoot, { recursive: true });
}
// 2. 决定实际执行模式
const effectiveMode = await resolveEffectiveMode({
requestedMode: options.mode,
profilesDir: options.profilesDir,
allowDangerousFallback: options.allowDangerousFallback,
onOsSandboxUnavailable: options.onOsSandboxUnavailable,
});
// 3. abort → 返回退出码 1
if (effectiveMode === null) {
return { exitCode: 1, effectiveMode: options.mode };
}
// 4. danger-full-access → 直接 spawn
if (effectiveMode === 'danger-full-access') {
const exitCode = options.commandSpec.kind === 'exec'
? await spawnAndWait(options.commandSpec.executable, options.commandSpec.args, ...)
: await spawnShellUnsafe(options.commandSpec.shellCommand, ...);
return { exitCode, effectiveMode };
}
// 5. OS 沙箱模式 → Seatbelt
const exitCode = await runWithOsSandbox({ ...options, mode: effectiveMode });
return { exitCode, effectiveMode };
}
7. 模式回退示例
7.1 macOS 上正常执行
arduino
请求: workspace-write
OS 沙箱可用 → Seatbelt 执行
effectiveMode = workspace-write
7.2 Linux 上无 OS 沙箱,用户同意回退
ini
请求: workspace-write
OS 沙箱不可用 → 问回调
回调返回 danger-full-access → 直接 spawn
effectiveMode = danger-full-access
dangerousFallbackUsed = true
7.3 Linux 上无 OS 沙箱,用户拒绝
ini
请求: workspace-write
OS 沙箱不可用 → 问回调
回调返回 abort → 不执行
exitCode = 1
8. 小结
| 函数 | 作用 |
|---|---|
runSandboxedCommand |
统一入口:决定执行方式并执行 |
resolveEffectiveMode |
决定实际执行模式(可能回退) |
checkOsSandboxCapability |
检查 OS 沙箱是否可用 |
runWithOsSandbox |
通过 OS 沙箱执行(目前只有 macOS) |
关键设计:
- Fail-closed:没回调就抛错,不自动回退
- 用户决策:有回调就让用户选------回退还是中止
- 配置守卫 :
allowDangerousFallback必须为true才允许回退 - 返回 effectiveMode:让调用方知道实际执行的是什么模式