( basePath: string, sessionId: string, relativePath: string, )
| 185 | * Returns null if the path is invalid (e.g., path traversal). |
| 186 | */ |
| 187 | export function buildDownloadPath( |
| 188 | basePath: string, |
| 189 | sessionId: string, |
| 190 | relativePath: string, |
| 191 | ): string | null { |
| 192 | const normalized = path.normalize(relativePath) |
| 193 | if (normalized.startsWith('..')) { |
| 194 | logDebugError( |
| 195 | `Invalid file path: ${relativePath}. Path must not traverse above workspace`, |
| 196 | ) |
| 197 | return null |
| 198 | } |
| 199 | |
| 200 | const uploadsBase = path.join(basePath, sessionId, 'uploads') |
| 201 | const redundantPrefixes = [ |
| 202 | path.join(basePath, sessionId, 'uploads') + path.sep, |
| 203 | path.sep + 'uploads' + path.sep, |
| 204 | ] |
| 205 | const matchedPrefix = redundantPrefixes.find(p => normalized.startsWith(p)) |
| 206 | const cleanPath = matchedPrefix |
| 207 | ? normalized.slice(matchedPrefix.length) |
| 208 | : normalized |
| 209 | return path.join(uploadsBase, cleanPath) |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Downloads a file and saves it to the session-specific workspace directory |
no test coverage detected