Skip to content

fix(msteams): prevent auth header leakage on redirect fallback#25045

Merged
steipete merged 5 commits intoopenclaw:mainfrom
bmendonca3:bm/msteams-auth-header-redirect-scope
Mar 2, 2026
Merged

fix(msteams): prevent auth header leakage on redirect fallback#25045
steipete merged 5 commits intoopenclaw:mainfrom
bmendonca3:bm/msteams-auth-header-redirect-scope

Conversation

@bmendonca3
Copy link
Copy Markdown
Contributor

@bmendonca3 bmendonca3 commented Feb 24, 2026

Summary

This fixes a credential-leak path in MSTeams attachment downloads: when auth fallback is used, safeFetch() followed redirects with the same Authorization header, even if the redirect target was outside authAllowHosts (but still inside broader allowHosts).

Concretely:

  • safeFetch() now accepts authorizationAllowHosts and strips Authorization before redirect hops that are not auth-allowlisted.
  • Auth fallback calls pass authAllowHosts into safeFetch() so redirect forwarding of bearer tokens stays bound to the auth trust boundary.
  • Adds a deterministic regression test proving token headers are not forwarded to a redirect host outside authAllowHosts.

Why this matters

An attacker who can influence redirect behavior on an allowlisted media host can exfiltrate Graph bearer tokens to another allowlisted host (for example CDN domain) that is intentionally not auth-trusted. That breaks the intended auth-host boundary and can enable cross-resource data access under stolen token scopes.

Deterministic Reproduction

Validate on this PR branch (fixed behavior)

pnpm install
pnpm vitest run --config vitest.extensions.config.ts extensions/msteams/src/attachments.test.ts -t "does not forward Authorization to redirects outside auth allowlist"

Expected: test passes and asserts redirected request to https://attacker.azureedge.net/collect has empty Authorization.

Confirm the bug on parent commit (vulnerable behavior)

git checkout f2bdf0ecb^
pnpm vitest run --config vitest.extensions.config.ts extensions/msteams/src/attachments.test.ts -t "does not forward Authorization to redirects outside auth allowlist"
git checkout bm/msteams-auth-header-redirect-scope

Expected on parent: test fails because redirected request receives Authorization.

Scope/Trust Alignment (quoted)

From SECURITY.md:

"Reproducible PoC against latest main or latest released version." (Report Acceptance Gate)

"Demonstrated impact tied to OpenClaw's documented trust boundaries." (Report Acceptance Gate)

"Scope check explaining why the report is not covered by the Out of Scope section below." (Report Acceptance Gate)

"The model/agent is not a trusted principal... Security boundaries come from host/config trust, auth, tool policy, sandboxing, and exec approvals." (Agent and Model Assumptions)

Alignment: this report is a concrete auth-boundary bypass in channel attachment handling (authAllowHosts vs redirect flow), with deterministic repro and direct demonstrated impact. It is not a prompt-injection-only or shared-operator trust-model claim.

Dedupe Check Evidence

Searched open PRs/issues for overlap (2026-02-24 UTC) and found no matching open work for this exact vulnerability:

GH_HOST=github.com gh search prs --repo openclaw/openclaw --state open "msteams attachment redirect authorization" --json number,title,url
# => []

GH_HOST=github.com gh search issues --repo openclaw/openclaw --state open "msteams attachment redirect authorization" --json number,title,url
# => []

Reviewed currently open MSTeams-related PRs and none address auth-header redirect leakage in attachment download fallback:

Tests

pnpm vitest run --config vitest.extensions.config.ts extensions/msteams/src/attachments.test.ts
pnpm vitest run --config vitest.extensions.config.ts extensions/msteams/src/attachments/shared.test.ts

Greptile Summary

This PR fixes a credential leak vulnerability in MSTeams attachment downloads where Authorization headers were forwarded to redirect targets outside the auth trust boundary (authAllowHosts), even though those targets were within the broader allowHosts.

The fix introduces an authorizationAllowHosts parameter to safeFetch() that controls when Authorization headers are preserved across redirects. When a redirect points to a host outside authAllowHosts, the header is stripped before following the redirect. The fix is applied to the auth fallback path in downloadWithAuthFallback() which now passes authAllowHosts to safeFetch().

A comprehensive regression test verifies that redirects to hosts outside the auth allowlist (e.g., attacker.azureedge.net) do not receive the bearer token, while the download still succeeds.

Confidence Score: 3/5

  • This PR partially addresses the auth header leakage issue but misses one call site
  • The core fix in safeFetch() and fetchWithAuthFallback() is well-designed with proper header stripping logic and comprehensive test coverage. However, the SharePoint download path in graph.ts:285 sets an Authorization header but doesn't pass authorizationAllowHosts to safeFetch(), which means it's still vulnerable to the same credential leak on redirects. This incomplete fix reduces confidence to 3.
  • Pay close attention to extensions/msteams/src/attachments/graph.ts which contains the same vulnerability pattern that wasn't fixed

Last reviewed commit: f2bdf0e

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 24, 2026

Additional Comments (1)

