Skip to content

fix(slack): preserve auth on same-origin media redirects#62996

Merged
vincentkoc merged 2 commits into
mainfrom
fix/slack-private-download-redirect-auth
Apr 8, 2026
Merged

fix(slack): preserve auth on same-origin media redirects#62996
vincentkoc merged 2 commits into
mainfrom
fix/slack-private-download-redirect-auth

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • Problem: Slack url_private_download fetches lost Authorization on same-origin files.slack.com redirects, so image attachments degraded into HTML login pages and were dropped.
  • Why it matters: Slack agents on current releases silently miss shared image attachments.
  • What changed: moved auth into requestInit, kept the guarded fetch wrapper for dispatcher-backed requests, and added regression coverage for same-origin auth preservation plus cross-origin auth stripping.
  • What did NOT change (scope boundary): no Slack CDN allowlist expansion, no generic SSRF redirect-policy changes, and no non-Slack media behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: extensions/slack/src/monitor/media.ts managed the bearer token outside the shared guarded-fetch redirect flow. It sent auth on the first hop, then unconditionally deleted Authorization on every subsequent hop.
  • Missing detection / guardrail: we did not have a regression test covering same-origin Slack redirects through the real resolveSlackMedia path.
  • Contributing context (if known): the regression showed up after the SSRF redirect changes in 2026.4.5, but the actual bug was Slack-specific auth handling layered on top of that shared guard.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/slack/src/monitor/media.test.ts
  • Scenario the test should lock in: same-origin files.slack.com redirects keep Authorization; cross-origin Slack CDN redirects strip it.
  • Why this is the smallest reliable guardrail: it exercises the real Slack media download path without needing a live Slack workspace.
  • Existing test that already covers this (if any): none for the same-origin redirect case.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Slack image attachments served through url_private_download load again instead of being silently dropped after a login-page redirect.

Diagram (if applicable)

Before:
[Slack image upload] -> [files.slack.com redirect] -> [auth stripped too early] -> [HTML login page] -> [attachment dropped]

After:
[Slack image upload] -> [same-origin redirect keeps auth] -> [cross-origin redirect strips auth] -> [binary media fetched]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: the bearer token now stays attached only on same-origin Slack redirects. Cross-origin redirects still pass through the shared guarded-fetch stripping logic, so we do not widen token exposure beyond Slack's original host.

Repro + Verification

Environment

  • OS: macOS host worktree verification
  • Runtime/container: local Node 25.8.2 / pnpm 10.32.1
  • Model/provider: N/A
  • Integration/channel (if any): Slack
  • Relevant config (redacted): N/A

Steps

  1. Mock a Slack media download that redirects from https://files.slack.com/... to a same-origin path.
  2. Verify the second request still carries Authorization.
  3. Mock a redirect to https://downloads.slack-edge.com/... and verify auth is stripped there.

Expected

  • Same-origin redirects keep the bearer token.
  • Cross-origin Slack CDN redirects strip the bearer token.
  • resolveSlackMedia returns the saved attachment instead of null.

Actual

  • Verified by the new regression tests and the targeted local run below.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: pnpm test extensions/slack/src/monitor/media.test.ts; pnpm build; targeted oxlint and oxfmt --check on the touched files.
  • Edge cases checked: same-origin redirect auth preservation, cross-origin redirect auth stripping, dispatcher-backed runtime fetch path.
  • What you did not verify: I did not get a clean repo-wide pnpm lint or pnpm test gate because current main has unrelated failures, and the default core sparse profile initially hid ui/, packages/, and OpenClawKit resource paths until I materialized them.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: preserving auth on redirects could accidentally keep tokens on the wrong host if the redirect-origin check were wrong.
    • Mitigation: auth now rides through the shared guarded-fetch flow, which preserves headers only for same-origin redirects and strips them on cross-origin hops.

@vincentkoc vincentkoc self-assigned this Apr 8, 2026
@openclaw-barnacle openclaw-barnacle Bot added channel: slack Channel integration: slack size: S maintainer Maintainer-authored PR labels Apr 8, 2026
@vincentkoc
vincentkoc force-pushed the fix/slack-private-download-redirect-auth branch from b32a96c to 9af257e Compare April 8, 2026 07:11
@vincentkoc
vincentkoc merged commit 55cbcd8 into main Apr 8, 2026
7 checks passed
@vincentkoc
vincentkoc deleted the fix/slack-private-download-redirect-auth branch April 8, 2026 07:11
@vincentkoc

