( _cmd: string, element?: ParsedCommandElement, )
| 74 | * arbitrary pipelines, Hashtable, etc.) is a leak vector. |
| 75 | */ |
| 76 | export function argLeaksValue( |
| 77 | _cmd: string, |
| 78 | element?: ParsedCommandElement, |
| 79 | ): boolean { |
| 80 | const argTypes = (element?.elementTypes ?? []).slice(1) |
| 81 | const args = element?.args ?? [] |
| 82 | const children = element?.children |
| 83 | for (let i = 0; i < argTypes.length; i++) { |
| 84 | if (argTypes[i] !== 'StringConstant' && argTypes[i] !== 'Parameter') { |
| 85 | // ArrayLiteralAst (`Select-Object Name, Id`) maps to 'Other' — the |
| 86 | // parse script only populates children for CommandParameterAst.Argument, |
| 87 | // so we can't inspect elements. Fall back to string-archaeology on the |
| 88 | // extent text: Hashtable has `@{`, ParenExpr has `(`, variables have |
| 89 | // `$`, type literals have `[`, scriptblocks have `{`. A comma-list of |
| 90 | // bare identifiers has none. `Name, $x` still rejects on `$`. |
| 91 | if (!/[$(@{[]/.test(args[i] ?? '')) { |
| 92 | continue |
| 93 | } |
| 94 | return true |
| 95 | } |
| 96 | if (argTypes[i] === 'Parameter') { |
| 97 | const paramChildren = children?.[i] |
| 98 | if (paramChildren) { |
| 99 | if (paramChildren.some(c => c.type !== 'StringConstant')) { |
| 100 | return true |
| 101 | } |
| 102 | } else { |
| 103 | // Fallback: string-archaeology on arg text (pre-children parsers). |
| 104 | // Reject `$` (variable), `(` (ParenExpressionAst), `@` (hash/array |
| 105 | // sub), `{` (scriptblock), `[` (type literal/static method). |
| 106 | const arg = args[i] ?? '' |
| 107 | const colonIdx = arg.indexOf(':') |
| 108 | if (colonIdx > 0 && /[$(@{[]/.test(arg.slice(colonIdx + 1))) { |
| 109 | return true |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | return false |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Allowlist of PowerShell cmdlets that are considered read-only. |
no outgoing calls
no test coverage detected