extensions/msteams/src/attachments/graph.ts
missing authorizationAllowHosts parameter - the Authorization header will be forwarded to any redirect target in allowHosts, even if outside the auth trust boundary

              return await safeFetch({
                url: requestUrl,
                allowHosts,
                authorizationAllowHosts: params.authAllowHosts,
                fetchFn,
                requestInit: {
                  ...init,
                  headers,
                },
              });
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/attachments/graph.ts
Line: 285-293

Comment:
missing `authorizationAllowHosts` parameter - the `Authorization` header will be forwarded to any redirect target in `allowHosts`, even if outside the auth trust boundary

```suggestion
              return await safeFetch({
                url: requestUrl,
                allowHosts,
                authorizationAllowHosts: params.authAllowHosts,
                fetchFn,
                requestInit: {
                  ...init,
                  headers,
                },
              });
```

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

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed and now covered by regression test in a07cd38.

  • extensions/msteams/src/attachments/graph.ts passes authorizationAllowHosts: params.authAllowHosts in the SharePoint Graph safeFetch path.
  • Added test does not forward Authorization for SharePoint redirects outside auth allowlist in extensions/msteams/src/attachments.test.ts to validate the redirect hop strips auth outside the auth trust boundary.

Copy link
Copy Markdown

@nikolasdehor nikolasdehor left a comment

Choose a reason for hiding this comment

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

Critical credential leak fix. The issue: when MS Teams fetches attachments from Graph API, the response can be a 302 redirect to an Azure CDN or SharePoint URL. Before this fix, the Authorization header (containing the Graph API bearer token) was forwarded blindly to the redirect target, even if it was outside the auth-trusted domain set.

What I verified:

  1. safeFetch in shared.ts: The core fix. A new optional authorizationAllowHosts parameter controls which redirect targets are allowed to receive the Authorization header. On each redirect hop, if the target host isn't in authorizationAllowHosts, the header is deleted from currentHeaders. Key detail: headers are now managed via a new Headers() instance that's passed on each iteration, rather than using the original requestInit.headers — this prevents mutation of the caller's headers object while allowing progressive stripping.

  2. Redirect chain handling: The check runs on every hop (inside the for loop), not just the first redirect. This is important because a redirect chain like graph.microsoft.com → cdn.example.com → attacker.com would strip auth at the cdn hop and keep it stripped.

  3. One-way stripping: Once Authorization is deleted, it stays deleted for all subsequent hops. This is correct — currentHeaders.delete("authorization") mutates the Headers object that's reused across iterations.

  4. download.ts and graph.ts: Both fetchWithAuthFallback and downloadMSTeamsGraphMedia now pass authorizationAllowHosts through to safeFetch. The naming is consistent (authAllowHosts at the caller level, authorizationAllowHosts at the safeFetch level).

  5. Test coverage: Two tests — one for the attachment download path (redirect from Graph to attacker.azureedge.net) and one for the SharePoint Graph media path. Both verify that the redirected request has an empty auth header via the seen array pattern.

  6. Backward compatibility: authorizationAllowHosts is optional. When not set, the existing behavior is preserved (no stripping). This means callers that don't use auth don't need changes.

Well-designed fix with clean separation between host allowlisting (can fetch) and auth allowlisting (can send credentials). LGTM.

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed the Greptile note from #25045 (comment).

Fixed in commit c88823c286fcd3e440cac5ee89440eb0a033e050 by passing authorizationAllowHosts: params.authAllowHosts into safeFetch(...) for the SharePoint attachment fetch path.

That keeps Authorization forwarding scoped to the intended auth host allowlist during redirects.

@bmendonca3
Copy link
Copy Markdown
Contributor Author

Addressed Greptile note:

Already implemented in commit c88823c286fcd3e440cac5ee89440eb0a033e050:

  • extensions/msteams/src/attachments/graph.ts
    • safeFetch for Graph redirect path includes authorizationAllowHosts: params.authAllowHosts.

@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Feb 27, 2026
@steipete steipete force-pushed the bm/msteams-auth-header-redirect-scope branch from 73a3f31 to 0feadfc Compare March 2, 2026 20:45
@steipete steipete merged commit d2bb04b into openclaw:main Mar 2, 2026
4 checks passed
@openclaw-barnacle openclaw-barnacle bot added size: S and removed agents Agent runtime and tooling size: M labels Mar 2, 2026
@steipete
Copy link
Copy Markdown
Contributor

steipete commented Mar 2, 2026

Landed via temp rebase onto main.

  • Gate: pnpm vitest run extensions/msteams/src/attachments.test.ts extensions/msteams/src/attachments/shared.test.ts --maxWorkers=1; pnpm check && pnpm build
  • Land commit: 0feadfc
  • Merge commit: d2bb04b

Thanks @bmendonca3!

dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
john-ver pushed a commit to apmcoin/apmclaw that referenced this pull request Mar 9, 2026
dustin-olenslager pushed a commit to dustin-olenslager/ironclaw-supreme that referenced this pull request Mar 24, 2026
lukeg826 pushed a commit to lukeg826/openclaw that referenced this pull request Mar 26, 2026
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: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants