← Back to PRs

#10902: fix(msteams): fix inline pasted image downloads

by jlian open 2026-02-07 05:11 View on GitHub →
channel: msteams size: S
Fixes #5448 ## Problem Inline pasted images in MS Teams (clipboard paste, not drag-and-drop file attachments) were silently dropped, and the gateway never received the image bytes. ## Root Cause Four independent bugs in the MSTeams extension's media download pipeline: ### 1. Bot Framework attachment URLs return JSON metadata, not binary content Teams sends pasted images as attachments with Bot Framework URLs like: ``` https://smba.trafficmanager.net/.../v3/attachments/{id} ``` Fetching that bare URL returns a JSON metadata envelope. The binary lives at `/views/original`: ``` https://smba.trafficmanager.net/.../v3/attachments/{id}/views/original ``` **Fix:** Added `isBotFrameworkAttachmentUrl()` regex helper and `/views/original` suffix in `download.ts`. Trailing slashes are stripped before appending. ### 2. `trafficmanager.net` missing from auth host allowlist `fetchWithAuthFallback` checks `DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST` before retrying with a Bearer token on 401 responses. `trafficmanager.net` (used by `smba.trafficmanager.net` Bot Framework service URLs) was not listed, so auth fallback was silently skipped. **Fix:** Added `trafficmanager.net` to `DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST` in `shared.ts`. ### 3. Inbound media gate required ALL attachments to be `text/html` The HTML-attachment gate used `every()`, requiring all attachments to be `text/html` before triggering the Graph API fallback (Tier 2). Teams sends mixed types (`image/*` + `text/html`) for pasted images, so the condition never matched. **Fix:** Changed `every()` → `some()` and renamed `onlyHtmlAttachments` → `hasHtmlAttachments` in `inbound-media.ts`. ### 4. Graph API `hostedContents` collection returns `contentBytes: null` `downloadGraphHostedContent` only read `contentBytes` from the collection response, which is [always null per Microsoft docs](https://learn.microsoft.com/en-us/graph/api/chatmessagehostedcontent-get?view=graph-rest-1.0&tabs=http): > **Note:** `contentBytes` and `contentType` are always set to null. The binary is available via the individual [`$value` endpoint](https://learn.microsoft.com/en-us/graph/api/chatmessagehostedcontent-get?view=graph-rest-1.0&tabs=http#example-2-get-hosted-content-bytes-for-an-image): ``` GET .../hostedContents/{id}/$value → 200 OK, content-type: image/png, <binary body> ``` **Fix:** Added `$value` endpoint fetch with Bearer auth as fallback when `contentBytes` is null in `graph.ts`. Kept the `contentBytes` fast path in case Microsoft starts populating it in the future. ## Changes | File | Change | |------|--------| | `extensions/msteams/src/attachments/download.ts` | `isBotFrameworkAttachmentUrl()` + `/views/original` URL rewriting | | `extensions/msteams/src/attachments/shared.ts` | `trafficmanager.net` added to auth allowlist | | `extensions/msteams/src/attachments/graph.ts` | `$value` endpoint fetch for hosted content | | `extensions/msteams/src/monitor-handler/inbound-media.ts` | `every()` → `some()` for HTML gate | | `extensions/msteams/src/attachments.test.ts` | 4 new tests + updated existing tests | | `CHANGELOG.md` | Fix entry | ## Testing - **Unit tests:** 22/22 pass, including 4 new tests: - `appends /views/original to Bot Framework attachment URLs` - `uses auth fallback for Bot Framework 401 responses` - `downloads hostedContents images via $value endpoint` - `skips hosted content when $value fetch fails` - **Live test:** Pasted image in Teams chat → gateway downloaded (2025×1525 PNG), resized, and dispatched to agent successfully - **Gate:** `pnpm build && pnpm check && pnpm test` all pass <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> - Fixes MSTeams inline pasted image handling by rewriting Bot Framework attachment URLs to fetch binary bytes via `/views/original`. - Expands the media auth allowlist to include `trafficmanager.net`, enabling Bearer-token retry when Bot Framework URLs return 401/403. - Adjusts inbound media Tier-2 (Graph) fallback gating to trigger when *any* `text/html` attachment is present (mixed attachment types). - Updates Graph hosted content download to fetch bytes from `hostedContents/{id}/$value` when collection `contentBytes` is null, with accompanying unit tests. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk. - Changes are narrowly scoped to MS Teams media download paths, include targeted unit tests for each regression, and preserve existing behavior via fast paths and allowlist checks. - No files require special attention <!-- greptile_other_comments_section --> <sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub> <!-- /greptile_comment -->

Most Similar PRs