(args: string[])
| 1582 | const DANGEROUS_GIT_SHORT_FLAGS_ATTACHED = ['-c', '-C'] |
| 1583 | |
| 1584 | function isGitSafe(args: string[]): boolean { |
| 1585 | if (args.length === 0) { |
| 1586 | return true |
| 1587 | } |
| 1588 | |
| 1589 | // SECURITY: Reject any arg containing `$` (variable reference). Bare |
| 1590 | // VariableExpressionAst positionals reach here as literal text ($env:SECRET, |
| 1591 | // $VAR). deriveSecurityFlags does not gate bare Variable args. The validator |
| 1592 | // sees `$VAR` as text; PowerShell expands it at runtime. Parser differential: |
| 1593 | // git diff $VAR where $VAR = '--output=/tmp/evil' |
| 1594 | // → validator sees positional '$VAR' → validateFlags passes |
| 1595 | // → PowerShell runs `git diff --output=/tmp/evil` → file write |
| 1596 | // This generalizes the ls-remote inline `$` guard below to all git subcommands. |
| 1597 | // Bash equivalent: BashTool blanket |
| 1598 | // `$` rejection at readOnlyValidation.ts:~1352. isGhSafe has the same guard. |
| 1599 | for (const arg of args) { |
| 1600 | if (arg.includes('$')) { |
| 1601 | return false |
| 1602 | } |
| 1603 | } |
| 1604 | |
| 1605 | // Skip over global flags before the subcommand, rejecting dangerous ones. |
| 1606 | // Flags that take space-separated values must consume the next token so it |
| 1607 | // isn't mistaken for the subcommand (e.g. `git --namespace foo status`). |
| 1608 | let idx = 0 |
| 1609 | while (idx < args.length) { |
| 1610 | const arg = args[idx] |
| 1611 | if (!arg || !arg.startsWith('-')) { |
| 1612 | break |
| 1613 | } |
| 1614 | // SECURITY: Attached-form short flags. `-ccore.pager=sh` splits on `=` to |
| 1615 | // `-ccore.pager`, which isn't in DANGEROUS_GIT_GLOBAL_FLAGS. Git accepts |
| 1616 | // `-c<name>=<value>` and `-C<path>` with no space. We must prefix-match. |
| 1617 | // Note: `--cached`, `--config-env`, etc. already fail startsWith('-c') at |
| 1618 | // position 1 (`-` ≠ `c`). The `!== '-'` guard only applies to `-c` |
| 1619 | // (git config keys never start with `-`, so `-c-key` is implausible). |
| 1620 | // It does NOT apply to `-C` — directory paths CAN start with `-`, so |
| 1621 | // `git -C-trap status` must reject. `git -ccore.pager=sh log` spawns a shell. |
| 1622 | for (const shortFlag of DANGEROUS_GIT_SHORT_FLAGS_ATTACHED) { |
| 1623 | if ( |
| 1624 | arg.length > shortFlag.length && |
| 1625 | arg.startsWith(shortFlag) && |
| 1626 | (shortFlag === '-C' || arg[shortFlag.length] !== '-') |
| 1627 | ) { |
| 1628 | return false |
| 1629 | } |
| 1630 | } |
| 1631 | const hasInlineValue = arg.includes('=') |
| 1632 | const flagName = hasInlineValue ? arg.split('=')[0] || '' : arg |
| 1633 | if (DANGEROUS_GIT_GLOBAL_FLAGS.has(flagName)) { |
| 1634 | return false |
| 1635 | } |
| 1636 | // Consume the next token if the flag takes a separate value |
| 1637 | if (!hasInlineValue && GIT_GLOBAL_FLAGS_WITH_VALUES.has(flagName)) { |
| 1638 | idx += 2 |
| 1639 | } else { |
| 1640 | idx++ |
| 1641 | } |
no test coverage detected