Copy link
Copy Markdown
Member Author

Landed via squash after rebasing onto the latest origin/main.

  • Verified: pnpm build
  • Verified: pnpm test extensions/slack/src/monitor/media.test.ts
  • Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts
  • Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md
  • Source branch head: 9af257e2839b111b442ae21313f7a897d891ac78
  • Merge commit: 55cbcd829d387ccd13202dcc5688950e93d59b8b

Note: repo-wide pnpm lint and pnpm test were already noisy outside this fix, and the first full-suite run from the default core sparse profile was additionally contaminated by missing ui/, packages/, and OpenClawKit resource paths until those paths were materialized.

Thanks @vincentkoc!

nxmxbbd pushed a commit to nxmxbbd/openclaw that referenced this pull request Apr 8, 2026
) (thanks @vincentkoc)

- Verified: pnpm build\n- Verified: pnpm test extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md\n\nRepo-wide pnpm lint and pnpm test were not clean on current main outside this fix, and the first full-suite test attempt from the default core sparse profile was additionally contaminated by missing ui/packages/OpenClawKit paths until they were materialized.
@greptile-apps

greptile-apps Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Slack url_private_download image attachments silently failing by moving the bearer token into requestInit so that fetchRemoteMedia's built-in SSRF-guarded redirect logic can correctly preserve auth on same-origin files.slack.com hops and strip it on cross-origin CDN redirects. Regression tests covering both cases are added through the full resolveSlackMedia path.

Confidence Score: 5/5

Safe to merge — the core fix is correct and well-tested; only a P2 style concern remains.

The bug fix (moving auth into requestInit so fetchRemoteMedia's SSRF-guarded redirect logic owns header propagation) is correct and directly validated by the new regression tests for same-origin preservation and cross-origin stripping. The only finding is a P2: fetchWithSlackAuth is a new exported symbol with no production callers, which is a clarity/dead-code concern but does not affect correctness or security.

No files require special attention beyond the P2 note on fetchWithSlackAuth in extensions/slack/src/monitor/media.ts.

Vulnerabilities

The bearer token is now scoped correctly: it rides through requestInit into fetchRemoteMedia's SSRF-guarded redirect flow, which preserves it only for same-origin files.slack.com hops and strips it on cross-origin redirects. The assertSlackFileUrl allowlist guard on the initial URL is retained. No new permissions or network surfaces are introduced. The one nuance is that fetchWithSlackAuth (currently unused in production) uses redirect: "follow" after the first same-origin hop — in Node 22+/undici, cross-origin Authorization stripping during follow-mode redirect chains is handled by the runtime, so no token leakage path exists in the supported environment.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/slack/src/monitor/media.ts
Line: 92-121

Comment:
**`fetchWithSlackAuth` is exported but has no production callers**

This function is exported and thoroughly tested in isolation, but it is not called by any production code path — not by `resolveSlackMedia`, `resolveSlackAttachmentContent`, `actions.ts`, or any other file in the extension (confirmed via a full extension-tree search). The actual fix routes auth through `requestInit` into `fetchRemoteMedia`'s built-in redirect handling. If `fetchWithSlackAuth` is intended as a utility for future Slack actions, a brief doc-comment noting its purpose and a re-export via `api.ts` would clarify intent; otherwise it's dead exported surface that could mislead future readers.

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

Reviews (1): Last reviewed commit: "docs(changelog): move slack fix to unrel..." | Re-trigger Greptile

Comment on lines 92 to 121
export async function fetchWithSlackAuth(url: string, token: string): Promise<Response> {
const parsed = assertSlackFileUrl(url);
const authHeaders = createSlackAuthHeaders(token);

// Initial request with auth and manual redirect handling
const initialRes = await fetch(parsed.href, {
headers: { Authorization: `Bearer ${token}` },
headers: authHeaders,
redirect: "manual",
});

// If not a redirect, return the response directly
if (initialRes.status < 300 || initialRes.status >= 400) {
return initialRes;
}

// Handle redirect - the redirected URL should be pre-signed and not need auth
const redirectUrl = initialRes.headers.get("location");
if (!redirectUrl) {
return initialRes;
}

// Resolve relative URLs against the original
const resolvedUrl = new URL(redirectUrl, parsed.href);

// Only follow safe protocols (we do NOT include Authorization on redirects).
if (resolvedUrl.protocol !== "https:") {
return initialRes;
}

// Follow the redirect without the Authorization header
// (Slack's CDN URLs are pre-signed and don't need it)
if (resolvedUrl.origin === parsed.origin) {
return fetch(resolvedUrl.toString(), {
headers: authHeaders,
redirect: "follow",
});
}
return fetch(resolvedUrl.toString(), { redirect: "follow" });
}

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 fetchWithSlackAuth is exported but has no production callers

This function is exported and thoroughly tested in isolation, but it is not called by any production code path — not by resolveSlackMedia, resolveSlackAttachmentContent, actions.ts, or any other file in the extension (confirmed via a full extension-tree search). The actual fix routes auth through requestInit into fetchRemoteMedia's built-in redirect handling. If fetchWithSlackAuth is intended as a utility for future Slack actions, a brief doc-comment noting its purpose and a re-export via api.ts would clarify intent; otherwise it's dead exported surface that could mislead future readers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/slack/src/monitor/media.ts
Line: 92-121

Comment:
**`fetchWithSlackAuth` is exported but has no production callers**

This function is exported and thoroughly tested in isolation, but it is not called by any production code path — not by `resolveSlackMedia`, `resolveSlackAttachmentContent`, `actions.ts`, or any other file in the extension (confirmed via a full extension-tree search). The actual fix routes auth through `requestInit` into `fetchRemoteMedia`'s built-in redirect handling. If `fetchWithSlackAuth` is intended as a utility for future Slack actions, a brief doc-comment noting its purpose and a re-export via `api.ts` would clarify intent; otherwise it's dead exported surface that could mislead future readers.

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

eleqtrizit pushed a commit that referenced this pull request Apr 8, 2026
…anks @vincentkoc)

