authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-04 13:33:23-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 20:33:23+00:00
log781f41ef1f6b5054e75f77237df9eba3b7f61cc9
tree24df483752ab90091e15a8e024ce2bcf5754ce17
parentb123cbb317ca47ff080a15b44c4bc0c99126f729
signature Signed by PGP key B5690EEEBB952194

fix(server): refuse symlinked theme files on Windows too (#9580)

Co-authored-by: Claude Code <noreply@anthropic.com>

1 files changed, 11 insertions(+), 1 deletions(-)

apps/server/src/environmentTheme.ts+11-1
......@@ -95,13 +95,23 @@ export class EnvironmentThemeService extends Context.Service<
9595 * usable, a symlinked file inside it does not), O_NONBLOCK keeps a FIFO from
9696 * blocking the open, and the fstat type and size gate examines the open
9797 * descriptor. Returns null for anything that is not a small regular file.
98 *
99 * Windows has neither flag (the constants are undefined, and OR-ing them in
100 * is a no-op), so the symlink check there is an lstat before the open. That
101 * leaves a window a swap could slip through, which the descriptor-bound
102 * checks below then narrow to "a regular file at that path".
98103 */
99104export const readThemeFileGuarded = (filePath: string, maxBytes: number): string | null => {
100105 let fd: number;
101106 try {
107 if (NodeFS.constants.O_NOFOLLOW === undefined && NodeFS.lstatSync(filePath).isSymbolicLink()) {
108 return null;
109 }
102110 fd = NodeFS.openSync(
103111 filePath,
104 NodeFS.constants.O_RDONLY | NodeFS.constants.O_NOFOLLOW | NodeFS.constants.O_NONBLOCK,
112 NodeFS.constants.O_RDONLY |
113 (NodeFS.constants.O_NOFOLLOW ?? 0) |
114 (NodeFS.constants.O_NONBLOCK ?? 0),
105115 );
106116 } catch {
107117 return null;