Skip to content

fix(line): migrate media downloads to saveMediaBuffer for sandbox access#42010

Closed
carrotRakko wants to merge 2 commits into
openclaw:mainfrom
delight-co:fix/line-media-sandbox-access
Closed

fix(line): migrate media downloads to saveMediaBuffer for sandbox access#42010
carrotRakko wants to merge 2 commits into
openclaw:mainfrom
delight-co:fix/line-media-sandbox-access

Conversation

@carrotRakko

Copy link
Copy Markdown
Contributor

Summary

  • Problem: LINE media downloads are saved to /tmp via buildRandomTempFilePath, which is outside the sandbox root (~/.openclaw/media/). Sandboxed agents cannot access downloaded LINE media files (images, videos, audio, documents).
  • Why it matters: Any LINE deployment using sandbox mode silently loses media context — the agent receives <media:...> placeholders but the files are inaccessible.
  • What changed: downloadLineMedia now uses saveMediaBuffer (writes to ~/.openclaw/media/inbound/), captures the Content-Type header from LINE API responses, and passes originalFilename from file messages through to detectMime for extension-based MIME fallback.
  • What did NOT change (scope boundary): No changes to non-LINE media paths. No changes to saveMediaBuffer itself (only the filePath argument to detectMime was missing). No new dependencies.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • LINE media files (image, video, audio, file) are now saved to ~/.openclaw/media/inbound/ instead of /tmp/openclaw/. Sandboxed agents can access these files.
  • Content-Type from LINE API response headers is now used for MIME detection, improving accuracy over buffer-only sniffing.
  • LINE file messages (PDF, DOCX, etc.) now preserve the original filename in the saved file path, enabling extension-based MIME fallback for uncommon file types.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No — same LINE API endpoint, switched from getMessageContent to getMessageContentWithHttpInfo (same HTTP call, richer response object)
  • Command/tool execution surface changed? No
  • Data access scope changed? No — files move from /tmp to ~/.openclaw/media/inbound/, both are local storage. saveMediaBuffer applies the same size limits.
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • Runtime/container: Node.js in Docker
  • Integration/channel: LINE Messaging API
  • Relevant config: channels.line with sandbox enabled

Steps

  1. Configure a LINE channel with sandbox mode enabled
  2. Send an image or PDF file to the LINE bot
  3. Check ~/.openclaw/media/inbound/ for the downloaded file

Expected

  • Media file appears in ~/.openclaw/media/inbound/ with correct MIME type and extension
  • Agent can access the file within the sandbox

Actual (before fix)

  • Media file saved to /tmp/openclaw/line-media-*.ext
  • Sandboxed agent cannot access the file (outside sandbox root)

Evidence

  • Failing test/log before + passing after
  • All existing tests pass. download.test.ts tests updated to match the new saveMediaBuffer-based flow (mock saveMediaBuffer instead of fs.writeFile).

Human Verification (required)

  • Verified scenarios: Reviewed upstream download.ts, bot-handlers.ts, and store.ts to confirm the changes are still applicable (no upstream changes to these code paths since the original fork implementation).
  • Edge cases checked: originalFilename is undefined for non-file message types (image/video/audio) — saveMediaBuffer handles this gracefully. Oversized media rejection still works (streaming size check before saveMediaBuffer call).
  • What you did not verify: Live LINE API integration test (requires LINE channel credentials and sandbox environment).

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

N/A — fresh PR, no review conversations yet.

Compatibility / Migration

  • Backward compatible? Yes — media files are now saved to a different path, but all consumers use the returned path from downloadLineMedia, not hardcoded paths.
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert commit, LINE media downloads fall back to /tmp storage (non-sandbox-accessible).
  • Files/config to restore: None
  • Known bad symptoms reviewers should watch for: LINE media files not appearing in ~/.openclaw/media/inbound/ after download.

Risks and Mitigations

  • Risk: getMessageContentWithHttpInfo API may behave differently across @line/bot-sdk versions (response shape).
    • Mitigation: The SDK types guarantee httpResponse.headers exists. The Content-Type header extraction has a ?? undefined fallback for missing headers.

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)

@greptile-apps

greptile-apps Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a sandbox access regression for LINE media downloads by migrating from /tmp-based writes (buildRandomTempFilePath + fs.writeFile) to saveMediaBuffer, which stores files under ~/.openclaw/media/inbound/ — the sandbox-accessible location. The fix also captures the Content-Type response header from LINE's API for more accurate MIME detection, and threads message.fileName through as originalFilename so that extension-based MIME fallback works for uncommon document types (PDF, DOCX, etc.).

Key findings:

  • The core logic is sound: the streaming size check in downloadLineMedia fires before saveMediaBuffer's own size check, so oversized media is still correctly rejected before any disk write.
  • originalFilename (from user-controlled message.fileName) is safely sanitized by sanitizeFilename in store.ts before path construction, preventing path traversal.
  • Minor style issue: The outer variable in download.ts is named httpResponse, matching an inner property of the SDK return value, creating a confusing double-dereference (httpResponse.httpResponse.headers). A simple variable rename clarifies the intent.

