Skip to content

feat: support non-image binary file attachments inbound (PDFs, office docs, video) on Slack/Discord #738

@ShinyChang

Description

@ShinyChang

Description

Inbound non-image, non-audio, non-text-extension files (PDFs, .docx, .xlsx, .pptx, video, archives, etc.) are silently dropped by the Slack and Discord adapters. The agent receives the user's text prompt with no indication that an attachment was even sent.

Current adapter routing on main (after #291):

  • image/* → resize + base64 → ContentBlock::Image
  • audio/* → STT transcript → ContentBlock::Text
  • text-extension files → inline content → ContentBlock::Text
  • everything else → silently dropped

This was previously raised in #254 ("support non-image file attachments (download to disk)"), where maintainers laid out a hybrid design and a security checklist. That issue was closed with Close in favor of #652, but #652 is the outbound images/files docs PR (docs: add sendimages.md and sendfiles.md), which doesn't address inbound binary handling. The inbound gap remains in main as of v0.8.3-beta.2.

Requesting we revive the design from #254 and land a download_to_disk fallback for binary attachments.

Use Case

Production deployment running openab-claude as a Slack bot for an internal team. Users routinely drop .xlsx reports, .docx drafts, .pdf documents, and .mp4 screen recordings into bot threads.

The bot has Anthropic's official document skills (docx, pdf, pptx, xlsx) plus a video-analysis skill mounted, with their runtime dependencies (LibreOffice, pandoc, Tesseract, ffmpeg) installed in the container. The skills can process every one of these formats — but openab never tells the agent the file exists, so the skills never trigger.

Concrete user-facing failure: user uploads report.xlsx and asks @bot summarize this. Bot responds with something like "I don't see any attachment in this thread." This is misleading and forces users to manually paste content as text or share via out-of-band channels.

This isn't a niche workflow, drag-and-drop file upload is the default mental model for Slack/Discord users. Every non-image attachment hitting an openab-driven bot today silently goes nowhere.

Proposed Solution

Implement the design from #254: a download_to_disk fallback in src/media.rs, used when a file isn't audio / inline-text / image. Returns a ContentBlock::Text with a structured "[Attachment received]" header pointing to the on-disk path, so agents with file-reading tools can pick it up.

Reference implementation (POC)

A working POC has been running in production for several days. Branch:

https://github.com/ShinyChang/openab/tree/feat/download-to-disk-attachments

Diff: main...ShinyChang:openab:feat/download-to-disk-attachments

Touches three files:

  • src/media.rs — adds download_to_disk() + sanitize_path_component()
  • src/slack.rs — replaces image-only fallback with image-OR-disk (bucket = ts)
  • src/discord.rs — same shape (bucket = msg.id)

Files land at ${OPENAB_ATTACHMENTS_DIR:-/tmp/openab-attachments}/<bucket_id>/<filename>. Tested in production with .xlsx / .pdf / .mp4 attachments via Slack — agent receives the path block, triggers the matching skill, processes the file end-to-end.

Known gaps vs the #254 security checklist

The POC ships fast but does not yet meet the bar maintainers proposed in #254:

  • Filename sanitization (path separators, null bytes, control chars)
  • Path containment check after resolve (prevent symlink / .. escape)
  • SSRF guard on download URL (block private IP ranges)
  • Cleanup after stream_prompt returns (collect PathBufs, remove_dir_all)
  • <<<EXTERNAL_UNTRUSTED_CONTENT>>> markers around file path block
  • Config-driven ([<adapter>.attachments] TOML section vs current env-var only)
  • 200 MB hardcoded cap → should be configurable

Hybrid (small-text inline vs disk-fallback) is already covered by the existing is_text_file branch landing first in the routing chain, so no new logic needed there.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions