CVE-2026-32846
Description
OpenClaw through 2026.3.23 (fixed in commit 4797bbc) contains a path traversal vulnerability in media parsing that allows attackers to read arbitrary files by bypassing path validation in the isLikelyLocalPath() and isValidMedia() functions. Attackers can exploit incomplete validation and the allowBareFilename bypass to reference files outside the intended application sandbox, resulting in disclosure of sensitive information including system files, environment files, and SSH keys.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.03.28 | 2026.03.28 |
Affected products
1Patches
14797bbc5b96efix: reject path traversal and home-dir patterns in media parse layer (#54642)
2 files changed · +52 −7
src/media/parse.test.ts+15 −2 modified@@ -12,8 +12,6 @@ describe("splitMediaFromOutput", () => { const pathCases = [ ["/Users/pete/My File.png", "MEDIA:/Users/pete/My File.png"], ["/Users/pete/My File.png", 'MEDIA:"/Users/pete/My File.png"'], - ["~/Pictures/My File.png", "MEDIA:~/Pictures/My File.png"], - ["../../etc/passwd", "MEDIA:../../etc/passwd"], ["./screenshots/image.png", "MEDIA:./screenshots/image.png"], ["media/inbound/image.png", "MEDIA:media/inbound/image.png"], ["./screenshot.png", " MEDIA:./screenshot.png"], @@ -31,6 +29,21 @@ describe("splitMediaFromOutput", () => { } }); + it("rejects traversal and home-dir paths and strips them from output", () => { + const traversalCases = [ + "MEDIA:../../../etc/passwd", + "MEDIA:../../.env", + "MEDIA:~/.ssh/id_rsa", + "MEDIA:~/Pictures/My File.png", + "MEDIA:./foo/../../../etc/shadow", + ]; + for (const input of traversalCases) { + const result = splitMediaFromOutput(input); + expect(result.mediaUrls, `should reject media: ${input}`).toBeUndefined(); + expect(result.text, `should strip from text: ${input}`).toBe(""); + } + }); + it("keeps audio_as_voice detection stable across calls", () => { const input = "Hello [[audio_as_voice]]"; const first = splitMediaFromOutput(input);
src/media/parse.ts+37 −5 modified@@ -18,10 +18,21 @@ const WINDOWS_DRIVE_RE = /^[a-zA-Z]:[\\/]/; const SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:/; const HAS_FILE_EXT = /\.\w{1,10}$/; -// Recognize local file path patterns. Security validation is deferred to the -// load layer (loadWebMedia / resolveSandboxedMediaSource) which has the context -// needed to enforce sandbox roots and allowed directories. -function isLikelyLocalPath(candidate: string): boolean { +// Matches ".." as a standalone path segment (start, middle, or end). +const TRAVERSAL_SEGMENT_RE = /(?:^|[/\\])\.\.(?:[/\\]|$)/; + +function hasTraversalOrHomeDirPrefix(candidate: string): boolean { + return ( + candidate.startsWith("../") || + candidate === ".." || + candidate.startsWith("~") || + TRAVERSAL_SEGMENT_RE.test(candidate) + ); +} + +// Broad structural check: does this look like a local file path? Used only for +// stripping MEDIA: lines from output text — never for media approval. +function looksLikeLocalFilePath(candidate: string): boolean { return ( candidate.startsWith("/") || candidate.startsWith("./") || @@ -33,6 +44,21 @@ function isLikelyLocalPath(candidate: string): boolean { ); } +// Recognize safe local file path patterns for media approval, rejecting +// traversal and home-dir paths so they never reach downstream load/send logic. +function isLikelyLocalPath(candidate: string): boolean { + if (hasTraversalOrHomeDirPrefix(candidate)) { + return false; + } + return ( + candidate.startsWith("/") || + candidate.startsWith("./") || + WINDOWS_DRIVE_RE.test(candidate) || + candidate.startsWith("\\\\") || + (!SCHEME_RE.test(candidate) && (candidate.includes("/") || candidate.includes("\\"))) + ); +} + function isValidMedia( candidate: string, opts?: { allowSpaces?: boolean; allowBareFilename?: boolean }, @@ -54,6 +80,12 @@ function isValidMedia( return true; } + // Hard reject traversal/home-dir patterns before the bare-filename fallback + // to prevent path traversal bypasses (e.g. "../../.env" matching HAS_FILE_EXT). + if (hasTraversalOrHomeDirPrefix(candidate)) { + return false; + } + // Accept bare filenames (e.g. "image.png") only when the caller opts in. // This avoids treating space-split path fragments as separate media items. if (opts?.allowBareFilename && !SCHEME_RE.test(candidate) && HAS_FILE_EXT.test(candidate)) { @@ -169,7 +201,7 @@ export function splitMediaFromOutput(raw: string): { const trimmedPayload = payloadValue.trim(); const looksLikeLocalPath = - isLikelyLocalPath(trimmedPayload) || trimmedPayload.startsWith("file://"); + looksLikeLocalFilePath(trimmedPayload) || trimmedPayload.startsWith("file://"); if ( !unwrapped && validCount === 1 &&
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/openclaw/openclaw/commit/4797bbc5b96e2cca5532e43b58915c051746fe37nvdPatchWEB
- github.com/openclaw/openclaw/pull/54642nvdExploitIssue TrackingVendor AdvisoryWEB
- github.com/advisories/GHSA-hggm-x7r9-mm7vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-32846ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-media-parsing-path-traversal-to-arbitrary-file-readnvdThird Party AdvisoryWEB
- github.com/openclaw/openclaw/security/advisories/GHSA-f6pf-4gjx-c94rnvdBroken LinkWEB
News mentions
0No linked articles in our index yet.