mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-19 05:41:42 +00:00
fix: bind package-manager exec approvals to inner commands [AI] (#102035)
* fix: bind package-manager exec approvals to inner commands * fix: fail closed package manager exec approval gaps * chore: refresh pr branch metadata * fix: cover npm exec alias approvals * fix: fail closed hidden package manager exec aliases * fix: unwrap chained package manager approvals * fix: cover yarn package manager approvals * fix: preserve yarn run approval compatibility * fix: bind package manager script argv checks * test: fix package manager script argv type check * fix: fail closed pnpm shorthand approvals * fix: preserve pnpm built-in approvals * fix: preserve pnpm script shortcuts * fix: preserve npm cwd exec unwrapping * fix: fail closed cwd and yarn package shorthands
This commit is contained in:
committed by
GitHub
parent
c69724ef3b
commit
534ace4d8a
@@ -590,6 +590,13 @@ describe("hardenApprovedExecutionPaths", () => {
|
||||
initialBody: 'console.log("SAFE");\n',
|
||||
expectedArgvIndex: 5,
|
||||
},
|
||||
{
|
||||
name: "pnpm cwd exec tsx file",
|
||||
argv: ["pnpm", "-C", "./package", "exec", "tsx", "./run.ts"],
|
||||
scriptName: "run.ts",
|
||||
initialBody: 'console.log("SAFE");\n',
|
||||
expectedArgvIndex: 5,
|
||||
},
|
||||
{
|
||||
name: "pnpm js shim exec tsx file",
|
||||
argv: ["./pnpm.js", "exec", "tsx", "./run.ts"],
|
||||
@@ -627,6 +634,27 @@ describe("hardenApprovedExecutionPaths", () => {
|
||||
initialBody: 'console.log("SAFE");\n',
|
||||
expectedArgvIndex: 4,
|
||||
},
|
||||
{
|
||||
name: "npm x tsx file",
|
||||
argv: ["npm", "x", "--", "tsx", "./run.ts"],
|
||||
scriptName: "run.ts",
|
||||
initialBody: 'console.log("SAFE");\n',
|
||||
expectedArgvIndex: 4,
|
||||
},
|
||||
{
|
||||
name: "npm loglevel exec tsx file",
|
||||
argv: ["npm", "--loglevel=silent", "exec", "--", "tsx", "./run.ts"],
|
||||
scriptName: "run.ts",
|
||||
initialBody: 'console.log("SAFE");\n',
|
||||
expectedArgvIndex: 5,
|
||||
},
|
||||
{
|
||||
name: "npm cwd exec tsx file",
|
||||
argv: ["npm", "-C", "./package", "exec", "--", "tsx", "./run.ts"],
|
||||
scriptName: "run.ts",
|
||||
initialBody: 'console.log("SAFE");\n',
|
||||
expectedArgvIndex: 6,
|
||||
},
|
||||
];
|
||||
|
||||
it("captures mutable runtime operands in approval plans", () => {
|
||||
|
||||
@@ -21,6 +21,14 @@ import {
|
||||
} from "../infra/exec-wrapper-resolution.js";
|
||||
import { sameFileIdentity } from "../infra/fs-safe-advanced.js";
|
||||
import { parseInlineOptionToken } from "../infra/inline-option-token.js";
|
||||
import {
|
||||
normalizePackageManagerExecToken,
|
||||
PNPM_CASE_SENSITIVE_OPTIONS_WITH_VALUE,
|
||||
PNPM_DLX_OPTIONS_WITH_VALUE,
|
||||
PNPM_FLAG_OPTIONS,
|
||||
PNPM_OPTIONS_WITH_VALUE,
|
||||
unwrapKnownPackageManagerExecInvocation,
|
||||
} from "../infra/package-manager-exec-wrapper.js";
|
||||
import {
|
||||
advancePosixInlineOptionScan,
|
||||
POSIX_INLINE_COMMAND_FLAGS,
|
||||
@@ -172,52 +180,6 @@ function isPosixShellOptionToken(token: string, supportsPlusOptions: boolean): b
|
||||
return token.startsWith("-") || (supportsPlusOptions && token.startsWith("+"));
|
||||
}
|
||||
|
||||
const NPM_EXEC_OPTIONS_WITH_VALUE = new Set([
|
||||
"--cache",
|
||||
"--package",
|
||||
"--prefix",
|
||||
"--script-shell",
|
||||
"--userconfig",
|
||||
"--workspace",
|
||||
"-p",
|
||||
"-w",
|
||||
]);
|
||||
|
||||
const NPM_EXEC_FLAG_OPTIONS = new Set([
|
||||
"--no",
|
||||
"--quiet",
|
||||
"--ws",
|
||||
"--workspaces",
|
||||
"--yes",
|
||||
"-q",
|
||||
"-y",
|
||||
]);
|
||||
|
||||
const PNPM_OPTIONS_WITH_VALUE = new Set([
|
||||
"--config",
|
||||
"--dir",
|
||||
"--filter",
|
||||
"--reporter",
|
||||
"--stream",
|
||||
"--test-pattern",
|
||||
"--workspace-concurrency",
|
||||
"-C",
|
||||
]);
|
||||
|
||||
const PNPM_FLAG_OPTIONS = new Set([
|
||||
"--aggregate-output",
|
||||
"--color",
|
||||
"--parallel",
|
||||
"--recursive",
|
||||
"--silent",
|
||||
"--workspace-root",
|
||||
"-r",
|
||||
"-s",
|
||||
"-w",
|
||||
]);
|
||||
|
||||
const PNPM_DLX_OPTIONS_WITH_VALUE = new Set(["--allow-build", "--package", "-p"]);
|
||||
|
||||
type FileOperandCollection = {
|
||||
hits: number[];
|
||||
sawOptionValueFile: boolean;
|
||||
@@ -428,171 +390,6 @@ function unwrapArgvForMutableOperand(argv: string[]): {
|
||||
}
|
||||
}
|
||||
|
||||
function unwrapKnownPackageManagerExecInvocation(argv: string[]): string[] | null {
|
||||
const executable = normalizePackageManagerExecToken(argv[0] ?? "");
|
||||
switch (executable) {
|
||||
case "npm":
|
||||
return unwrapNpmExecInvocation(argv);
|
||||
case "npx":
|
||||
case "bunx":
|
||||
return unwrapDirectPackageExecInvocation(argv);
|
||||
case "pnpm":
|
||||
return unwrapPnpmExecInvocation(argv);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePackageManagerExecToken(token: string): string {
|
||||
const normalized = normalizeExecutableToken(token);
|
||||
if (!normalized) {
|
||||
return normalized;
|
||||
}
|
||||
// Approval binding only promises best-effort recovery of the effective runtime
|
||||
// command for common package-manager shims; it is not full package-manager semantics.
|
||||
return normalized.replace(/\.(?:c|m)?js$/i, "");
|
||||
}
|
||||
|
||||
function unwrapPnpmExecInvocation(argv: string[]): string[] | null {
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
if (token === "--") {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
if (!token.startsWith("-")) {
|
||||
if (token === "exec") {
|
||||
if (idx + 1 >= argv.length) {
|
||||
return null;
|
||||
}
|
||||
const tail = argv.slice(idx + 1);
|
||||
return tail[0] === "--" ? (tail.length > 1 ? tail.slice(1) : null) : tail;
|
||||
}
|
||||
if (token === "dlx") {
|
||||
return unwrapPnpmDlxInvocation(argv.slice(idx + 1));
|
||||
}
|
||||
if (token === "node") {
|
||||
const tail = argv.slice(idx + 1);
|
||||
const normalizedTail = tail[0] === "--" ? tail.slice(1) : tail;
|
||||
return ["node", ...normalizedTail];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (PNPM_OPTIONS_WITH_VALUE.has(flag) || PNPM_DLX_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (PNPM_FLAG_OPTIONS.has(flag)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function unwrapPnpmDlxInvocation(argv: string[]): string[] | null {
|
||||
let idx = 0;
|
||||
while (idx < argv.length) {
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
if (token === "--") {
|
||||
const tail = argv.slice(idx + 1);
|
||||
return tail.length > 0 ? tail : null;
|
||||
}
|
||||
if (!token.startsWith("-")) {
|
||||
// Once dlx-specific flags are stripped, the first positional token is the
|
||||
// package binary pnpm will execute inside the temporary environment.
|
||||
return argv.slice(idx);
|
||||
}
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (flag === "-c" || flag === "--shell-mode") {
|
||||
return null;
|
||||
}
|
||||
if (PNPM_OPTIONS_WITH_VALUE.has(flag) || PNPM_DLX_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (PNPM_FLAG_OPTIONS.has(flag)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function unwrapDirectPackageExecInvocation(argv: string[]): string[] | null {
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
if (!token.startsWith("-")) {
|
||||
return argv.slice(idx);
|
||||
}
|
||||
const flag = normalizeOptionFlag(token);
|
||||
if (flag === "-c" || flag === "--call") {
|
||||
return null;
|
||||
}
|
||||
if (NPM_EXEC_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (NPM_EXEC_FLAG_OPTIONS.has(flag)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function unwrapNpmExecInvocation(argv: string[]): string[] | null {
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
if (!token.startsWith("-")) {
|
||||
if (token !== "exec") {
|
||||
return null;
|
||||
}
|
||||
idx += 1;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
(token === "-C" || token === "--prefix" || token === "--userconfig") &&
|
||||
!token.includes("=")
|
||||
) {
|
||||
idx += 2;
|
||||
continue;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
if (idx >= argv.length) {
|
||||
return null;
|
||||
}
|
||||
const tail = argv.slice(idx);
|
||||
if (tail[0] === "--") {
|
||||
return tail.length > 1 ? tail.slice(1) : null;
|
||||
}
|
||||
return unwrapDirectPackageExecInvocation(["npx", ...tail]);
|
||||
}
|
||||
|
||||
function resolvePosixShellScriptOperandIndex(argv: string[], executable: string): number | null {
|
||||
const supportsPlusOptions = POSIX_SHELLS_WITH_PLUS_OPTIONS.has(executable);
|
||||
if (
|
||||
@@ -1007,11 +804,16 @@ function pnpmDlxInvocationNeedsFailClosedBinding(argv: string[], cwd: string | u
|
||||
}
|
||||
return pnpmDlxTailNeedsFailClosedBinding(argv.slice(idx + 1), cwd);
|
||||
}
|
||||
const flag = normalizeOptionFlag(token);
|
||||
const parsedOption = parseInlineOptionToken(token);
|
||||
const flag = normalizeLowercaseStringOrEmpty(parsedOption.name);
|
||||
if (PNPM_OPTIONS_WITH_VALUE.has(flag) || PNPM_DLX_OPTIONS_WITH_VALUE.has(flag)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (PNPM_CASE_SENSITIVE_OPTIONS_WITH_VALUE.has(parsedOption.name)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (PNPM_FLAG_OPTIONS.has(flag)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -1036,7 +838,8 @@ function pnpmDlxTailNeedsFailClosedBinding(argv: string[], cwd: string | undefin
|
||||
if (!token.startsWith("-")) {
|
||||
return pnpmDlxTailMayNeedStableBinding(argv.slice(idx), cwd);
|
||||
}
|
||||
const flag = normalizeOptionFlag(token);
|
||||
const parsedOption = parseInlineOptionToken(token);
|
||||
const flag = normalizeLowercaseStringOrEmpty(parsedOption.name);
|
||||
if (flag === "-c" || flag === "--shell-mode") {
|
||||
return false;
|
||||
}
|
||||
@@ -1044,6 +847,10 @@ function pnpmDlxTailNeedsFailClosedBinding(argv: string[], cwd: string | undefin
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (PNPM_CASE_SENSITIVE_OPTIONS_WITH_VALUE.has(parsedOption.name)) {
|
||||
idx += token.includes("=") ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (PNPM_FLAG_OPTIONS.has(flag)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user