org.llm4s.toolapi.builtin.shell
Shell tools for executing system commands.
These tools provide safe shell access with configurable command allowlists and execution limits.
== Configuration ==
All shell tools require explicit command allowlisting via ShellConfig:
- Allowed commands list (required for any execution)
- Working directory configuration
- Timeout limits
- Output size limits
== Available Tools ==
- ShellTool: Execute shell commands (requires explicit allowlist)
Attributes
- Example
-
import org.llm4s.toolapi.builtin.shell._ import org.llm4s.toolapi.ToolRegistry // Read-only shell (ls, cat, etc.) val readOnlyShell = ShellTool.create(ShellConfig.readOnly()) // Development shell with common dev tools val devShell = ShellTool.create(ShellConfig.development( workingDirectory = Some("/path/to/project") )) // Custom restricted shell val customShell = ShellTool.create(ShellConfig( allowedCommands = Seq("git", "npm"), timeout = 60.seconds )) val tools = new ToolRegistry(Seq(devShell))
Members list
Type members
Classlikes
Configuration for shell command tool.
Configuration for shell command tool.
== Environment == A command does not inherit the process environment, which is where provider API keys and other secrets live. It gets only the variables named in inheritedEnvironment (by default PATH, LANG, LC_ALL, TERM and SystemRoot, whichever exist), plus everything in environment. ShellConfig.development inherits the whole environment, because build tools need it; it is documented as not a sandbox.
== File arguments == Without pathPolicy the tool does not look at the files a command names: cat reads any file the process can read, whatever the file tools are configured to allow. Set pathPolicy to hold every file-like argument to the file tools' allowed and blocked entries and real-location rule (FileConfig.isPathAllowed) (see org.llm4s.toolapi.builtin.filesystem.FileConfig): each argument is resolved against the working directory and judged as the program will hand it to the OS: a .. after a link is read both as POSIX applies it (linksub/.. is the parent of the link's target) and as Windows does (linksub/.. is the working directory), and both locations must be allowed, the working directory itself must be allowed, and a flag that carries a path (-f/etc/passwd) is refused. A value attached to a flag is checked as a path as well, since the program may open it: the value after = of a long option (--file=lout) and every tail of a short-option cluster after the dash (-iflout gives iflout, flout, lout, ...), so a link out of the allowed directory, a blocked file or .. given that way is refused. Which options take a value is not modelled, so a text value that happens to name such a file (grep -e..) is refused too. sort -t's separator is the exception: a value attached to it (sort -to, sort -t/) is text, not checked (except on Windows, where -t is refused; see below). A value or tail that is one plain path component (no / or \, not . or .., and none of what Windows reads specially: :, a trailing . or space, *?"<>|, control characters, a reserved device name) costs one lookup: whether anything is in the working directory under that name. If nothing is, the policy is applied to its spelling there with no further lookup, which is exactly what the full check would decide, since a name that is not there cannot be a link; if something is, it is resolved in full like any other path. No limit on the length of a file name is assumed. The checks are bounded by the command, not by each argument: an argument longer than 4096 characters is refused, each distinct path is checked once, and a command whose checks would take more than 20000 file-system lookups (one per plain component, two per component of a path resolved in full) is refused as too costly to check, so a command of thousands of distinct long flags is refused in well under a second instead of being checked for minutes before it starts. -- is not taken as the end of the options, since a program may read it as the argument of the option before it: every argument is checked as a path, and also as a flag when it starts with -. The working directory is checked for every command. A hard link inside an allowed directory to a file elsewhere passes, as it does for the file tools: no path check can see it. Arguments of echo, pwd, date, whoami and which are not treated as paths. ls -L and ls -H (and --dereference*), which follow links while listing, are refused when a policy is set.
== Refused options == Whatever the policy, the options that make an otherwise read-only command write a file, run a program or read a file it does not name as an argument are refused: file -C, -m, -M, -f (--compile, --magic-file, --files-from), date -f, -r (--file, --reference), wc --files0-from, and, for an allowlist that adds sort, sort -o (--output), -T (--temporary-directory), --compress-program and --files0-from; on Windows, where sort may be sort.exe, also sort -t and any sort switch starting with /O or /T, in either case. A short option is refused anywhere in a cluster, with its value attached or not (-bC, -Mmagic, -roout), but not in the value of sort -t (sort -to sets the separator to o). Long options are matched on any prefix of at least one letter, since GNU programs accept an unambiguous abbreviation (date --fil), with or without =value, and wherever they appear, after a -- too (file -F -- -f list reads -f list as an option). These rules match the program by its file name, ignoring case and a Windows executable suffix, so /usr/bin/file -C, FILE -C and file.exe -C are refused as file -C is. A command that walks directories by itself (ls -R, grep -r, find) is checked at its starting point only.
Value parameters
- allowedCommands
-
List of allowed command names (e.g., "ls", "cat", "echo"). If empty, no commands are allowed.
- environment
-
Additional environment variables to set. They are set after the inherited ones and win.
- inheritedEnvironment
-
Names of the process variables a command receives.
Nonepasses the whole environment through. - maxOutputSize
-
Maximum output size in characters.
- pathPolicy
-
Containment rule for the file-like arguments of a command, or
Nonefor no check. - timeout
-
Maximum execution time.
- workingDirectory
-
Optional working directory for command execution.
Attributes
- Companion
- object
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
trait Producttrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
ShellConfig.type
Shell command execution result.
Shell command execution result.
Attributes
- Companion
- object
- Supertypes
-
trait Serializabletrait Producttrait Equalsclass Objecttrait Matchableclass AnyShow all
Attributes
- Companion
- class
- Supertypes
-
trait Producttrait Mirrorclass Objecttrait Matchableclass Any
- Self type
-
ShellResult.type
Tool for executing shell commands under a strict allowlist.
Tool for executing shell commands under a strict allowlist.
IMPORTANT: This tool requires an explicit allowlist of commands for safety. It will not execute any command whose first token is not in the allowlist.
== Execution model ==
Commands are tokenized with shell-style quoting (see CommandTokenizer) and executed directly via ProcessBuilder '''without invoking a shell'''. As a consequence:
- No glob expansion (
*,?,[abc]are passed literally to the program). - No variable substitution (
$VAR,${VAR},$(...), backticks). - No command chaining or redirection (
&&,;,|,<,>,&). - Quoted arguments are honoured:
echo "hello world"runsechowith one argumenthello world, not two.
Combined with the allowlist, this means LLM-supplied input that contains shell metacharacters cannot escape into a shell — characters such as && survive tokenization as literal argument bytes and are handed to the allowlisted program, which generally treats them as harmless input.
== Environment and files ==
A command receives a scrubbed environment (see ShellConfig): only the variables named in inheritedEnvironment, plus environment. The files a command names are not checked unless ShellConfig.pathPolicy is set; then every file-like argument must pass that FileConfig's isPathAllowed: the file tools' allowed and blocked entries and real-location rule, with the argument judged as the program will hand it to the OS, .. and all. A .. after a link is read both ways: physically, as POSIX applies it (the link target's parent), and lexically, as Windows applies it (the directory holding the link), and both locations must be allowed. The file tools, which open the path themselves, remove .. as text first. A value attached to a flag is checked as a path too: the value after = of a long option (--file=x), and every tail of a short-option cluster (-fx, -ifx), within a budget of file-system lookups for the whole command. A value that is one plain path component costs one lookup - whether anything is there - and is resolved in full only when something is; this gives the full check's verdict exactly, with no assumption about name lengths. file -C, -m, -M and -f, date -f and -r, wc --files0-from, and sort -o, --output, -T, --temporary-directory, --compress-program and --files0-from (which write a file, run a program or read one the command does not name) are refused whatever the policy: anywhere in a short-option cluster, attached value or not, under any abbreviation of the long form, with or without =value, wherever they appear, -- or not before them, and however the program is spelled (/usr/bin/file, FILE, file.exe).
On Windows, with or without a path policy, an argument, the working directory and an environment value are refused when they hold a character a program reading its command line in an ANSI code page may receive as a different ASCII character ("best fit": ../../secret as ../../secret, -o as -o; see WindowsBestFit, #1766). On a double-byte ANSI host (code pages 932, 936, 950) an argument the JDK puts in quotes is also refused when it holds a character whose second byte is 0x5C, the byte of \ (a 表, #1807).
== Features ==
- Command allowlist for security
- Configurable working directory
- Optional path policy for file arguments
- Scrubbed environment
- Timeout support
- Output size limits
Attributes
- Example
-
{ import org.llm4s.toolapi.builtin.shell._ // Read-only shell (safe commands) val readOnlyShell = ShellTool.create(ShellConfig.readOnly()) // Development shell (common dev tools) val devShell = ShellTool.create(ShellConfig.development( workingDirectory = Some("/home/user/project") )) val tools = new ToolRegistry(Seq(devShell)) agent.run("List files in the current directory", tools)}
- Supertypes
-
class Objecttrait Matchableclass Any
- Self type
-
ShellTool.type