- Verified: pnpm build\n- Verified: pnpm test extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md\n\nRepo-wide pnpm lint and pnpm test were not clean on current main outside this fix, and the first full-suite test attempt from the default core sparse profile was additionally contaminated by missing ui/packages/OpenClawKit paths until they were materialized.
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
) (thanks @vincentkoc)

- Verified: pnpm build\n- Verified: pnpm test extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md\n\nRepo-wide pnpm lint and pnpm test were not clean on current main outside this fix, and the first full-suite test attempt from the default core sparse profile was additionally contaminated by missing ui/packages/OpenClawKit paths until they were materialized.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
) (thanks @vincentkoc)

- Verified: pnpm build\n- Verified: pnpm test extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md\n\nRepo-wide pnpm lint and pnpm test were not clean on current main outside this fix, and the first full-suite test attempt from the default core sparse profile was additionally contaminated by missing ui/packages/OpenClawKit paths until they were materialized.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
) (thanks @vincentkoc)

- Verified: pnpm build\n- Verified: pnpm test extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md\n\nRepo-wide pnpm lint and pnpm test were not clean on current main outside this fix, and the first full-suite test attempt from the default core sparse profile was additionally contaminated by missing ui/packages/OpenClawKit paths until they were materialized.
@clawsweeper clawsweeper Bot mentioned this pull request May 20, 2026
19 tasks
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
) (thanks @vincentkoc)

- Verified: pnpm build\n- Verified: pnpm test extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxlint extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts\n- Verified: pnpm exec oxfmt --check extensions/slack/src/monitor/media.ts extensions/slack/src/monitor/media.test.ts CHANGELOG.md\n\nRepo-wide pnpm lint and pnpm test were not clean on current main outside this fix, and the first full-suite test attempt from the default core sparse profile was additionally contaminated by missing ui/packages/OpenClawKit paths until they were materialized.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 2026.4.5 regression: Slack private file images no longer visible (SSRF redirect fix breaks url_private_download fetch)

1 participant