ShellSyntaxTree 0.1.5

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package ShellSyntaxTree --version 0.1.5
                    
NuGet\Install-Package ShellSyntaxTree -Version 0.1.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ShellSyntaxTree" Version="0.1.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ShellSyntaxTree" Version="0.1.5" />
                    
Directory.Packages.props
<PackageReference Include="ShellSyntaxTree" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ShellSyntaxTree --version 0.1.5
                    
#r "nuget: ShellSyntaxTree, 0.1.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ShellSyntaxTree@0.1.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ShellSyntaxTree&version=0.1.5
                    
Install as a Cake Addin
#tool nuget:?package=ShellSyntaxTree&version=0.1.5
                    
Install as a Cake Tool

ShellSyntaxTree

NuGet

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 | bash from 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_history or auditd records 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

Build script preset

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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