Confidence Score: 4/5

  • This PR is safe to merge. The security-sensitive originalFilename input is properly sanitized before path construction, and the core media download logic correctly enforces size limits before persisting to disk.
  • The fix is correct and the implementation is secure. The streaming size check prevents oversized media from reaching the filesystem, and sanitizeFilename in store.ts prevents path-traversal attacks on the user-controlled originalFilename. All existing tests pass and have been appropriately updated. The one deduction is for a minor style inconsistency in variable naming that affects readability but not functionality.
  • src/line/download.ts — minor style issue with variable naming (double-dereference httpResponse.httpResponse) addressed in review comment.

Last reviewed commit: b89fd9a

Comment thread src/line/download.ts Outdated
@carrotRakko

Copy link
Copy Markdown
Contributor Author

saveMediaBuffer is an existing utility — this PR only migrates LINE's download path to use it instead of writing to /tmp directly. The symlink/race hardening suggestion applies to saveMediaBuffer itself, not to the changes in this PR.

The attack prerequisite (write access to ~/.openclaw/media/, created with mode: 0o700) implies the attacker already has the user's privileges, making the symlink vector redundant. Out of scope for this PR.

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)

Mitsuyuki Osabe added 2 commits March 10, 2026 20:03
…nd improve MIME detection

Migrate LINE media downloads from buildRandomTempFilePath (/tmp) to
saveMediaBuffer (~/.openclaw/media/inbound/) so files are accessible
inside the sandbox.  Also:

- Switch from getMessageContent to getMessageContentWithHttpInfo to
  capture the Content-Type header from LINE API responses.
- Pass originalFilename (from LINE file messages) through to
  saveMediaBuffer, enabling extension-based MIME fallback in detectMime.
- Remove the local detectContentType/getExtensionForContentType helpers
  (now handled by saveMediaBuffer's detectMime pipeline).

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
Addresses Greptile review: `httpResponse.httpResponse.headers` was
confusing because the outer variable shadowed the SDK property name.

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 27, 2026
@clawsweeper

clawsweeper Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I did a careful shell check against current main, and this is already implemented.

Current main now implements this PR's central LINE sandbox-access fix in the active bundled plugin layout: LINE inbound media is persisted through saveMediaBuffer(..., "inbound", ...) instead of temp storage, tests assert the shared media-store path and oversized-media guard, and docs/changelog record the behavior. The branch's optional Content-Type/original-filename MIME refinements are not fully present, but the observable sandbox-access bug named by the title and PR body is fixed well enough on main; any remaining MIME fidelity work should be a narrower follow-up.

Best possible solution:

Close this PR as implemented on main by 4e921808d18adaab19445f82d0a886028ce4942c. Keep the shipped extensions/line implementation that persists inbound LINE media through the plugin SDK media store, and open a smaller follow-up only if LINE Content-Type header or original filename MIME refinement is still desired.

What I checked:

  • Current implementation saves LINE media in the shared inbound media store: downloadLineMedia imports saveMediaBuffer from the plugin SDK and calls saveMediaBuffer(buffer, contentType, "inbound", maxBytes), then returns saved.path and saved.contentType instead of a temp file path. (extensions/line/src/download.ts:37, db40ec404a91)
  • Fix commit on main: Commit 4e921808d18adaab19445f82d0a886028ce4942c is titled fix(line): persist inbound media in shared store and changes the LINE downloader, its tests, docs, and changelog. (extensions/line/src/download.ts:37, 4e921808d18a)
  • Regression tests assert media-store persistence: The LINE download test mocks saveMediaBuffer, asserts the buffer, image/jpeg content type, "inbound" subdir, default max byte limit, and returned ~/.openclaw/media/inbound/... path. It also asserts oversized media is rejected before invoking saveMediaBuffer. (extensions/line/src/download.test.ts:59, db40ec404a91)
  • Sandbox staging requires media-store-rooted local files: Current sandbox staging only accepts local media under getMediaDir(), which is why moving LINE media to ~/.openclaw/media/inbound/ solves the reported sandbox access failure. (src/auto-reply/reply/stage-sandbox-media.ts:218, db40ec404a91)
  • Docs and changelog record the shipped behavior: The LINE docs state inbound media is saved under ~/.openclaw/media/inbound/; the changelog records the LINE fix under Unreleased and credits the related report. No tag currently contains the fix commit, so no fixed release is identified. Public docs: docs/channels/line.md. (docs/channels/line.md:145, db40ec404a91)
  • Current plugin layout supersedes the PR's old paths: The checkout has extensions/line/src/* as the active LINE plugin path and no src/line directory, so the PR's legacy src/line/* diff is obsolete even though its central behavior has landed. (extensions/line/src/download.ts:1, db40ec404a91)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Codex review notes: model gpt-5.5, reasoning high; reviewed against db40ec404a91; fix evidence: commit 4e921808d18a.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 28, 2026
@clawsweeper clawsweeper Bot closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support LINE file message type (PDF, etc.) in inbound media download

1 participant