media: allow temp-dir MEDIA paths for tool outputs#7400
media: allow temp-dir MEDIA paths for tool outputs#7400grammakov wants to merge 2 commits intoopenclaw:mainfrom
Conversation
| function isSafeTempPath(candidate: string): boolean { | ||
| const resolved = path.resolve(candidate); | ||
| const tempRoots = getTempRoots(); |
There was a problem hiding this comment.
[P1] Temp-root comparison can be bypassed on case-insensitive filesystems (Windows/macOS) because path.relative() is case-sensitive.
If os.tmpdir() returns a different casing than the candidate path (or attacker-controlled path casing), path.relative(tempRoot, resolved) can start with .. even when the path is actually within the same directory, causing false negatives. Conversely, if some tempRoot comes in with different casing, a path outside could be misclassified as inside on a case-insensitive volume. Consider normalizing both tempRoot and resolved for platform case-sensitivity (e.g., toLowerCase() on win32/darwin, or use realpath-based normalization if available).
Do you expect this code to run on case-insensitive macOS volumes or Windows, and should temp containment treat paths case-insensitively there?
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/media/parse.ts
Line: 40:42
Comment:
[P1] Temp-root comparison can be bypassed on case-insensitive filesystems (Windows/macOS) because `path.relative()` is case-sensitive.
If `os.tmpdir()` returns a different casing than the candidate path (or attacker-controlled path casing), `path.relative(tempRoot, resolved)` can start with `..` even when the path is actually within the same directory, causing false negatives. Conversely, if some tempRoot comes in with different casing, a path outside could be misclassified as inside on a case-insensitive volume. Consider normalizing both `tempRoot` and `resolved` for platform case-sensitivity (e.g., `toLowerCase()` on `win32`/`darwin`, or use `realpath`-based normalization if available).
Do you expect this code to run on case-insensitive macOS volumes or Windows, and should temp containment treat paths case-insensitively there?
How can I resolve this? If you propose a fix, please make it concise.
src/media/parse.test.ts
Outdated
| it("accepts paths under /tmp on POSIX", function () { | ||
| if (path.sep !== "/") { | ||
| // Skip on Windows | ||
| return; | ||
| } | ||
| const testPath = path.join("/tmp", "tts-abc123", "voice.opus"); | ||
| const result = splitMediaFromOutput(`MEDIA:${testPath}`); | ||
| expect(result.mediaUrls).toHaveLength(1); | ||
| expect(result.mediaUrls?.[0]).toBe(testPath); | ||
| }); |
There was a problem hiding this comment.
[P3] This test uses an early return to skip on Windows; Vitest has it.skipIf(...) / describe.skipIf(...) patterns that make skips more visible in test output.
Not required for correctness, but switching to an explicit skip helper can make CI results clearer when the test is intentionally not executed on some platforms.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/media/parse.test.ts
Line: 74:83
Comment:
[P3] This test uses an early `return` to skip on Windows; Vitest has `it.skipIf(...)` / `describe.skipIf(...)` patterns that make skips more visible in test output.
Not required for correctness, but switching to an explicit skip helper can make CI results clearer when the test is intentionally not executed on some platforms.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.MEDIA paths from internal tools like `tts` are rejected by `isValidMedia()` because they use absolute paths under the OS temp directory (e.g., `/tmp/tts-abc123/voice.opus`). This causes TTS voice responses to be sent as plain text markup instead of voice messages, since the media file path fails validation. Solution: - Add `getTempRoots()` to identify valid temp directories (os.tmpdir() + /tmp on POSIX for macOS compatibility) - Add `isSafeTempPath()` using path.relative() for traversal-safe containment checking - Update `isValidMedia()` to allow temp paths Security considerations: - Only paths resolving strictly inside temp directories are allowed - Traversal-safe: path.resolve() normalizes ".." before comparison - Cross-platform: uses path.relative() for Windows compatibility - Prefix attack protection: path.relative() correctly handles /tmp-evil/ vs /tmp Co-Authored-By: Claude Opus 4.5 <[email protected]>
920a027 to
281ef8d
Compare
…#7400) Security fix openclaw#4930 blocked all absolute paths in isValidMedia() to prevent LFI, but this also blocked paths generated by internal tools like TTS which write to OS temp directories (/var/folders/... on macOS, /tmp on Linux). Allow absolute paths that safely resolve under os.tmpdir() or /tmp, with path traversal protection and case-insensitive comparison for macOS/Windows. Co-Authored-By: grammakov <[email protected]>
bfc1ccb to
f92900f
Compare
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
|
This pull request has been automatically marked as stale due to inactivity. |
|
Closing due to inactivity. |
Problem
MEDIA paths from internal tools like
ttsare rejected byisValidMedia()because they use absolute paths under the OS temp directory (e.g.,/tmp/tts-abc123/voice.opus).This causes TTS voice responses to be sent as plain text markup instead of voice messages, since the media file path fails validation.
Reproduction
messages.tts.auto: "tagged"MEDIA:<temp-path>[[tts]]markup as plain textNote: A local hotfix patching
dist/was applied to verify the fix; this PR targets the source.Solution
Allow absolute paths that safely resolve under temp directories:
os.tmpdir()AND/tmpon POSIX (macOS returns/var/folders/...but tools often use/tmp)path.resolve()path.relative()to safely determine containment./paths, and traversal blockingSecurity considerations
path.resolve()normalizes..before comparisonpath.relative()andpath.isAbsolute()for Windows compatibilitypath.relative()correctly handles/tmp-evil/vs/tmpChanges
src/media/parse.ts: AddgetTempRoots()andisSafeTempPath()helpers, updateisValidMedia()to allow temp pathssrc/media/parse.test.ts: Add OS-portable test cases for temp path acceptance and traversal rejectionTesting
pnpm vitest run src/media/parse.test.ts— all 16 tests passpnpm build— compiles successfullyAI Disclosure
This PR was developed with AI assistance (Claude). The code has been reviewed and tested locally.
Greptile Overview
Greptile Summary
This PR extends media token validation to support absolute
MEDIA:file paths that resolve under OS temp directories (viaos.tmpdir()and/tmpon POSIX), allowing internal tools like TTS to attach generated audio files instead of leaving[[tts]]markup in the message. It adds helper functions (getTempRoots,isSafeTempPath) and new Vitest coverage for accepted temp paths and rejected traversal/prefix attacks.Overall, the change fits into the existing
splitMediaFromOutputpipeline by relaxingisValidMedia()only for a tightly-scoped class of absolute paths while keeping URL handling and./relative path requirements unchanged.Confidence Score: 4/5
path.resolve+path.relativecontainment checks plus tests for traversal and prefix edge cases. Main remaining concern is platform-specific path/case semantics (especially Windows/case-insensitive volumes) that could cause incorrect accept/reject behavior.(2/5) Greptile learns from your feedback when you react with thumbs up/down!