* Extract file paths from a parsed PowerShell command element. * Uses the AST args to find positional and named path parameters. * * If any path argument has a complex elementType (e.g., array literal, * subexpression) that cannot be statically validated, sets * hasUnvalidatablePathArg so the c
(cmd: ParsedCommandElement)
| 1302 | * hasUnvalidatablePathArg so the caller can force an ask. |
| 1303 | */ |
| 1304 | function extractPathsFromCommand(cmd: ParsedCommandElement): { |
| 1305 | paths: string[] |
| 1306 | operationType: FileOperationType |
| 1307 | hasUnvalidatablePathArg: boolean |
| 1308 | optionalWrite: boolean |
| 1309 | } { |
| 1310 | const canonical = resolveToCanonical(cmd.name) |
| 1311 | const config = CMDLET_PATH_CONFIG[canonical] |
| 1312 | |
| 1313 | if (!config) { |
| 1314 | return { |
| 1315 | paths: [], |
| 1316 | operationType: 'read', |
| 1317 | hasUnvalidatablePathArg: false, |
| 1318 | optionalWrite: false, |
| 1319 | } |
| 1320 | } |
| 1321 | |
| 1322 | // Build per-cmdlet known-param sets, merging in common parameters. |
| 1323 | const switchParams = [...config.knownSwitches, ...COMMON_SWITCHES] |
| 1324 | const valueParams = [...config.knownValueParams, ...COMMON_VALUE_PARAMS] |
| 1325 | |
| 1326 | const paths: string[] = [] |
| 1327 | const args = cmd.args |
| 1328 | // elementTypes[0] is the command name; elementTypes[i+1] corresponds to args[i] |
| 1329 | const elementTypes = cmd.elementTypes |
| 1330 | let hasUnvalidatablePathArg = false |
| 1331 | let positionalsSeen = 0 |
| 1332 | const positionalSkip = config.positionalSkip ?? 0 |
| 1333 | |
| 1334 | function checkArgElementType(argIdx: number): void { |
| 1335 | if (!elementTypes) return |
| 1336 | const et = elementTypes[argIdx + 1] |
| 1337 | if (et && !SAFE_PATH_ELEMENT_TYPES.has(et)) { |
| 1338 | hasUnvalidatablePathArg = true |
| 1339 | } |
| 1340 | } |
| 1341 | |
| 1342 | // Extract named parameter values (e.g., -Path "C:\foo") |
| 1343 | for (let i = 0; i < args.length; i++) { |
| 1344 | const arg = args[i] |
| 1345 | if (!arg) continue |
| 1346 | |
| 1347 | // Check if this arg is a parameter name. |
| 1348 | // SECURITY: Use elementTypes as ground truth. PowerShell's tokenizer |
| 1349 | // accepts en-dash/em-dash/horizontal-bar (U+2013/2014/2015) as parameter |
| 1350 | // prefixes; a raw startsWith('-') check misses `–Path` (en-dash). The |
| 1351 | // parser maps CommandParameterAst → 'Parameter' regardless of dash char. |
| 1352 | // isPowerShellParameter also correctly rejects quoted "-Include" |
| 1353 | // (StringConstant, not a parameter). |
| 1354 | const argElementType = elementTypes ? elementTypes[i + 1] : undefined |
| 1355 | if (isPowerShellParameter(arg, argElementType)) { |
| 1356 | // Handle colon syntax: -Path:C:\secret |
| 1357 | // Normalize Unicode dash to ASCII `-` (pathParams are stored with `-`). |
| 1358 | const normalized = '-' + arg.slice(1) |
| 1359 | const colonIdx = normalized.indexOf(':', 1) // skip first char (the dash) |
| 1360 | const paramName = |
| 1361 | colonIdx > 0 ? normalized.substring(0, colonIdx) : normalized |
no test coverage detected