( runtime: Runtime, workspacePath: string, filePath: string )
| 25 | } |
| 26 | |
| 27 | function resolveWorkspaceFilePath( |
| 28 | runtime: Runtime, |
| 29 | workspacePath: string, |
| 30 | filePath: string |
| 31 | ): string { |
| 32 | assert(filePath, "filePath is required"); |
| 33 | |
| 34 | // Disallow absolute and home-relative paths. |
| 35 | if (isAbsolutePathAny(filePath) || filePath.startsWith("~")) { |
| 36 | throw new Error(`Invalid file path in @mention (must be workspace-relative): ${filePath}`); |
| 37 | } |
| 38 | |
| 39 | // SSH uses POSIX paths; local runtime can use the platform resolver. |
| 40 | const pathModule = runtime instanceof SSHRuntime ? path.posix : path; |
| 41 | const cleaned = runtime instanceof SSHRuntime ? filePath.replace(/\\/g, "/") : filePath; |
| 42 | |
| 43 | const resolved = pathModule.resolve(workspacePath, cleaned); |
| 44 | const relative = pathModule.relative(workspacePath, resolved); |
| 45 | |
| 46 | // Note: relative === "" means "same directory" (the workspace root itself). |
| 47 | if (relative === "" || relative === ".") { |
| 48 | throw new Error(`Invalid file path in @mention (expected a file, got directory): ${filePath}`); |
| 49 | } |
| 50 | |
| 51 | if (relative.startsWith("..") || pathModule.isAbsolute(relative)) { |
| 52 | throw new Error(`Invalid file path in @mention (path traversal): ${filePath}`); |
| 53 | } |
| 54 | |
| 55 | return resolved; |
| 56 | } |
| 57 | |
| 58 | function guessCodeFenceLanguage(filePath: string): string { |
| 59 | const ext = path.extname(filePath).toLowerCase(); |
no test coverage detected