fix: check if sandbox-exec is available (#696)
- Introduce `isSandboxExecAvailable()` helper and tidy import ordering in `handle-exec-command.ts`. - Add runtime check for the `sandbox-exec` binary on macOS; fall back to `SandboxType.NONE` with a warning if it’s missing, preventing crashes. --------- Signed-off-by: Thibault Sottiaux <tibo@openai.com> Co-authored-by: Fouad Matin <fouad@openai.com>
This commit is contained in:
committed by
GitHub
parent
523996b5cb
commit
e9d16d3c2b
@@ -1,17 +1,19 @@
|
|||||||
import type { CommandConfirmation } from "./agent-loop.js";
|
|
||||||
import type { AppConfig } from "../config.js";
|
|
||||||
import type { ExecInput } from "./sandbox/interface.js";
|
|
||||||
import type { ApplyPatchCommand, ApprovalPolicy } from "../../approvals.js";
|
import type { ApplyPatchCommand, ApprovalPolicy } from "../../approvals.js";
|
||||||
|
import type { AppConfig } from "../config.js";
|
||||||
|
import type { CommandConfirmation } from "./agent-loop.js";
|
||||||
|
import type { ExecInput } from "./sandbox/interface.js";
|
||||||
import type { ResponseInputItem } from "openai/resources/responses/responses.mjs";
|
import type { ResponseInputItem } from "openai/resources/responses/responses.mjs";
|
||||||
|
|
||||||
import { exec, execApplyPatch } from "./exec.js";
|
|
||||||
import { ReviewDecision } from "./review.js";
|
|
||||||
import { FullAutoErrorMode } from "../auto-approval-mode.js";
|
|
||||||
import { SandboxType } from "./sandbox/interface.js";
|
|
||||||
import { canAutoApprove } from "../../approvals.js";
|
import { canAutoApprove } from "../../approvals.js";
|
||||||
import { formatCommandForDisplay } from "../../format-command.js";
|
import { formatCommandForDisplay } from "../../format-command.js";
|
||||||
|
import { FullAutoErrorMode } from "../auto-approval-mode.js";
|
||||||
|
import { CODEX_UNSAFE_ALLOW_NO_SANDBOX } from "../config.js";
|
||||||
|
import { exec, execApplyPatch } from "./exec.js";
|
||||||
|
import { ReviewDecision } from "./review.js";
|
||||||
import { isLoggingEnabled, log } from "../logger/log.js";
|
import { isLoggingEnabled, log } from "../logger/log.js";
|
||||||
|
import { SandboxType } from "./sandbox/interface.js";
|
||||||
import { access } from "fs/promises";
|
import { access } from "fs/promises";
|
||||||
|
import { execFile } from "node:child_process";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Session‑level cache of commands that the user has chosen to always approve.
|
// Session‑level cache of commands that the user has chosen to always approve.
|
||||||
@@ -279,10 +281,48 @@ const isInLinux = async (): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return `true` if the `sandbox-exec` binary can be located. This intentionally does **not**
|
||||||
|
* spawn the binary – we only care about its presence.
|
||||||
|
*/
|
||||||
|
export const isSandboxExecAvailable = (): Promise<boolean> =>
|
||||||
|
new Promise((res) =>
|
||||||
|
execFile(
|
||||||
|
"command",
|
||||||
|
["-v", "sandbox-exec"],
|
||||||
|
{ signal: AbortSignal.timeout(200) },
|
||||||
|
(err) => res(!err), // exit 0 ⇒ found
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
async function getSandbox(runInSandbox: boolean): Promise<SandboxType> {
|
async function getSandbox(runInSandbox: boolean): Promise<SandboxType> {
|
||||||
if (runInSandbox) {
|
if (runInSandbox) {
|
||||||
if (process.platform === "darwin") {
|
if (process.platform === "darwin") {
|
||||||
return SandboxType.MACOS_SEATBELT;
|
// On macOS we rely on the system-provided `sandbox-exec` binary to
|
||||||
|
// enforce the Seatbelt profile. However, starting with macOS 14 the
|
||||||
|
// executable may be removed from the default installation or the user
|
||||||
|
// might be running the CLI on a stripped-down environment (for
|
||||||
|
// instance, inside certain CI images). Attempting to spawn a missing
|
||||||
|
// binary makes Node.js throw an *uncaught* `ENOENT` error further down
|
||||||
|
// the stack which crashes the whole CLI.
|
||||||
|
|
||||||
|
// To provide a graceful degradation path we first check at runtime
|
||||||
|
// whether `sandbox-exec` can be found **and** is executable. If the
|
||||||
|
// check fails we fall back to the NONE sandbox while emitting a
|
||||||
|
// warning so users and maintainers are aware that the additional
|
||||||
|
// process isolation is not in effect.
|
||||||
|
|
||||||
|
if (await isSandboxExecAvailable()) {
|
||||||
|
return SandboxType.MACOS_SEATBELT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CODEX_UNSAFE_ALLOW_NO_SANDBOX) {
|
||||||
|
log(
|
||||||
|
"WARNING: macOS Seatbelt sandbox requested but 'sandbox-exec' was not found in PATH. " +
|
||||||
|
"With `CODEX_UNSAFE_ALLOW_NO_SANDBOX` enabled, continuing without sandbox.",
|
||||||
|
);
|
||||||
|
return SandboxType.NONE;
|
||||||
|
}
|
||||||
} else if (await isInLinux()) {
|
} else if (await isInLinux()) {
|
||||||
return SandboxType.NONE;
|
return SandboxType.NONE;
|
||||||
} else if (process.platform === "win32") {
|
} else if (process.platform === "win32") {
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ export let OPENAI_API_KEY = process.env["OPENAI_API_KEY"] || "";
|
|||||||
export const OPENAI_ORGANIZATION = process.env["OPENAI_ORGANIZATION"] || "";
|
export const OPENAI_ORGANIZATION = process.env["OPENAI_ORGANIZATION"] || "";
|
||||||
export const OPENAI_PROJECT = process.env["OPENAI_PROJECT"] || "";
|
export const OPENAI_PROJECT = process.env["OPENAI_PROJECT"] || "";
|
||||||
|
|
||||||
|
export const CODEX_UNSAFE_ALLOW_NO_SANDBOX = Boolean(
|
||||||
|
process.env["CODEX_UNSAFE_ALLOW_NO_SANDBOX"] || "",
|
||||||
|
);
|
||||||
|
|
||||||
export function setApiKey(apiKey: string): void {
|
export function setApiKey(apiKey: string): void {
|
||||||
OPENAI_API_KEY = apiKey;
|
OPENAI_API_KEY = apiKey;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user