|
2 | 2 | const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu, protocol, net } = require('electron'); |
3 | 3 | const path = require('path'); |
4 | 4 | const fs = require('fs'); |
| 5 | +const { pathToFileURL } = require('url'); |
5 | 6 |
|
6 | 7 | // Register custom protocol scheme before app is ready |
7 | 8 | // This allows serving the Expo web export with absolute paths (/_expo/static/...) |
@@ -179,25 +180,35 @@ app.whenReady().then(() => { |
179 | 180 | // Register custom protocol handler for serving the Expo web export |
180 | 181 | // This resolves absolute paths like /_expo/static/js/... from the dist directory |
181 | 182 | const distPath = path.join(__dirname, '..', 'dist'); |
| 183 | + const resolvedDist = path.resolve(distPath); |
182 | 184 |
|
183 | 185 | protocol.handle('app', (request) => { |
184 | 186 | const url = new URL(request.url); |
185 | 187 | // Decode the pathname and resolve to a file in dist/ |
186 | | - let filePath = path.join(distPath, decodeURIComponent(url.pathname)); |
187 | | - |
188 | | - // If the path points to a directory or file doesn't exist, fall back to index.html |
189 | | - // This supports SPA client-side routing |
190 | | - try { |
191 | | - const stat = fs.statSync(filePath); |
192 | | - if (stat.isDirectory()) { |
193 | | - filePath = path.join(distPath, 'index.html'); |
| 188 | + const resolvedPath = path.resolve(distPath, decodeURIComponent(url.pathname)); |
| 189 | + |
| 190 | + // Security check: ensure resolved path is within distPath to prevent directory traversal |
| 191 | + let filePath; |
| 192 | + if (!resolvedPath.startsWith(resolvedDist + path.sep) && resolvedPath !== resolvedDist) { |
| 193 | + // Path escapes distPath - fall back to index.html |
| 194 | + filePath = path.join(resolvedDist, 'index.html'); |
| 195 | + } else { |
| 196 | + filePath = resolvedPath; |
| 197 | + |
| 198 | + // If the path points to a directory or file doesn't exist, fall back to index.html |
| 199 | + // This supports SPA client-side routing |
| 200 | + try { |
| 201 | + const stat = fs.statSync(filePath); |
| 202 | + if (stat.isDirectory()) { |
| 203 | + filePath = path.join(resolvedDist, 'index.html'); |
| 204 | + } |
| 205 | + } catch { |
| 206 | + // File not found - serve index.html for client-side routing |
| 207 | + filePath = path.join(resolvedDist, 'index.html'); |
194 | 208 | } |
195 | | - } catch { |
196 | | - // File not found - serve index.html for client-side routing |
197 | | - filePath = path.join(distPath, 'index.html'); |
198 | 209 | } |
199 | 210 |
|
200 | | - return net.fetch('file://' + filePath); |
| 211 | + return net.fetch(pathToFileURL(filePath).toString()); |
201 | 212 | }); |
202 | 213 |
|
203 | 214 | createMenu(); |
|
0 commit comments