Parity gap
The Bash parser recurses into static command strings for bash -c and sh -c.
The PowerShell parser recurses into pwsh -Command and pwsh -EncodedCommand. However, it does not recurse into Invoke-Expression or its iex alias.
These constructs have different process and scope rules. They still share one security property: each construct executes a string as shell code.
ShellSyntaxTree currently gives consumers different visibility into that code:
- bash -c 'rm -rf /tmp/x' surfaces the inner rm clause.
- iex 'Remove-Item C:\x' surfaces only the outer Invoke-Expression clause.
SPEC.POWERSHELL.md section 10 makes this difference explicit. PowerShell corpus case 157 locks the current behavior.
Why this matters
ShellSyntaxTree feeds security gates that evaluate the surfaced command verbs.
A consumer can inspect a static Bash command string. The same consumer cannot inspect an equivalent static PowerShell expression string.
A persistent approval for Invoke-Expression can then authorize a different payload later.
Netclaw PR #1733 exposes this difference in its PowerShell approval corpus:
netclaw-dev/netclaw#1733
Required behavior
ShellSyntaxTree should give both grammars the same safe-fail contract for command strings.
- Recurse into a PowerShell expression string only when the parser proves that the string is static.
- Apply the behavior to Invoke-Expression and the canonical iex alias.
- Surface the inner clauses for consumer policy checks.
- Preserve the current PowerShell location because Invoke-Expression uses the current scope.
- Apply the existing input-size and recursion-depth limits.
- Mark an uncertain payload as DynamicSkip or IsUnparseable.
- Do not return a clean, persistently approvable Invoke-Expression clause for a dynamic payload.
Dynamic forms include variables, interpolation, concatenation, subexpressions, pipeline input, and other computed expressions.
Examples
Static input:
Expected result: the AST surfaces the canonical Remove-Item clause and its path.
Dynamic input:
Expected result: the AST exposes the payload as dynamic.
Interpolated input:
Expected result: the AST exposes the payload as dynamic.
Acceptance criteria
- Update SPEC.POWERSHELL.md section 10.
- Replace corpus case 157 with static and dynamic cases.
- Add coverage for Invoke-Expression and iex.
- Add coverage for literal, interpolated, variable, and pipeline payloads.
- Preserve PowerShell location attribution for static inner clauses.
- Preserve the current public API unless the design requires an approved API change.
- Keep all existing Bash and PowerShell corpus cases green.
Parity gap
The Bash parser recurses into static command strings for bash -c and sh -c.
The PowerShell parser recurses into pwsh -Command and pwsh -EncodedCommand. However, it does not recurse into Invoke-Expression or its iex alias.
These constructs have different process and scope rules. They still share one security property: each construct executes a string as shell code.
ShellSyntaxTree currently gives consumers different visibility into that code:
SPEC.POWERSHELL.md section 10 makes this difference explicit. PowerShell corpus case 157 locks the current behavior.
Why this matters
ShellSyntaxTree feeds security gates that evaluate the surfaced command verbs.
A consumer can inspect a static Bash command string. The same consumer cannot inspect an equivalent static PowerShell expression string.
A persistent approval for Invoke-Expression can then authorize a different payload later.
Netclaw PR #1733 exposes this difference in its PowerShell approval corpus:
netclaw-dev/netclaw#1733
Required behavior
ShellSyntaxTree should give both grammars the same safe-fail contract for command strings.
Dynamic forms include variables, interpolation, concatenation, subexpressions, pipeline input, and other computed expressions.
Examples
Static input:
Expected result: the AST surfaces the canonical Remove-Item clause and its path.
Dynamic input:
Expected result: the AST exposes the payload as dynamic.
Interpolated input:
Expected result: the AST exposes the payload as dynamic.
Acceptance criteria