Removes computeAutoApproval() and tightens up canAutoApprove() as the source of truth (#126)
Previously, `parseToolCall()` was using `computeAutoApproval()`, which was a somewhat parallel implementation of `canAutoApprove()` in order to get `SafeCommandReason` metadata for presenting information to the user. The only function that was using `SafeCommandReason` was `useMessageGrouping()`, but it turns out that function was unused, so this PR removes `computeAutoApproval()` and all code related to it. More importantly, I believe this fixes https://github.com/openai/codex/issues/87 because `computeAutoApproval()` was calling `parse()` from `shell-quote` without wrapping it in a try-catch. This PR updates `canAutoApprove()` to use a tighter try-catch block that is specific to `parse()` and returns an appropriate `SafetyAssessment` in the event of an error, based on the `ApprovalPolicy`. Signed-off-by: Michael Bolin <mbolin@openai.com>
This commit is contained in:
@@ -1,11 +1,3 @@
|
||||
import type { SafeCommandReason } from "../../approvals";
|
||||
|
||||
export type CommandReviewDetails = {
|
||||
cmd: Array<string>;
|
||||
cmdReadableText: string;
|
||||
autoApproval: SafeCommandReason | null;
|
||||
};
|
||||
|
||||
export enum ReviewDecision {
|
||||
YES = "yes",
|
||||
NO_CONTINUE = "no-continue",
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import type { CommandReviewDetails } from "./agent/review.js";
|
||||
import type {
|
||||
ExecInput,
|
||||
ExecOutputMetadata,
|
||||
} from "./agent/sandbox/interface.js";
|
||||
import type { ResponseFunctionToolCall } from "openai/resources/responses/responses.mjs";
|
||||
|
||||
import { isSafeCommand, type SafeCommandReason } from "../approvals.js";
|
||||
import { log } from "node:console";
|
||||
import process from "process";
|
||||
import { parse } from "shell-quote";
|
||||
import { formatCommandForDisplay } from "src/format-command.js";
|
||||
|
||||
// The console utility import is intentionally explicit to avoid bundlers from
|
||||
// including the entire `console` module when only the `log` function is
|
||||
// required.
|
||||
|
||||
// Allowed shell operators that we consider "safe" as they do not introduce
|
||||
// side‑effects on their own (unlike redirections). Parentheses and braces for
|
||||
// grouping are excluded for simplicity.
|
||||
const SAFE_SHELL_OPERATORS: ReadonlySet<string> = new Set([
|
||||
"&&",
|
||||
"||",
|
||||
"|",
|
||||
";",
|
||||
]);
|
||||
|
||||
export function parseToolCallOutput(toolCallOutput: string): {
|
||||
output: string;
|
||||
metadata: ExecOutputMetadata;
|
||||
@@ -46,6 +32,17 @@ export function parseToolCallOutput(toolCallOutput: string): {
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandReviewDetails = {
|
||||
cmd: Array<string>;
|
||||
cmdReadableText: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tries to parse a tool call and, if successful, returns an object that has
|
||||
* both:
|
||||
* - an array of strings to use with `ExecInput` and `canAutoApprove()`
|
||||
* - a human-readable string to display to the user
|
||||
*/
|
||||
export function parseToolCall(
|
||||
toolCall: ResponseFunctionToolCall,
|
||||
): CommandReviewDetails | undefined {
|
||||
@@ -57,12 +54,9 @@ export function parseToolCall(
|
||||
const { cmd } = toolCallArgs;
|
||||
const cmdReadableText = formatCommandForDisplay(cmd);
|
||||
|
||||
const autoApproval = computeAutoApproval(cmd);
|
||||
|
||||
return {
|
||||
cmd,
|
||||
cmdReadableText,
|
||||
autoApproval,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,87 +103,3 @@ function toStringArray(obj: unknown): Array<string> | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- safe‑command helpers ----------------
|
||||
|
||||
/**
|
||||
* Attempts to determine whether `cmd` is composed exclusively of safe
|
||||
* sub‑commands combined using only operators from the SAFE_SHELL_OPERATORS
|
||||
* allow‑list. Returns the `SafeCommandReason` (taken from the first sub‑command)
|
||||
* if the whole expression is safe; otherwise returns `null`.
|
||||
*/
|
||||
function computeAutoApproval(cmd: Array<string>): SafeCommandReason | null {
|
||||
// Fast path: a simple command with no shell processing.
|
||||
const direct = isSafeCommand(cmd);
|
||||
if (direct != null) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
// For expressions like ["bash", "-lc", "ls && pwd"] break down the inner
|
||||
// string and verify each segment.
|
||||
if (
|
||||
cmd.length === 3 &&
|
||||
cmd[0] === "bash" &&
|
||||
cmd[1] === "-lc" &&
|
||||
typeof cmd[2] === "string"
|
||||
) {
|
||||
const parsed = parse(cmd[2], process.env ?? {});
|
||||
if (parsed.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let current: Array<string> = [];
|
||||
let first: SafeCommandReason | null = null;
|
||||
|
||||
const flush = (): boolean => {
|
||||
if (current.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const safe = isSafeCommand(current);
|
||||
if (safe == null) {
|
||||
return false;
|
||||
}
|
||||
if (!first) {
|
||||
first = safe;
|
||||
}
|
||||
current = [];
|
||||
return true;
|
||||
};
|
||||
|
||||
for (const part of parsed) {
|
||||
if (typeof part === "string") {
|
||||
// Simple word/argument token.
|
||||
if (part === "(" || part === ")" || part === "{" || part === "}") {
|
||||
// We treat explicit grouping tokens as unsafe because their
|
||||
// semantics depend on the shell evaluation environment.
|
||||
return null;
|
||||
}
|
||||
current.push(part);
|
||||
} else if (part && typeof part === "object") {
|
||||
const opToken = part as { op?: string };
|
||||
if (typeof opToken.op === "string") {
|
||||
if (!flush()) {
|
||||
return null;
|
||||
}
|
||||
if (!SAFE_SHELL_OPERATORS.has(opToken.op)) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Unknown object token kind (e.g. redirection) – treat as unsafe.
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Token types such as numbers / booleans are unexpected – treat as unsafe.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flush()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user