A restricted shell interpreter for Go. Designed for AI agents that need to run shell commands safely.
go get github.com/DataDog/rshellpackage main
import (
"context"
"os"
"strings"
"github.com/DataDog/rshell/interp"
"mvdan.cc/sh/v3/syntax"
)
func main() {
script := `echo "hello from rshell"`
prog, _ := syntax.NewParser().Parse(strings.NewReader(script), "")
runner, _ := interp.New(
interp.StdIO(nil, os.Stdout, os.Stderr),
interp.AllowedCommands([]string{"rshell:echo"}),
)
defer runner.Close()
runner.Run(context.Background(), prog)
}Every access path is default-deny:
| Resource | Default | Opt-in |
|---|---|---|
| Command execution | All commands blocked (exit code 127) | AllowedCommands with namespaced command list (e.g. rshell:cat), or AllowAllCommands |
| External commands | Blocked (exit code 127) | Provide an ExecHandler |
| Filesystem access | Blocked | Configure AllowedPaths with directory list |
| Environment variables | Empty (no host env inherited) | Pass variables via the Env option |
| Output redirections | Only /dev/null allowed (exit code 2 for other targets) |
>/dev/null, 2>/dev/null, &>/dev/null, 2>&1 |
AllowedCommands restricts which commands (builtins or external) the interpreter may execute. Commands must be specified with the rshell: namespace prefix (e.g. rshell:cat, rshell:echo). If not set, no commands are allowed. Use AllowAllCommands to permit all commands (useful for testing).
AllowedPaths restricts all file operations to specified directories using Go's os.Root API (openat syscalls), making it immune to symlink traversal, TOCTOU races, and .. escape attacks.
See SHELL_FEATURES.md for the complete list of supported and blocked features.
Linux, macOS, and Windows.
900+ YAML-driven test scenarios cover builtins, shell features, and security restrictions.
tests/scenarios/
├── cmd/ # builtin command tests (echo, cat, grep, head, tail, test, uniq, wc, ...)
└── shell/ # shell feature tests (pipes, variables, control flow, ...)
By default, each scenario is executed twice: once in rshell and once in a real bash shell, ensuring output parity with POSIX behavior. Scenarios that test rshell-specific restrictions (blocked commands, readonly enforcement, etc.) opt out of the bash comparison.
go test ./...After merging changes to main create a release by:
-
Navigate to the Releases page
-
Click "Draft a new release"
-
You can "Select a tag" using the dropdown or "Create a new tag"
When creating a new tag, make sure to include the
vprefix. For example, if the last release was v0.1.29, your release should be v0.1.30. -
The release title should be the same as the version tag
-
Use "Generate release notes" to fill in the release description
-
Click "Publish release"
This will create a git tag that can now be referenced in other repos. This will trigger go-releaser that will add installable artifacts to the release.