mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 07:30:21 +00:00
fix(security): restrict local path extraction in media parser to prevent LFI (#4880)
* Media: restrict local path extraction to prevent LFI * Lint: remove unused variable hasValidMediaOnLine
This commit is contained in:
@@ -19,11 +19,9 @@ function isValidMedia(candidate: string, opts?: { allowSpaces?: boolean }) {
|
||||
if (candidate.length > 4096) return false;
|
||||
if (!opts?.allowSpaces && /\s/.test(candidate)) return false;
|
||||
if (/^https?:\/\//i.test(candidate)) return true;
|
||||
if (candidate.startsWith("/")) return true;
|
||||
if (candidate.startsWith("./")) return true;
|
||||
if (candidate.startsWith("../")) return true;
|
||||
if (candidate.startsWith("~")) return true;
|
||||
return false;
|
||||
|
||||
// Local paths: only allow safe relative paths starting with ./ that do not traverse upwards.
|
||||
return candidate.startsWith("./") && !candidate.includes("..");
|
||||
}
|
||||
|
||||
function unwrapQuoted(value: string): string | undefined {
|
||||
@@ -85,10 +83,8 @@ export function splitMediaFromOutput(raw: string): {
|
||||
continue;
|
||||
}
|
||||
|
||||
foundMediaToken = true;
|
||||
const pieces: string[] = [];
|
||||
let cursor = 0;
|
||||
let hasValidMedia = false;
|
||||
|
||||
for (const match of matches) {
|
||||
const start = match.index ?? 0;
|
||||
@@ -101,11 +97,13 @@ export function splitMediaFromOutput(raw: string): {
|
||||
const mediaStartIndex = media.length;
|
||||
let validCount = 0;
|
||||
const invalidParts: string[] = [];
|
||||
let hasValidMedia = false;
|
||||
for (const part of parts) {
|
||||
const candidate = normalizeMediaSource(cleanCandidate(part));
|
||||
if (isValidMedia(candidate, unwrapped ? { allowSpaces: true } : undefined)) {
|
||||
media.push(candidate);
|
||||
hasValidMedia = true;
|
||||
foundMediaToken = true;
|
||||
validCount += 1;
|
||||
} else {
|
||||
invalidParts.push(part);
|
||||
@@ -130,6 +128,7 @@ export function splitMediaFromOutput(raw: string): {
|
||||
if (isValidMedia(fallback, { allowSpaces: true })) {
|
||||
media.splice(mediaStartIndex, media.length - mediaStartIndex, fallback);
|
||||
hasValidMedia = true;
|
||||
foundMediaToken = true;
|
||||
validCount = 1;
|
||||
invalidParts.length = 0;
|
||||
}
|
||||
@@ -140,12 +139,18 @@ export function splitMediaFromOutput(raw: string): {
|
||||
if (isValidMedia(fallback, { allowSpaces: true })) {
|
||||
media.push(fallback);
|
||||
hasValidMedia = true;
|
||||
foundMediaToken = true;
|
||||
invalidParts.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasValidMedia && invalidParts.length > 0) {
|
||||
pieces.push(invalidParts.join(" "));
|
||||
if (hasValidMedia) {
|
||||
if (invalidParts.length > 0) {
|
||||
pieces.push(invalidParts.join(" "));
|
||||
}
|
||||
} else {
|
||||
// If no valid media was found in this match, keep the original token text.
|
||||
pieces.push(match[0]);
|
||||
}
|
||||
|
||||
cursor = start + match[0].length;
|
||||
|
||||
Reference in New Issue
Block a user