Laravel Packages

BashBox: A Sandboxed Bash Interpreter for AI Agents in PHP

Published
BashBox: A Sandboxed Bash Interpreter for AI Agents in PHP image

BashBox is a bash interpreter written in pure PHP by Pushpak Chhajed, who also built the Grant roles and permissions package. It runs scripts that AI agents generate without calling proc_open, exec, or shell_exec. Each command is a PHP class that works against a virtual filesystem, and every execution runs under limits you can configure.

Running Scripts

Create a Bash instance and pass a script to exec(). The result holds the script's output and exit code:

use BashBox\Bash;
 
$bash = new Bash;
 
$result = $bash->exec('
NAME="BashBox"
echo "Hello, $NAME" | tr a-z A-Z
');
 
$result->stdout; // "HELLO, BASHBOX\n"
$result->exitCode; // 0

The interpreter supports pipes, fd redirections such as 2>&1, here-docs, process substitution, arrays, functions, control flow, parameter expansion, and set/shopt options. It ships 58 commands, including grep, sed, sort, find, xargs, and sha256sum, plus builtins like cd, export, source, read, and trap. There is no sh or bash binary to call. A script that fails to parse returns exit code 2 with bash's syntax error on stderr instead of throwing an exception.

You can seed the sandbox with files and environment variables when you create it:

use BashBox\Bash;
use BashBox\BashOptions;
 
$bash = new Bash(new BashOptions(
initialFiles: [
'/home/user/app.log' => "INFO boot\nERROR db timeout\nINFO ready\n",
],
env: ['APP_ENV' => 'production'],
));
 
$bash->exec('grep ERROR app.log')->stdout; // "ERROR db timeout\n"

For a single call, ExecOptions passes stdin, extra environment variables, or different limits to exec().

Filesystem Backends

The default InMemoryFs never touches disk. Three other backends give a script access to real files:

  • OverlayFs reads from a real directory and keeps writes in an in-memory copy-on-write layer
  • ReadWriteFs reads and writes to disk, confined to a root directory
  • MountableFs mounts different backends at different paths
use BashBox\Filesystem\InMemoryFs;
use BashBox\Filesystem\MountableFs;
use BashBox\Filesystem\OverlayFs;
use BashBox\Filesystem\ReadWriteFs;
 
// Read a real project, keep all writes in memory
$bash = new Bash(new BashOptions(
fs: new OverlayFs('/path/to/project'),
));
 
// Keep everything in memory except a writable /data mount
$mount = new MountableFs(new InMemoryFs);
$mount->mount('/data', new ReadWriteFs('/real/data'));
$bash = new Bash(new BashOptions(fs: $mount));

Each backend has a disk quota of 64 MB and 10,000 entries by default. A write past the quota fails with No space left on device, as it would on a full disk. BashBox also blocks path traversal and null-byte injection, and OverlayFs denies symlinks by default when it reads from a real directory.

Execution Limits

The Limits class has 18 settings, from command count and loop iterations to here-doc size and parse tree depth. Going over one throws BashBox\Exceptions\ExecutionLimitException:

use BashBox\Limits;
 
$bash = new Bash(new BashOptions(
limits: new Limits(
maxCommandCount: 100,
maxLoopIterations: 500,
maxOutputSize: 1024 * 1024, // 1MB
maxCallDepth: 10,
),
));

The defaults allow 10,000 commands per script, 10,000 iterations per loop, 10 MB of output, a function call depth of 100, and 1 MB of script text. ExecOptions can override the limits for one exec() call, except the filesystem quota, which is set when you create the Bash instance.

Network Access

Network access is off by default, and the curl command does not exist until you pass a NetworkConfig:

use BashBox\Network\NetworkConfig;
 
$bash = new Bash(new BashOptions(
network: new NetworkConfig(
allowedUrlPrefixes: ['https://api.example.com/'],
allowedMethods: ['GET', 'POST'],
denyPrivateRanges: true,
maxResponseSize: 5 * 1024 * 1024, // 5MB
timeout: 10,
),
));

Scripts can then call curl on any URL under an allowed prefix. The denyPrivateRanges option is on by default. It refuses loopback, private, link-local, and other non-public IPv4 and IPv6 addresses, and checks both the URL's host and the address curl connects to. Like real curl, BashBox's curl follows redirects only with -L. It checks each redirect target against the allow-list and drops Authorization and Cookie headers on cross-origin redirects. An empty allowedUrlPrefixes list denies every URL. To allow any URL, set dangerouslyAllowFullInternetAccess: true.

Custom Commands

To add a command BashBox doesn't ship, extend AbstractCommand and register it on the Bash instance:

use BashBox\Commands\AbstractCommand;
use BashBox\Commands\CommandContext;
use BashBox\ExecResult;
 
class MyCommand extends AbstractCommand
{
public function getName(): string
{
return 'mycommand';
}
 
public function execute(array $args, CommandContext $ctx): ExecResult
{
return $this->success('Hello from my command!');
}
}
 
$bash->registerCommand(new MyCommand);
 
$bash->exec('mycommand')->stdout; // "Hello from my command!"

Installation

BashBox requires PHP 8.4 or later and has no other Composer dependencies. You can call it from a tool in the Laravel AI SDK or any other agent library and return the output to the model. With the optional ffi extension, BashBox uses the RE2 regex engine. Without it, BashBox uses PCRE with strict limits.

composer require shipfastlabs/bashbox

BashBox is MIT licensed, and the latest release is v0.0.3. You can find the source and full documentation in the BashBox repository on GitHub.

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

masteringlaravel logo
Laravel Code Review

Get expert guidance in a few days with a Laravel code review

Visit Laravel Code Review

The latest

View all →
Look What You Helped Me Build image

Look What You Helped Me Build

Read article
Running Laravel's Scheduler on Multiple Servers image

Running Laravel's Scheduler on Multiple Servers

Read article
Laravel Fake Assertions Now Accept Property Arrays image

Laravel Fake Assertions Now Accept Property Arrays

Read article
Route::query() and Model defaults() in Laravel 13.35 image

Route::query() and Model defaults() in Laravel 13.35

Read article
Laravel AI SDK 1.1 Adds Agent Skills and Cohere Chat image

Laravel AI SDK 1.1 Adds Agent Skills and Cohere Chat

Read article
VMPal: Give Your AI Agent a Whole Computer image

VMPal: Give Your AI Agent a Whole Computer

Read article