ShellSyntaxTree 0.1.5
See the version list below for details.
dotnet add package ShellSyntaxTree --version 0.1.5
NuGet\Install-Package ShellSyntaxTree -Version 0.1.5
<PackageReference Include="ShellSyntaxTree" Version="0.1.5" />
<PackageVersion Include="ShellSyntaxTree" Version="0.1.5" />
<PackageReference Include="ShellSyntaxTree" />
paket add ShellSyntaxTree --version 0.1.5
#r "nuget: ShellSyntaxTree, 0.1.5"
#:package ShellSyntaxTree@0.1.5
#addin nuget:?package=ShellSyntaxTree&version=0.1.5
#tool nuget:?package=ShellSyntaxTree&version=0.1.5
ShellSyntaxTree
A focused .NET library that parses bash 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.1.0-alpha
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
Public API surface (locked for v0.1)
namespace ShellSyntaxTree;
public interface IShellParser { ParsedCommand Parse(string command); }
public sealed class BashParser : IShellParser { /* … */ }
public sealed record BashParserOptions { /* HomeDirectory, WorkingDirectory */ }
public sealed record ParsedCommand { /* Source, Clauses, IsUnparseable, … */ }
public sealed record Clause { /* Operator, Verb, Args, Redirects, … */ }
public sealed record VerbChain { /* Tokens, Joined */ }
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 RedirectDirection { In, Out, Append, ErrOut, ErrAppend }
public enum CompoundOperator { None, AndIf, OrIf, Sequence, Pipe }
PowerShell and Windows cmd parsers are deferred to later versions; the
IShellParser seam is in place so consumers don't refactor when they
ship.
Full behavioral contract: SPEC.md.
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"
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 policy code — ~50 lines.
ShellSyntaxTree.Web.Sample — Blazor WebAssembly Mermaid visualizer
Paste a bash 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 recursion.
dotnet run --project samples/ShellSyntaxTree.Web.Sample
# → http://localhost:5239
The visualizer ships preset scripts demonstrating compound commands,
subshell isolation, bash -c recursion, dynamic-cwd attribution, and
unparseable inputs (control-flow, function definitions). 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.0-alpha — first publishable cut. Bash-only.
- 0.1.x — additive (more verb table entries, more corpus, bug fixes).
- 0.2.0 — first PowerShell parser.
- 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 |
tests/ShellSyntaxTree.Tests/ |
xUnit unit tests + corpus runner |
tests/ShellSyntaxTree.Tests/Corpus/bash/*.json |
115 corpus entries — the acceptance contract |
samples/ShellSyntaxTree.Cli.Sample/ |
Console explainer + audit policy |
samples/ShellSyntaxTree.Web.Sample/ |
Blazor WASM Mermaid visualizer |
SPEC.md |
Locked v0.1 contract |
openspec/ |
Change-proposal history (rationale for v0.1 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
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.0-alpha | 0 | 5/20/2026 |
| 0.1.5 | 2,028 | 5/16/2026 |
| 0.1.5-beta | 312 | 5/15/2026 |
| 0.1.4 | 87 | 5/15/2026 |
| 0.1.4-alpha | 825 | 5/12/2026 |
| 0.1.3-alpha | 92 | 5/12/2026 |
| 0.1.2-alpha | 86 | 5/11/2026 |
| 0.1.1-alpha | 120 | 5/11/2026 |
| 0.1.0-alpha | 84 | 5/11/2026 |