mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-30 11:45:04 +00:00
fix(imessage): sanitize SCP remote path to prevent shell metacharacter injection
References GHSA-g2f6-pwvx-r275.
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isSafeScpRemoteHost, normalizeScpRemoteHost } from "./scp-host.js";
|
||||
import {
|
||||
isSafeScpRemoteHost,
|
||||
isSafeScpRemotePath,
|
||||
normalizeScpRemoteHost,
|
||||
normalizeScpRemotePath,
|
||||
} from "./scp-host.js";
|
||||
|
||||
describe("scp remote host", () => {
|
||||
it.each([
|
||||
@@ -33,3 +38,40 @@ describe("scp remote host", () => {
|
||||
expect(isSafeScpRemoteHost(value)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("scp remote path", () => {
|
||||
it.each([
|
||||
{
|
||||
value: "/Users/demo/Library/Messages/Attachments/ab/cd/photo.jpg",
|
||||
expected: "/Users/demo/Library/Messages/Attachments/ab/cd/photo.jpg",
|
||||
},
|
||||
{
|
||||
value: " /Users/demo/Library/Messages/Attachments/ab/cd/IMG 1234 (1).jpg ",
|
||||
expected: "/Users/demo/Library/Messages/Attachments/ab/cd/IMG 1234 (1).jpg",
|
||||
},
|
||||
])("normalizes safe paths for %j", ({ value, expected }) => {
|
||||
expect(normalizeScpRemotePath(value)).toBe(expected);
|
||||
expect(isSafeScpRemotePath(value)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
null,
|
||||
undefined,
|
||||
"",
|
||||
" ",
|
||||
"relative/path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad$path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad`path`.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad;path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad|path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad&path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad<path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad>path.jpg",
|
||||
'/Users/demo/Library/Messages/Attachments/ab/cd/bad"path.jpg',
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad'path.jpg",
|
||||
"/Users/demo/Library/Messages/Attachments/ab/cd/bad\\path.jpg",
|
||||
])("rejects unsafe path tokens: %j", (value) => {
|
||||
expect(normalizeScpRemotePath(value)).toBeUndefined();
|
||||
expect(isSafeScpRemotePath(value)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const SSH_TOKEN = /^[A-Za-z0-9._-]+$/;
|
||||
const BRACKETED_IPV6 = /^\[[0-9A-Fa-f:.%]+\]$/;
|
||||
const WHITESPACE = /\s/;
|
||||
const SCP_REMOTE_PATH_UNSAFE_CHARS = new Set(["\\", "'", '"', "`", "$", ";", "|", "&", "<", ">"]);
|
||||
|
||||
function hasControlOrWhitespace(value: string): boolean {
|
||||
for (const char of value) {
|
||||
@@ -60,3 +61,26 @@ export function normalizeScpRemoteHost(value: string | null | undefined): string
|
||||
export function isSafeScpRemoteHost(value: string | null | undefined): boolean {
|
||||
return normalizeScpRemoteHost(value) !== undefined;
|
||||
}
|
||||
|
||||
export function normalizeScpRemotePath(value: string | null | undefined): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || !trimmed.startsWith("/")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const char of trimmed) {
|
||||
const code = char.charCodeAt(0);
|
||||
if (code <= 0x1f || code === 0x7f || SCP_REMOTE_PATH_UNSAFE_CHARS.has(char)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function isSafeScpRemotePath(value: string | null | undefined): boolean {
|
||||
return normalizeScpRemotePath(value) !== undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user