* Checks for dangerous Start-Process patterns. * * Two vectors: * 1. `-Verb RunAs` — privilege escalation (UAC prompt). * 2. Launching a PowerShell executable — nested invocation. * `Start-Process pwsh -ArgumentList "-e "` evades * checkEncodedCommand/checkPwshCommandOrFile because cmd.na
( parsed: ParsedPowerShellCommand, )
| 548 | * executable: the nested invocation is unvalidatable by construction. |
| 549 | */ |
| 550 | function checkStartProcess( |
| 551 | parsed: ParsedPowerShellCommand, |
| 552 | ): PowerShellSecurityResult { |
| 553 | for (const cmd of getAllCommands(parsed)) { |
| 554 | const lower = cmd.name.toLowerCase() |
| 555 | if (lower !== 'start-process' && lower !== 'saps' && lower !== 'start') { |
| 556 | continue |
| 557 | } |
| 558 | // Vector 1: -Verb RunAs (space or colon syntax). |
| 559 | // Space syntax: psExeHasParamAbbreviation finds -Verb/-v, then scan args |
| 560 | // for a bare 'runas' token. |
| 561 | if ( |
| 562 | psExeHasParamAbbreviation(cmd, '-Verb', '-v') && |
| 563 | cmd.args.some(a => a.toLowerCase() === 'runas') |
| 564 | ) { |
| 565 | return { |
| 566 | behavior: 'ask', |
| 567 | message: 'Command requests elevated privileges', |
| 568 | } |
| 569 | } |
| 570 | // Colon syntax — two layers: |
| 571 | // (a) Structural: PR #23554 added children[] for colon-bound param args. |
| 572 | // children[i] = [{type, text}] for the bound value. Check if any |
| 573 | // -v*-prefixed param has a child whose text normalizes (strip |
| 574 | // quotes/backtick/whitespace) to 'runas'. Robust against arbitrary |
| 575 | // quoting the regex can't anticipate. |
| 576 | // (b) Regex fallback: for parsed output without children[] or as |
| 577 | // defense-in-depth. -Verb:'RunAs', -Verb:"RunAs", -Verb:`runas all |
| 578 | // bypassed the old /...:runas$/ pattern because the quote/tick broke |
| 579 | // the match. |
| 580 | if (cmd.children) { |
| 581 | for (let i = 0; i < cmd.args.length; i++) { |
| 582 | // Strip backticks before matching param name (bug #14): -V`erb:RunAs |
| 583 | const argClean = cmd.args[i]!.replace(/`/g, '') |
| 584 | if (!/^[-\u2013\u2014\u2015/]v[a-z]*:/i.test(argClean)) continue |
| 585 | const kids = cmd.children[i] |
| 586 | if (!kids) continue |
| 587 | for (const child of kids) { |
| 588 | if (child.text.replace(/['"`\s]/g, '').toLowerCase() === 'runas') { |
| 589 | return { |
| 590 | behavior: 'ask', |
| 591 | message: 'Command requests elevated privileges', |
| 592 | } |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | } |
| 597 | if ( |
| 598 | cmd.args.some(a => { |
| 599 | // Strip backticks before matching (bug #14 / review nit #2) |
| 600 | const clean = a.replace(/`/g, '') |
| 601 | return /^[-\u2013\u2014\u2015/]v[a-z]*:['"` ]*runas['"` ]*$/i.test( |
| 602 | clean, |
| 603 | ) |
| 604 | }) |
| 605 | ) { |
| 606 | return { |
| 607 | behavior: 'ask', |
nothing calls this directly
no test coverage detected