mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 17:44:57 +00:00
fix: harden sandbox tmp media validation (#17892) (thanks @dashed)
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Security/macOS app beta: enforce path-only `system.run` allowlist matching (drop basename matches like `echo`), migrate legacy basename entries to last resolved paths when available, and harden shell-chain handling to fail closed on unsafe parse/control syntax (including quoted command substitution/backticks). This is an optional allowlist-mode feature; default installs remain deny-by-default. This ships in the next npm release. Thanks @tdjackey for reporting.
|
- Security/macOS app beta: enforce path-only `system.run` allowlist matching (drop basename matches like `echo`), migrate legacy basename entries to last resolved paths when available, and harden shell-chain handling to fail closed on unsafe parse/control syntax (including quoted command substitution/backticks). This is an optional allowlist-mode feature; default installs remain deny-by-default. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||||
- Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), and centralize range checks into a single CIDR policy table to reduce classifier drift.
|
- Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), and centralize range checks into a single CIDR policy table to reduce classifier drift.
|
||||||
- Security/Archive: block zip symlink escapes during archive extraction.
|
- Security/Archive: block zip symlink escapes during archive extraction.
|
||||||
|
- Security/Media sandbox: keep tmp media allowance for absolute tmp paths only and enforce symlink-escape checks before sandbox-validated reads, preventing tmp symlink exfiltration and relative `../` sandbox escapes when sandboxes live under tmp. (#17892) Thanks @dashed.
|
||||||
- Security/Discord: add `openclaw security audit` warnings for name/tag-based Discord allowlist entries (DM allowlists, guild/channel `users`, and pairing-store entries), highlighting slug-collision risk while keeping name-based matching supported, and canonicalize resolved Discord allowlist names to IDs at runtime without rewriting config files. Thanks @tdjackey for reporting.
|
- Security/Discord: add `openclaw security audit` warnings for name/tag-based Discord allowlist entries (DM allowlists, guild/channel `users`, and pairing-store entries), highlighting slug-collision risk while keeping name-based matching supported, and canonicalize resolved Discord allowlist names to IDs at runtime without rewriting config files. Thanks @tdjackey for reporting.
|
||||||
- Security/Gateway: block node-role connections when device identity metadata is missing.
|
- Security/Gateway: block node-role connections when device identity metadata is missing.
|
||||||
- Security/Media: enforce inbound media byte limits during download/read across Discord, Telegram, Zalo, Microsoft Teams, and BlueBubbles to prevent oversized payload memory spikes before rejection. This ships in the next npm release. Thanks @tdjackey for reporting.
|
- Security/Media: enforce inbound media byte limits during download/read across Discord, Telegram, Zalo, Microsoft Teams, and BlueBubbles to prevent oversized payload memory spikes before rejection. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||||
|
|||||||
@@ -88,6 +88,39 @@ describe("resolveSandboxedMediaSource", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects relative traversal outside sandbox even when sandbox root is under tmpdir", async () => {
|
||||||
|
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "sandbox-media-"));
|
||||||
|
try {
|
||||||
|
await expect(
|
||||||
|
resolveSandboxedMediaSource({
|
||||||
|
media: "../outside-sandbox.png",
|
||||||
|
sandboxRoot: sandboxDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/sandbox/i);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects symlinked tmpdir paths escaping tmpdir", async () => {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "sandbox-media-"));
|
||||||
|
const symlinkPath = path.join(sandboxDir, "tmp-link-escape");
|
||||||
|
try {
|
||||||
|
await fs.symlink("/etc/passwd", symlinkPath);
|
||||||
|
await expect(
|
||||||
|
resolveSandboxedMediaSource({
|
||||||
|
media: symlinkPath,
|
||||||
|
sandboxRoot: sandboxDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(/symlink|sandbox/i);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects file:// URLs outside sandbox", async () => {
|
it("rejects file:// URLs outside sandbox", async () => {
|
||||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "sandbox-media-"));
|
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "sandbox-media-"));
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -90,10 +90,11 @@ export async function resolveSandboxedMediaSource(params: {
|
|||||||
throw new Error(`Invalid file:// URL for sandboxed media: ${raw}`);
|
throw new Error(`Invalid file:// URL for sandboxed media: ${raw}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Allow files under os.tmpdir() — consistent with buildMediaLocalRoots() defaults.
|
const resolved = path.resolve(resolveSandboxInputPath(candidate, params.sandboxRoot));
|
||||||
const resolved = path.resolve(params.sandboxRoot, candidate);
|
const tmpDir = path.resolve(os.tmpdir());
|
||||||
const tmpDir = os.tmpdir();
|
const candidateIsAbsolute = path.isAbsolute(expandPath(candidate));
|
||||||
if (resolved === tmpDir || resolved.startsWith(tmpDir + path.sep)) {
|
if (candidateIsAbsolute && isPathInside(tmpDir, resolved)) {
|
||||||
|
await assertNoSymlinkEscape(path.relative(tmpDir, resolved), tmpDir);
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
const sandboxResult = await assertSandboxPath({
|
const sandboxResult = await assertSandboxPath({
|
||||||
|
|||||||
Reference in New Issue
Block a user