Shared utility providing a single source of truth for formatting and parsing chat attachment markdown between the host-side bubble renderer and server-side LLM input cleaner, preventing drift between the two systems. ## Key Components | Export | Type | Description | |--------|------|-------------| | `CHAT_ATTACHMENT_VIEW_URL_PREFIX` | `const` | Hub-default proxy URL prefix for attachment view routes | | `CHAT_ATTACHMENT_VIEW_TOKEN_QUERY_PARAM` | `const` | Query param name (`t`) for the HMAC view token | | `ANTHROPIC_SUPPORTED_IMAGE_MIME` | `const` | Image MIME types renderable as Anthropic image blocks | | `ChatAttachment` | `interface` | Wire shape for a single chat attachment | | `buildChatAttachmentViewUrl` | `function` | Constructs the signed proxy URL for an attachment | | `escapeMarkdownInline` | `function` | Sanitizes filenames against markdown injection attacks | | `formatChatAttachmentMarkdownForBubble` | `function` | Produces the `![]()` or `[Attached: ...]` markdown line | | `CHAT_ATTACHMENT_MARKDOWN_PATTERN` | `RegExp` | Anchored regex matching both image and link attachment forms | | `stripChatAttachmentMarkdown` | `function` | Strips attachment markdown from text and extracts storage paths | ## Usage Example ```typescript import { CHAT_ATTACHMENT_VIEW_URL_PREFIX, formatChatAttachmentMarkdownForBubble, stripChatAttachmentMarkdown, } from './chat-attachment-markdown' // Host-side: format attachment for user bubble (pass runtime prefix) const attachment: ChatAttachment = { storagePath: 'org-123/chat/screenshot.png', viewToken: 'hmac-signed-token', contentType: 'image/png', fileName: 'screenshot.png', size: 204800, } // Embedded app overrides prefix; hub uses the constant directly const markdown = formatChatAttachmentMarkdownForBubble( attachment, 'https://hub.example.com/api/storage/view/chat-attachments/', ) // → "\n\n![screenshot.png](https://hub.example.com/api/storage/view/...?t=...)" // Server-side: strip attachment lines before sending to LLM const userMessage = `Please review this\n\n![screenshot.png](/api/storage/view/chat-attachments/org-123/chat/screenshot.png?t=abc)` const { stripped, storagePaths } = stripChatAttachmentMarkdown(userMessage) // stripped → "Please review this" // storagePaths → ["org-123/chat/screenshot.png"] ``` ## Security Notes - **Markdown injection**: `escapeMarkdownInline` escapes `[`, `]`, `(`, `)`, `\`, `<`, `>`, and `"` from filenames — preventing a crafted name like `screenshot](https://evil.com).png` from hijacking the image URL. - **Anthropic image blocks**: raw Supabase signed URLs are used (not the proxy) because Anthropic's fetcher does not reliably follow `302` redirects. - **View tokens**: HMAC-signed server-side; the client never generates them.