Skip to content

fix: authenticate image attachment preview urls to prevent 403 errors - #1403

Open
swarnendusahu10-dotcom wants to merge 2 commits into
RocketChat:developfrom
swarnendusahu10-dotcom:fix/issue-1229-authenticated-image-previews
Open

swarnendusahu10-dotcom wants to merge 2 commits into
RocketChat:developfrom
swarnendusahu10-dotcom:fix/issue-1229-authenticated-image-previews

Conversation

@swarnendusahu10-dotcom

Copy link
Copy Markdown
Contributor

Summary

Fixes #1229

When Rocket.Chat servers have the default "Block Unauthenticated Access" setting enabled for uploaded files under /file-upload/..., image attachment previews rendered directly via HTML <img> tags fail to load with 403 Forbidden because standard browser image requests do not carry custom HTTP headers (X-Auth-Token / X-User-Id).

This pull request introduces an authenticated media URL handler that appends Rocket.Chat's query-parameter authentication (?rc_token=<token>&rc_uid=<userId>) to internal file upload URLs while strictly safeguarding external URLs against credential leakage.


Security Architecture & Validation Boundary

To guarantee that user authentication credentials (rc_token, rc_uid) can never leak to third-party domains or external image hosts (CWE-200 prevention), URL resolution enforces a strict dual-boundary validation:

flowchart TD
    A["Incoming Media URL<br/><i>(attachment.image_url)</i>"] --> B{"Is URL falsy or empty?"}
    
    B -- "Yes" --> C["Return '' (Defensive Guard)"]
    B -- "No" --> D{"Is URL Relative or Absolute?"}
    
    D -- "Relative (/file-upload/...)" --> E{"Path starts with<br/>'/file-upload/'?"}
    D -- "Absolute (http/https)" --> F{"Origin === Rocket.Chat Host Origin?"}
    
    F -- "No (External / Attacker URL)" --> G["Return Raw External URL<br/><b>[ZERO TOKEN LEAKAGE - CWE-200 SAFE]</b>"]
    F -- "Yes (Same Origin)" --> E
    
    E -- "No (e.g. /avatar/)" --> H["Return host + path URL without tokens"]
    E -- "Yes (/file-upload/)" --> I{"Are rc_token & rc_uid available?"}
    
    I -- "No (Unauthenticated / Anonymous)" --> H
    I -- "Yes (Authenticated Session)" --> J["Append ?rc_token=...&rc_uid=... via URLSearchParams<br/><b>[PREVIEW AUTHENTICATED - 403 RESOLVED]</b>"]

    style A fill:#1f2937,stroke:#4b5563,stroke-width:2px,color:#f3f4f6
    style B fill:#374151,stroke:#6b7280,stroke-width:2px,color:#f3f4f6
    style C fill:#4b5563,stroke:#9ca3af,stroke-width:1px,color:#f3f4f6
    style D fill:#374151,stroke:#6b7280,stroke-width:2px,color:#f3f4f6
    style E fill:#1e3a8a,stroke:#3b82f6,stroke-width:2px,color:#eff6ff
    style F fill:#831843,stroke:#ec4899,stroke-width:2px,color:#fdf2f8
    style G fill:#991b1b,stroke:#ef4444,stroke-width:2px,color:#fef2f2
    style H fill:#374151,stroke:#9ca3af,stroke-width:1px,color:#f3f4f6
    style I fill:#1e3a8a,stroke:#3b82f6,stroke-width:2px,color:#eff6ff
    style J fill:#065f46,stroke:#10b981,stroke-width:2px,color:#ecfdf5
Loading

Key Changes

  1. useAuthenticatedMediaUrl Hook (packages/react/src/hooks/useAuthenticatedMediaUrl.js):
    • Eagerly evaluates current session credentials from RCInstance.auth.currentUser to prevent loading flash or broken images.
    • Asynchronously reconciles with RCInstance.auth.getCurrentUser() and reactive RCInstance.auth.onAuthChange listener subscriptions.
    • Cleanly cleans up event listeners on component unmount.
  2. Strict Boundary & Token Leakage Prevention (CWE-200):
    • Only URLs residing on the Rocket.Chat host instance AND pointing to /file-upload/ paths receive authentication query parameters.
    • External URLs (e.g., Unsplash, Giphy, custom webhooks, or third-party links) and non-upload paths are strictly returned untouched, guaranteeing zero credential leakage.
  3. ImageAttachment.js & NestedImageAttachment (packages/react/src/views/AttachmentHandler/ImageAttachment.js):
    • Authenticates both the preview <img> source and the AttachmentMetadata download URL (fixing 403 Forbidden on attachment download).
    • Extracts nested attachments into a clean NestedImageAttachment subcomponent, strictly complying with the Rules of Hooks.
    • Guards extractIdFromUrl against null/undefined URLs using optional chaining.
    • Adds full PropTypes coverage.

Verification & Testing

  • Unit & Logic Verification: Verified with automated test matrix covering relative paths, absolute host URLs, external URLs, phishing/spoofed hosts, unauthenticated sessions, and URL-encoded token characters.
  • Prettier & Formatting: Passed prettier --check.
  • Rollup Build: Clean build completed with npm run build (dist/cjs and dist/esm generated without errors).

@swarnendusahu10-dotcom

Copy link
Copy Markdown
Contributor Author

Hi @Aryan-Verma-999, gentle bump on this one. All CI checks are green with 0 merge conflicts against latest develop. Could you please let me know if this looks good to merge or if there's anything you'd like me to update. Appreciate your guidance! ✨

@Aryan-Verma-999

Copy link
Copy Markdown
Collaborator

hey @swarnendusahu10-dotcom, appending rc_token / rc_uid to every media URL exposes the session token in DOM-visible URLs and server/CDN/proxy logs. can we rework this use credential-preserving media delivery mechanism

@swarnendusahu10-dotcom

Copy link
Copy Markdown
Contributor Author

Hi @Aryan-Verma-999! 👋

Great point on token exposure. I’ve refactored this in commit e194923 to use a credential-preserving blob delivery mechanism:

  • Removed rc_token / rc_uid query parameters completely from all URLs.
  • Protected media (/file-upload/...) is now fetched via secure request headers (X-Auth-Token, X-User-Id) and credentials: 'include'.
  • Delivered to tags as transient Object URLs (URL.createObjectURL(blob)), keeping the DOM and proxy/CDN logs completely free of session tokens.
  • Automatic cleanup: revokes blob URLs via URL.revokeObjectURL on unmount/re-render to prevent memory leaks, with a small LRU cache for smooth scrolling.
  • External images (Giphy, Unsplash, external avatars) remain unaffected.

Formatted with prettier@2.8.1 and Rollup build verified clean. Ready for your review! 🙌

@Aryan-Verma-999

Copy link
Copy Markdown
Collaborator

Can you share a demo video, demonstrating that you're not getting the 403

@swarnendusahu10-dotcom

Copy link
Copy Markdown
Contributor Author

@Aryan-Verma-999 I will give u the video , but i need some time before that , i Have my final exams from tmrw . Sry for the delay . But it will run for like a week , if i get time in btw i will give u the vid or nxt week i will give u a proper demo .
Thank you.

@Aryan-Verma-999

Copy link
Copy Markdown
Collaborator

@Aryan-Verma-999 I will give u the video , but i need some time before that , i Have my final exams from tmrw . Sry for the delay . But it will run for like a week , if i get time in btw i will give u the vid or nxt week i will give u a proper demo .
Thank you.

No worries, take your time :)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Image attachment previews return 403 Forbidden (uploaded images not visible in chat)

2 participants