Skip to content

fix(msteams): SharePoint media fetch fails on Node 24+ (#63396)#64652

Merged
BradGroux merged 2 commits intoopenclaw:mainfrom
sudie-codes:fix/msteams-sharepoint-node24-63396
Apr 12, 2026
Merged

fix(msteams): SharePoint media fetch fails on Node 24+ (#63396)#64652
BradGroux merged 2 commits intoopenclaw:mainfrom
sudie-codes:fix/msteams-sharepoint-node24-63396

Conversation

@sudie-codes
Copy link
Copy Markdown
Contributor

Summary

Fixes silent failure of SharePoint/OneDrive file downloads on Node 24+ (undici v7).

Root cause

Two bugs compounded:

  1. fetchRemoteMedia routes through createPinnedDispatcher which is incompatible with undici v7's stricter dispatcher interface shipped in Node 24+. When a caller passes a custom fetchImpl that eventually delegates to globalThis.fetch, the pinned dispatcher built from bundled undici@7.24.7 gets handed to Node's built-in undici v7, which rejects it with TypeError: fetch failed | invalid onRequestStart method.
  2. The error was swallowed by empty catch {} blocks in download.ts, graph.ts, and bot-framework.ts, leaving no logs or diagnostic signal.

Fix

  • Added a scoped useDirectFetch path in downloadAndStoreMSTeamsRemoteMedia that bypasses fetchRemoteMedia's strict SSRF dispatcher when the caller's fetchImpl has already validated the URL against a hostname allowlist (via safeFetchWithPolicy). This is only opted into by the two MSTeams call sites that pre-validate (download attachments + SharePoint reference downloads), so other callers still get the strict dispatcher.
  • The direct-fetch path still enforces maxBytes via readResponseWithLimit (including a Content-Length pre-check) so rogue responses cannot drive RSS past the configured limit.
  • Replaced empty catch {} blocks in download.ts, graph.ts, and bot-framework.ts with structured logger.warn calls so operators can actually see fetch failures. The logger is threaded from resolveMSTeamsInboundMedia (which already has access to MSTeamsMonitorLogger) through the attachments path.

Test plan

  • New remote-media.test.ts: verifies the direct-fetch path bypasses fetchRemoteMedia, surfaces HTTP errors, enforces Content-Length caps, and still falls back to the strict dispatcher when useDirectFetch is not set.
  • New logger tests in attachments.test.ts: pin the logger.warn contract on failure, and verify success paths stay silent.
  • Full MSTeams suite: pnpm test extensions/msteams (773 tests across 61 files, all green).
  • pnpm tsgo, pnpm lint, pnpm format, pnpm build all green.
  • Manual: share a SharePoint file to a Teams bot on Node 24+

Fixes #63396

Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: M labels Apr 11, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 11, 2026

Greptile Summary

This PR fixes a silent failure of SharePoint/OneDrive file downloads on Node 24+ by introducing a useDirectFetch bypass in downloadAndStoreMSTeamsRemoteMedia for call sites whose fetchImpl already enforces SSRF validation per redirect hop via safeFetch/safeFetchWithPolicy. It also replaces empty catch {} blocks across download.ts, graph.ts, and bot-framework.ts with structured logger.warn calls threaded from resolveMSTeamsInboundMedia.

The security boundary is sound: safeFetch (the underlying implementation of all supplied fetchImpls in this PR) always overrides the incoming redirect option with redirect: \"manual\" and validates every redirect hop against the allowlist, so the bypass does not weaken SSRF protection. maxBytes is still enforced by the Content-Length pre-check and readResponseWithLimit. The only finding is a minor P2: the non-ok branch in fetchRemoteMediaDirect throws without consuming or cancelling the response body, which can delay undici connection-pool recycling under repeated failures.

Confidence Score: 5/5

Safe to merge — the fix is correct and well-tested, with SSRF protection maintained and error visibility substantially improved.

All findings are P2. The useDirectFetch path is only opted into by the two pre-validated call sites, maxBytes limits are enforced, and safeFetch always intercepts redirects for allowlist validation regardless of the redirect hint from the caller. Tests cover the bypass, HTTP error surfaces, Content-Length caps, and logger contract.

No files require special attention beyond the minor body-not-consumed note in remote-media.ts.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/attachments/remote-media.ts
Line: 33-36

Comment:
**Response body not consumed on HTTP error**

When `response.ok` is false the function throws without draining or cancelling the body. Undici (Node's built-in fetch) keeps the socket checked-out until the body is GC-ed or the connection times out, and emits a "body was not consumed" diagnostic in some configurations. Because this path can be hit on every SharePoint 401/403/5xx, repeated failures would stall the keep-alive pool sooner than expected.

```suggestion
  if (!response.ok) {
    await response.body?.cancel().catch(() => {});
    const statusText = response.statusText ? ` ${response.statusText}` : "";
    throw new Error(`HTTP ${response.status}${statusText}`);
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(msteams): fix SharePoint media fetch..." | Re-trigger Greptile

Comment on lines +33 to +36
if (!response.ok) {
const statusText = response.statusText ? ` ${response.statusText}` : "";
throw new Error(`HTTP ${response.status}${statusText}`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Response body not consumed on HTTP error

When response.ok is false the function throws without draining or cancelling the body. Undici (Node's built-in fetch) keeps the socket checked-out until the body is GC-ed or the connection times out, and emits a "body was not consumed" diagnostic in some configurations. Because this path can be hit on every SharePoint 401/403/5xx, repeated failures would stall the keep-alive pool sooner than expected.

Suggested change
if (!response.ok) {
const statusText = response.statusText ? ` ${response.statusText}` : "";
throw new Error(`HTTP ${response.status}${statusText}`);
}
if (!response.ok) {
await response.body?.cancel().catch(() => {});
const statusText = response.statusText ? ` ${response.statusText}` : "";
throw new Error(`HTTP ${response.status}${statusText}`);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/attachments/remote-media.ts
Line: 33-36

Comment:
**Response body not consumed on HTTP error**

When `response.ok` is false the function throws without draining or cancelling the body. Undici (Node's built-in fetch) keeps the socket checked-out until the body is GC-ed or the connection times out, and emits a "body was not consumed" diagnostic in some configurations. Because this path can be hit on every SharePoint 401/403/5xx, repeated failures would stall the keep-alive pool sooner than expected.

```suggestion
  if (!response.ok) {
    await response.body?.cancel().catch(() => {});
    const statusText = response.statusText ? ` ${response.statusText}` : "";
    throw new Error(`HTTP ${response.status}${statusText}`);
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c2b04c039

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +77 to +81
if (params.useDirectFetch && params.fetchImpl) {
fetched = await fetchRemoteMediaDirect({
url: params.url,
fetchImpl: params.fetchImpl,
maxBytes: params.maxBytes,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve SSRF IP checks in direct fetch mode

When useDirectFetch is enabled, this branch bypasses channel.media.fetchRemoteMedia(...ssrfPolicy) and therefore skips the strict fetchWithSsrFGuard path that enforces pinned-DNS/private-IP blocking. The current callers provide fetchImpl wrappers based on safeFetchWithPolicy without a resolveFn, so they only enforce hostname suffix allowlists; an allowlisted domain with attacker-controlled DNS (or rebinding) can now resolve to internal/private addresses that were blocked before this commit.

Useful? React with 👍 / 👎.

@sudie-codes sudie-codes force-pushed the fix/msteams-sharepoint-node24-63396 branch from 0c2b04c to 15b468e Compare April 11, 2026 05:13
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 15b468edb1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// allowlist via `safeFetchWithPolicy`, so skip `fetchRemoteMedia`'s
// strict SSRF dispatcher (incompatible with Node 24+ / undici v7;
// see issue #63396).
useDirectFetch: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep strict SSRF IP validation on attachment downloads

This now forces all attachment URLs through useDirectFetch, which bypasses channel.media.fetchRemoteMedia(...ssrfPolicy) and its pinned-DNS/private-IP guard; the replacement path (fetchWithAuthFallback -> safeFetchWithPolicy) is called without resolveFn, so it only checks hostname suffixes. That means allowlisted domains can now resolve/redirect to private/internal IPs that were previously blocked, which is a security regression for generic attachment downloads (not just the SharePoint Node 24 workaround).

Useful? React with 👍 / 👎.

Comment on lines +290 to +292
params.logger?.warn?.("msteams attachment download failed", {
error: err instanceof Error ? err.message : String(err),
host: safeHostForLog(candidate.url),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact URL-bearing fetch errors before warning logs

This logs raw err.message, but direct-fetch failures can bubble up messages from safeFetch that include full redirect URLs (for example blocked redirect targets). In Teams/SharePoint flows those URLs can carry signed query parameters, so failed downloads can leak sensitive link tokens into warn logs. Please redact URL content (or log host/status only) before attaching error metadata.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c64df6136

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +74 to +77
response = await safeFetchWithPolicy({
url,
policy: params.policy,
fetchFn: params.fetchFn,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reinstate IP-level SSRF checks for Bot Framework downloads

This change replaces fetchWithSsrFGuard(...ssrfPolicy) with safeFetchWithPolicy(...) for Bot Framework attachment fetches, but no resolveFn is supplied, so only hostname suffix allowlisting is enforced. In this path, an allowlisted hostname (or redirect target) that resolves to a private/loopback address will no longer be blocked, which is a security regression from the previous pinned-DNS/private-IP guard behavior used before this commit; the same pattern is applied to both attachment info and view fetches.

Useful? React with 👍 / 👎.

@sudie-codes sudie-codes force-pushed the fix/msteams-sharepoint-node24-63396 branch from 6c64df6 to 3f956c6 Compare April 11, 2026 05:56
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f956c68d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +43 to +44
if (Number.isFinite(length) && length > params.maxBytes) {
throw new Error(`content length ${length} exceeds maxBytes ${params.maxBytes}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel oversized response bodies before throwing

When Content-Length is above maxBytes, this branch throws immediately without canceling the response stream. In the new direct-fetch path, that leaves the body unread, so repeated oversized attachments can keep undici sockets busy and reduce connection reuse (a regression from the previous fetchRemoteMedia(...).release() cleanup path). Please cancel the body before throwing on this early-exit path.

Useful? React with 👍 / 👎.

Comment on lines +396 to +398
params.logger?.warn?.("msteams SharePoint reference download failed", {
error: err instanceof Error ? err.message : String(err),
name,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact SharePoint fetch errors before warning logs

This warning logs raw err.message, but failures from safeFetchWithPolicy can include full redirect URLs. In SharePoint flows those URLs often carry signed query parameters, so a blocked/failed download can leak tokenized links into logs. Please redact URL content (or log host/status only) before attaching the error text.

Useful? React with 👍 / 👎.

@BradGroux BradGroux force-pushed the fix/msteams-sharepoint-node24-63396 branch from 3f956c6 to 1b15f1a Compare April 11, 2026 18:05
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b15f1a44d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +133 to +137
if (!response.ok) {
params.logger?.warn?.("msteams botFramework attachmentView non-ok", {
status: response.status,
});
return undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel Bot Framework response body before early return

After replacing fetchWithSsrFGuard(...).release() with safeFetchWithPolicy, this branch returns on non-OK responses without draining or canceling response.body. In undici, unread bodies can keep connections occupied until GC, so repeated failed attachment downloads (for example 401/404/500) can reduce connection reuse and hurt throughput. The previous implementation always released resources in a finally block, so this is a regression in the new path.

Useful? React with 👍 / 👎.

Comment on lines +83 to +85
params.logger?.warn?.("msteams botFramework attachmentInfo fetch failed", {
error: err instanceof Error ? err.message : String(err),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Redact Bot Framework fetch errors before warning logs

This logs raw err.message from safeFetchWithPolicy, whose thrown messages can include full redirect target URLs. Bot Framework attachment redirects can carry signed query parameters, so failed/blocked fetches can leak sensitive tokens into warning logs. Please redact URL/query content (or log host/status only) before attaching the error string.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle Bot added the agents Agent runtime and tooling label Apr 11, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ccd0fded6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// allowlist via `safeFetchWithPolicy`, so skip `fetchRemoteMedia`'s
// strict SSRF dispatcher (incompatible with Node 24+ / undici v7;
// see issue #63396).
useDirectFetch: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reinstate pinned DNS for direct attachment downloads

Setting useDirectFetch: true here forces all attachment fetches onto fetchRemoteMediaDirect, which calls fetchImpl without fetchWithSsrFGuard's pinned dispatcher. Fresh evidence versus earlier SSRF findings: even though resolveFn is now threaded through, safeFetchWithPolicy only performs a preflight DNS/IP check and then issues a separate fetch, so DNS can change between validation and connect. That is a security regression from the previous pinned-DNS path for environments that rely on strict SSRF guarantees (especially when allowHosts includes tenant-configured domains).

Useful? React with 👍 / 👎.

@BradGroux BradGroux force-pushed the fix/msteams-sharepoint-node24-63396 branch from ccd0fde to ee7bc12 Compare April 12, 2026 00:48
@openclaw-barnacle openclaw-barnacle Bot removed the agents Agent runtime and tooling label Apr 12, 2026
@BradGroux BradGroux merged commit 2084441 into openclaw:main Apr 12, 2026
40 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee7bc12811

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +33 to +35
if (!response.ok) {
const statusText = response.statusText ? ` ${response.statusText}` : "";
throw new Error(`HTTP ${response.status}${statusText}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel direct-fetch response before throwing on HTTP failure

When useDirectFetch is enabled, this branch throws on non-2xx status without draining or canceling response.body. In Node/undici, unread bodies can keep connections occupied until GC, so repeated failed attachment downloads (for example 401/403/500 responses) can reduce connection reuse and hurt throughput. The previous fetchRemoteMedia(...).release() path cleaned up transport resources even on failure.

Useful? React with 👍 / 👎.

Comment on lines +139 to +141
const contentLength = response.headers.get("content-length");
if (contentLength && Number(contentLength) > params.maxBytes) {
return undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel Bot Framework view body on size precheck rejection

This early return exits when Content-Length exceeds maxBytes but leaves the response body unread. In the new safeFetchWithPolicy path there is no release() cleanup, so repeated oversized Bot Framework attachments can keep undici sockets busy and degrade download throughput. The prior guarded-fetch implementation always released resources in a finally block.

Useful? React with 👍 / 👎.

trudbot pushed a commit to trudbot/openclaw that referenced this pull request Apr 12, 2026
… (openclaw#64652)

* fix(msteams): fix SharePoint media fetch on Node 24+ and stop swallowing errors (openclaw#63396)

* fix(msteams): extend Node 24 dispatcher fix to Bot Framework attachment view downloads
TOMUIV pushed a commit to TOMUIV/openclaw that referenced this pull request Apr 14, 2026
… (openclaw#64652)

* fix(msteams): fix SharePoint media fetch on Node 24+ and stop swallowing errors (openclaw#63396)

* fix(msteams): extend Node 24 dispatcher fix to Bot Framework attachment view downloads
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
… (openclaw#64652)

* fix(msteams): fix SharePoint media fetch on Node 24+ and stop swallowing errors (openclaw#63396)

* fix(msteams): extend Node 24 dispatcher fix to Bot Framework attachment view downloads
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
… (openclaw#64652)

* fix(msteams): fix SharePoint media fetch on Node 24+ and stop swallowing errors (openclaw#63396)

* fix(msteams): extend Node 24 dispatcher fix to Bot Framework attachment view downloads
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
… (openclaw#64652)

* fix(msteams): fix SharePoint media fetch on Node 24+ and stop swallowing errors (openclaw#63396)

* fix(msteams): extend Node 24 dispatcher fix to Bot Framework attachment view downloads
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Teams: SharePoint file download fails on Node 24+ due to undici strict dispatcher incompatibility

2 participants