(
fd: string,
operator: '>' | '>>',
target: ParseEntry | undefined,
redirections: Array<{ target: string; operator: '>' | '>>' }>,
kept: ParseEntry[],
skipCount = 1,
)
| 1091 | } |
| 1092 | |
| 1093 | function handleFileDescriptorRedirection( |
| 1094 | fd: string, |
| 1095 | operator: '>' | '>>', |
| 1096 | target: ParseEntry | undefined, |
| 1097 | redirections: Array<{ target: string; operator: '>' | '>>' }>, |
| 1098 | kept: ParseEntry[], |
| 1099 | skipCount = 1, |
| 1100 | ): { skip: number; dangerous: boolean } { |
| 1101 | const isStdout = fd === '1' |
| 1102 | const isFileTarget = |
| 1103 | target && |
| 1104 | isSimpleTarget(target) && |
| 1105 | typeof target === 'string' && |
| 1106 | !/^\d+$/.test(target) |
| 1107 | const isFdTarget = typeof target === 'string' && /^\d+$/.test(target.trim()) |
| 1108 | |
| 1109 | // Always remove the fd number from kept |
| 1110 | if (kept.length > 0) kept.pop() |
| 1111 | |
| 1112 | // SECURITY: Check for dangerous expansion FIRST before any early returns |
| 1113 | // This catches cases like 2>$HOME/file or 2>%TEMP%/file |
| 1114 | if (!isFdTarget && hasDangerousExpansion(target)) { |
| 1115 | return { skip: 0, dangerous: true } |
| 1116 | } |
| 1117 | |
| 1118 | // Handle file redirection (simple targets like 2>/tmp/file) |
| 1119 | if (isFileTarget) { |
| 1120 | redirections.push({ target: target as string, operator }) |
| 1121 | |
| 1122 | // Non-stdout: preserve the redirection in the command |
| 1123 | if (!isStdout) { |
| 1124 | kept.push(fd + operator, target as string) |
| 1125 | } |
| 1126 | return { skip: skipCount, dangerous: false } |
| 1127 | } |
| 1128 | |
| 1129 | // Handle fd-to-fd redirection (e.g., 2>&1) |
| 1130 | // Only preserve for non-stdout |
| 1131 | if (!isStdout) { |
| 1132 | kept.push(fd + operator) |
| 1133 | if (target) { |
| 1134 | kept.push(target) |
| 1135 | return { skip: 1, dangerous: false } |
| 1136 | } |
| 1137 | } |
| 1138 | |
| 1139 | return { skip: 0, dangerous: false } |
| 1140 | } |
| 1141 | |
| 1142 | // Helper: Check if '(' is part of command substitution |
| 1143 | function detectCommandSubstitution( |
no test coverage detected