Repository navigation
fix(ui): render templates from the path checked against the emails directory #3840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@react-email/ui": patch | ||
| --- | ||
|
|
||
| Render templates from the path that was checked against the emails directory, so a relative path can no longer pass the check but load a file from the working directory. |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import fs from 'node:fs'; | ||
| import path from 'node:path'; | ||
|
|
||
| const safeRealpath = (target: string): string => { | ||
| try { | ||
| return fs.realpathSync(target); | ||
| } catch { | ||
| return path.resolve(target); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Prompt for AI agents |
||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Resolves `emailPath` against the emails directory configured via | ||
| * REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH and returns its real path, or | ||
| * `undefined` when it lands outside of it. | ||
| * | ||
| * Server actions accept arbitrary strings from the client, so any path that | ||
| * eventually reaches the filesystem must go through this boundary to prevent | ||
| * traversal (`../../etc/passwd`) and absolute-path escapes. Callers must use the | ||
| * returned path: resolving the original one again (e.g. against the working | ||
| * directory) would read a different file than the one that was checked. | ||
| */ | ||
| export const resolveWithinEmailsDirectory = ( | ||
| emailPath: string, | ||
| ): string | undefined => { | ||
| const root = process.env.REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH; | ||
| if (!root) return undefined; | ||
|
|
||
| const resolvedRoot = safeRealpath(path.resolve(root)); | ||
| const resolvedTarget = safeRealpath(path.resolve(resolvedRoot, emailPath)); | ||
|
|
||
| if (!resolvedTarget.startsWith(resolvedRoot + path.sep)) return undefined; | ||
|
|
||
| return resolvedTarget; | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2:
renderEmailByPathnow derivesbasename,extname, and the render-mode branch from the symlink target's real path, not the requested template path. For a symlinked template the preview title (page.tsx) and the metadatabasenamecan disagree, and a target with a different extension changes the render mode. If this is intended, keep the requested path for display and only use the real path for file access.Prompt for AI agents