十、统一 Runner 入口——能力检测与模式回退

本篇讲解 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 上),怎么办?

  1. 直接报错(fail-closed,最安全)
  2. 问用户要不要回退到 danger-full-access(用户说了算)
  3. 自动回退到 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)

关键设计:

  1. Fail-closed:没回调就抛错,不自动回退
  2. 用户决策:有回调就让用户选------回退还是中止
  3. 配置守卫allowDangerousFallback 必须为 true 才允许回退
  4. 返回 effectiveMode:让调用方知道实际执行的是什么模式
相关推荐
宸津-代码粉碎机5 分钟前
AI攻防战升级!基于Spring AI构建Java应用自动免疫安全体系
java·大数据·开发语言·人工智能·python·安全·spring
克里斯蒂亚诺更新34 分钟前
机器学习库sklearn的主要任务
人工智能·机器学习·sklearn
先跑起来再说1 小时前
Qavor:一个能跑、能观测、能扩展的开源 AI Agent 工作台
人工智能·开源
Mr数据杨1 小时前
从文本特征到回归建模 Syllable Counting Model 实战解析
人工智能·数据分析·kaggle竞赛
HIT_Weston1 小时前
201、【Agent】【OpenCode】JS 语法:setTimeout 七种常用形式
人工智能·agent·opencode
维基框架1 小时前
萨尔瓦多学校免费上Grok 没账单的AI怎么限流
人工智能
敢敢是只喵i1 小时前
给现有业务系统接 AI 助手,入口怎么选?网页聊天、自动化任务与本地 Agent 对比
运维·人工智能·ai·开源·自动化·安全架构
AI行业说1 小时前
誉财自动化YC-18-M8045实测:服装自动化vs人工缝制产能成本对比
大数据·人工智能·自动化·智能模板机
朦胧之1 小时前
PostgreSQL 数据库笔记
人工智能·后端
hiahiahia1231 小时前
Cookie、Session、Token 到底有什么区别?
人工智能