mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 19:28:28 +00:00
fix(security): harden safeBins long-option validation
This commit is contained in:
@@ -134,17 +134,23 @@ export const SAFE_BIN_PROFILE_FIXTURES: Record<string, SafeBinProfileFixture> =
|
||||
"--key",
|
||||
"--field-separator",
|
||||
"--buffer-size",
|
||||
"--temporary-directory",
|
||||
"--parallel",
|
||||
"--batch-size",
|
||||
"--random-source",
|
||||
"-k",
|
||||
"-t",
|
||||
"-S",
|
||||
"-T",
|
||||
],
|
||||
// --compress-program can invoke an external executable and breaks stdin-only guarantees.
|
||||
deniedFlags: ["--compress-program", "--files0-from", "--output", "-o"],
|
||||
// --random-source/--temporary-directory/-T are filesystem-dependent and not stdin-only.
|
||||
deniedFlags: [
|
||||
"--compress-program",
|
||||
"--files0-from",
|
||||
"--output",
|
||||
"--random-source",
|
||||
"--temporary-directory",
|
||||
"-T",
|
||||
"-o",
|
||||
],
|
||||
},
|
||||
uniq: {
|
||||
maxPositional: 0,
|
||||
@@ -293,6 +299,38 @@ function isInvalidValueToken(value: string | undefined): boolean {
|
||||
return !value || !isSafeLiteralToken(value);
|
||||
}
|
||||
|
||||
function collectKnownLongFlags(
|
||||
allowedValueFlags: ReadonlySet<string>,
|
||||
deniedFlags: ReadonlySet<string>,
|
||||
): string[] {
|
||||
const known = new Set<string>();
|
||||
for (const flag of allowedValueFlags) {
|
||||
if (flag.startsWith("--")) {
|
||||
known.add(flag);
|
||||
}
|
||||
}
|
||||
for (const flag of deniedFlags) {
|
||||
if (flag.startsWith("--")) {
|
||||
known.add(flag);
|
||||
}
|
||||
}
|
||||
return Array.from(known);
|
||||
}
|
||||
|
||||
function resolveCanonicalLongFlag(flag: string, knownLongFlags: string[]): string | null {
|
||||
if (!flag.startsWith("--") || flag.length <= 2) {
|
||||
return null;
|
||||
}
|
||||
if (knownLongFlags.includes(flag)) {
|
||||
return flag;
|
||||
}
|
||||
const matches = knownLongFlags.filter((candidate) => candidate.startsWith(flag));
|
||||
if (matches.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
return matches[0] ?? null;
|
||||
}
|
||||
|
||||
function consumeLongOptionToken(
|
||||
args: string[],
|
||||
index: number,
|
||||
@@ -301,13 +339,22 @@ function consumeLongOptionToken(
|
||||
allowedValueFlags: ReadonlySet<string>,
|
||||
deniedFlags: ReadonlySet<string>,
|
||||
): number {
|
||||
if (deniedFlags.has(flag)) {
|
||||
const knownLongFlags = collectKnownLongFlags(allowedValueFlags, deniedFlags);
|
||||
const canonicalFlag = resolveCanonicalLongFlag(flag, knownLongFlags);
|
||||
if (!canonicalFlag) {
|
||||
return -1;
|
||||
}
|
||||
if (deniedFlags.has(canonicalFlag)) {
|
||||
return -1;
|
||||
}
|
||||
const expectsValue = allowedValueFlags.has(canonicalFlag);
|
||||
if (inlineValue !== undefined) {
|
||||
if (!expectsValue) {
|
||||
return -1;
|
||||
}
|
||||
return isSafeLiteralToken(inlineValue) ? index + 1 : -1;
|
||||
}
|
||||
if (!allowedValueFlags.has(flag)) {
|
||||
if (!expectsValue) {
|
||||
return index + 1;
|
||||
}
|
||||
return isInvalidValueToken(args[index + 1]) ? -1 : index + 2;
|
||||
|
||||
Reference in New Issue
Block a user