ShellSyntaxTree 0.3.0-alpha
See the version list below for details.
dotnet add package ShellSyntaxTree --version 0.3.0-alpha
NuGet\Install-Package ShellSyntaxTree -Version 0.3.0-alpha
<PackageReference Include="ShellSyntaxTree" Version="0.3.0-alpha" />
<PackageVersion Include="ShellSyntaxTree" Version="0.3.0-alpha" />
<PackageReference Include="ShellSyntaxTree" />
paket add ShellSyntaxTree --version 0.3.0-alpha
#r "nuget: ShellSyntaxTree, 0.3.0-alpha"
#:package ShellSyntaxTree@0.3.0-alpha
#addin nuget:?package=ShellSyntaxTree&version=0.3.0-alpha&prerelease
#tool nuget:?package=ShellSyntaxTree&version=0.3.0-alpha&prerelease
ShellSyntaxTree
A focused .NET library that parses bash and PowerShell command strings into a structured AST. Purpose-built for tools that need to reason about shell commands without running them — approval gates for LLM-emitted commands, CI/CD script auditors, sandbox policy generators, audit-log analytics.
Hand-rolled, AOT-trim friendly, zero native dependencies. Multi-targets
netstandard2.0 and net8.0.
dotnet add package ShellSyntaxTree --version 0.2.0
The 0.2.0 release adds PowerShell support. The latest stable
0.2.x package supports bash and PowerShell together. The public surface
documented below tracks the dev branch.
What you get
For an input like cd /repo && rm /etc/passwd, ShellSyntaxTree produces:
flowchart TD
classDef bad fill:#fee,stroke:#b00,stroke-width:2px
A[cd /repo<br/>📁 /repo] -- "&&" --> B[rm<br/>📁 /etc/passwd<br/>cwd: /repo]
class B bad
A two-clause AST where the second clause's Args includes a synthetic
/repo attribution arg (so consumers can see "this rm is implicitly
operating in /repo") and /etc/passwd is resolved and marked
IsPath = true. Hard-deny rules over /etc/* fire immediately; no
substring matching, no shelling out, no false positives.
Things you can do with it
- Approval gates for AI agents — given a command emitted by an LLM, decide ALLOW / PROMPT / DENY before invoking the shell.
- CI/CD pipeline audits — scan shell steps in GitHub Actions /
Jenkinsfile / Azure Pipelines for writes outside the workspace,
curl | bashfrom non-allowlisted hosts, hardcoded credential echoes. - Sandbox / container policy — derive the minimum-viable volume mount set or AppArmor profile from a build script.
- Pre-commit linters — flag dangerous patterns (
rm -rf /,chmod 777 /etc/*) in shell scripts at commit time. - Shell history / audit-log analytics — ingest
~/.bash_historyorauditdrecords into structured form for SIEM-style insights. - Documentation / explainers — convert complex one-liners into readable structure for tutorials and runbooks.
The original consumer is Netclaw's approval policy; the library is built to be reusable beyond that.
Quick start
using ShellSyntaxTree;
var parser = new BashParser();
var parsed = parser.Parse("cd /repo && rm /etc/passwd");
if (parsed.IsUnparseable)
{
// Safe-fail: prompt the user, deny the command, etc.
Console.WriteLine($"can't model: {parsed.UnparseableReason}");
return;
}
foreach (var clause in parsed.Clauses)
{
Console.WriteLine($"{clause.Operator} {clause.Verb.Joined}");
foreach (var arg in clause.Args.Where(a => a.IsPath))
{
var marker = arg.IsCwdAttribution ? "↳ cwd" : " path";
Console.WriteLine($" {marker}: {arg.Resolved}");
}
foreach (var redirect in clause.Redirects.Where(r => !r.IsDynamicSkip))
{
Console.WriteLine($" {redirect.Direction}: {redirect.Target}");
}
}
Run that against the example input and you get:
None cd
path: /repo
AndIf rm
↳ cwd: /repo
path: /etc/passwd
Consumer guide
The consumer guide develops the quick start into a production-oriented algorithm: parser selection, safe-fail handling, command identity, paths and cwd attribution, redirects, compounds and pipelines, and PowerShell-specific alias and dynamic-command behavior. It also links to immutable examples from Netclaw's live approval-gate integration.
Public API surface
namespace ShellSyntaxTree;
public interface IShellParser { ParsedCommand Parse(string command); }
public sealed class BashParser : IShellParser { /* … */ }
public sealed class PwshParser : IShellParser { /* … */ } // v0.2.0
public abstract record ShellParserOptions { /* HomeDirectory, WorkingDirectory */ }
public sealed record BashParserOptions : ShellParserOptions; // InitialStateMode
public sealed record PwshParserOptions : ShellParserOptions; // InitialStateMode
public sealed record ParsedCommand { /* Source, Clauses, IsUnparseable, … */ }
public sealed record Clause { /* Operator, Verb, Args, Redirects, Elements, IsSubshell, IsCommandStringWrapped */ }
public sealed record ClauseElement { /* Raw, Value, Role, source span, verb-relative position, path facts */ }
public sealed record VerbChain { /* Tokens, Joined, CanonicalVerb, IsDynamic */ }
public sealed record Arg { /* Raw, Resolved, Kind, IsPath, IsCwdAttribution, IsFlag */ }
public sealed record Redirect { /* Direction, Target, IsDynamicSkip */ }
public enum ArgKind { Literal, EnvVar, Glob, Tilde, DynamicSkip }
public enum ClauseElementRole { Verb, Argument, Redirect }
public enum RedirectDirection { In, Out, Append, ErrOut, ErrAppend }
public enum CompoundOperator { None, AndIf, OrIf, Sequence, Pipe }
Both parsers emit the same ParsedCommand AST — a consumer walks a
PowerShell parse exactly as it walks a bash one. A Windows cmd parser
remains deferred.
Behavioral contract: SPEC.md (bash + shared surface) and
SPEC.POWERSHELL.md (PowerShell).
Samples
Two runnable samples live under samples/.
ShellSyntaxTree.Cli.Sample — terminal explainer + audit policy
dotnet run --project samples/ShellSyntaxTree.Cli.Sample -- explain "cd /repo && rm /etc/passwd"
dotnet run --project samples/ShellSyntaxTree.Cli.Sample -- audit "cd /repo && rm /etc/passwd"
# --shell pwsh routes the same explain / audit logic through PwshParser:
dotnet run --project samples/ShellSyntaxTree.Cli.Sample -- explain --shell pwsh "gci C:\logs | rm"
explain pretty-prints the AST with [flag] / [path] / [cwd-attr] /
[dyn-skip] / [glob] markers per arg. audit runs a small built-in
policy ("deny writes in /etc, /usr, /bin, /sbin, /lib",
"warn on curl | bash", "warn on dynamic args in path slots") and
exits 0 / 1 / 2 by severity. See
samples/ShellSyntaxTree.Cli.Sample/Commands/AuditPolicy.cs
for the illustrative policy code.
ShellSyntaxTree.Web.Sample — Blazor WebAssembly Mermaid visualizer
Paste a bash or PowerShell script, watch the parsed AST render as a
Mermaid flowchart in your browser. Everything runs client-side — pasted
scripts never leave your machine. Useful for "what does this script
actually do?" moments and for understanding how the library models
constructs like subshells and bash -c / pwsh -Command recursion.
Static Invoke-Expression / iex payloads surface the same way, while
computed payloads safe-fail.
dotnet run --project samples/ShellSyntaxTree.Web.Sample
# → http://localhost:5239
A shell selector switches between the bash and PowerShell parsers. The presets demonstrate compound commands, Bash subshell isolation, PowerShell grouping and alias resolution, command-string recursion, dynamic-cwd attribution, and unparseable inputs. Each preset shows what the library produces in a single click.
Building from source
dotnet tool restore
dotnet build -c Release
dotnet test -c Release
dotnet pack -c Release -o ./bin/nuget
global.json pins the SDK; you need .NET 10 SDK or later for the
.slnx solution format.
Versioning
Tags are bare SemVer version numbers — no v prefix. The release
workflow asserts this and fails fast on misformatted tags.
- 0.1.x — bash parser. Additive after
0.1.0(more verb table entries, more corpus, bug fixes). - 0.2.0 — first PowerShell parser (
PwshParser). Adds the sharedShellParserOptionsbase and the additiveVerbChain.CanonicalVerb/VerbChain.IsDynamicfields; renamesClause.IsBashCWrapped→IsCommandStringWrapped(breaking — seeRELEASE_NOTES.md). - 1.0.0 — when an external consumer beyond Netclaw ships against it without finding API gaps.
License
Apache-2.0. Copyright © 2026 Aaron Stannard.
Repository layout — for contributors and curious agents:
| Path | What |
|---|---|
src/ShellSyntaxTree/ |
The library (bash + PowerShell parsers) |
tests/ShellSyntaxTree.Tests/ |
xUnit unit tests + corpus runner |
tests/ShellSyntaxTree.Tests/Corpus/<shell>/*.json |
Corpus entries — the acceptance contract (bash + powershell) |
samples/ShellSyntaxTree.Cli.Sample/ |
Console explainer + audit policy |
samples/ShellSyntaxTree.Web.Sample/ |
Blazor WASM Mermaid visualizer |
docs/CONSUMER_GUIDE.md |
Production-oriented consumer algorithm + Netclaw case study |
tools/PwshCorpusTool/ |
PowerShell corpus authoring aid |
SPEC.md, SPEC.POWERSHELL.md |
The behavioral contract |
openspec/ |
Change-proposal history (rationale for design decisions) |
PROJECT_CONTEXT.md, TOOLING.md, AGENTS.md |
Repo governance — for autonomous agents |
IMPLEMENTATION_PLAN.md |
NOW / NEXT / LATER work tracker |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on ShellSyntaxTree:
| Repository | Stars |
|---|---|
|
netclaw-dev/netclaw
Simple, secure, reliable agents. Self-hosted. Open source. Built with .NET.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.3 | 0 | 8/13/2026 |
| 0.3.2 | 0 | 8/12/2026 |
| 0.3.1 | 140 | 8/11/2026 |
| 0.3.0 | 910 | 8/11/2026 |
| 0.3.0-alpha.6 | 379 | 8/10/2026 |
| 0.3.0-alpha.5 | 407 | 8/10/2026 |
| 0.3.0-alpha.4 | 70 | 8/9/2026 |
| 0.3.0-alpha.3 | 43 | 8/9/2026 |
| 0.3.0-alpha.2 | 345 | 8/9/2026 |
| 0.3.0-alpha.1 | 83 | 8/9/2026 |
| 0.3.0-alpha | 202 | 8/8/2026 |
| 0.2.0 | 1,952 | 8/5/2026 |
| 0.2.0-beta.1 | 416 | 7/22/2026 |
| 0.2.0-alpha | 503 | 5/20/2026 |
| 0.1.5 | 19,531 | 5/16/2026 |
| 0.1.5-beta | 336 | 5/15/2026 |
| 0.1.4 | 106 | 5/15/2026 |
| 0.1.4-alpha | 936 | 5/12/2026 |
| 0.1.3-alpha | 110 | 5/12/2026 |
| 0.1.2-alpha | 101 | 5/11/2026 |