* Validates that a groupId is safe to use as a single path segment. * Rejects path traversal attempts and path separators to prevent writing outside intended directory. * * @param groupId - The groupId to validate * @throws Error if groupId contains unsafe characters or path traversal sequences
(groupId: string)
| 26 | * @throws Error if groupId contains unsafe characters or path traversal sequences |
| 27 | */ |
| 28 | function validateGroupId(groupId: string): void { |
| 29 | // Reject empty or whitespace-only groupIds |
| 30 | if (!groupId || groupId.trim().length === 0) { |
| 31 | throw new Error('groupId cannot be empty or whitespace-only'); |
| 32 | } |
| 33 | |
| 34 | // Reject path separators (both forward and backward slashes) |
| 35 | if (groupId.includes('/') || groupId.includes('\\')) { |
| 36 | throw new Error('groupId cannot contain path separators'); |
| 37 | } |
| 38 | |
| 39 | // Reject relative path components |
| 40 | if (groupId === '..' || groupId === '.') { |
| 41 | throw new Error('groupId cannot be "." or ".."'); |
| 42 | } |
| 43 | |
| 44 | // Reject null bytes which can be used to bypass validation |
| 45 | if (groupId.includes('\0')) { |
| 46 | throw new Error('groupId cannot contain null bytes'); |
| 47 | } |
| 48 | |
| 49 | // Validate that the resolved path stays within the intended directory |
| 50 | // This catches cases where the path library normalizes to a parent directory |
| 51 | const normalized = path.normalize(groupId); |
| 52 | if (normalized !== groupId || normalized.startsWith('..')) { |
| 53 | throw new Error( |
| 54 | `groupId normalization resulted in unsafe path: ${normalized}`, |
| 55 | ); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | // eslint-disable-next-line functional/no-let |
| 60 | let shardCount = 0; |