Packages
nous
0.15.7
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
lib/nous/tools/bash.ex
defmodule Nous.Tools.Bash do
@moduledoc """
Shell command execution tool.
Uses `NetRunner` for safe process execution with automatic timeout
handling and output size limits. Zero zombie processes guaranteed.
## Security
Commands run as the current OS user. Use `Nous.Permissions` to gate
access to this tool in production.
"""
@behaviour Nous.Tool.Behaviour
@default_timeout 120_000
@max_output_size 1_000_000
@impl true
def metadata do
%{
name: "bash",
description: "Execute a shell command and return its output.",
category: :execute,
requires_approval: true,
parameters: %{
"type" => "object",
"properties" => %{
"command" => %{
"type" => "string",
"description" => "The shell command to execute"
},
"timeout" => %{
"type" => "integer",
"description" => "Timeout in milliseconds. Defaults to 120000 (2 minutes)."
}
},
"required" => ["command"]
}
}
end
@impl true
def execute(_ctx, %{"command" => command} = args) do
timeout = Map.get(args, "timeout", @default_timeout)
# Use absolute path to /bin/sh and a scrubbed env so the spawned shell
# doesn't inherit OPENAI_API_KEY / BRAVE_API_KEY / TAVILY_API_KEY etc.
# The LLM can `printenv` itself one bash call away from secret leak
# if the BEAM env isn't filtered.
result =
NetRunner.run(["/bin/sh", "-c", command],
timeout: timeout,
max_output_size: @max_output_size,
env: scrubbed_env()
)
case result do
{:error, :timeout} ->
{:error, "Command timed out after #{timeout}ms"}
{:error, {:max_output_exceeded, partial}} ->
{:ok, "#{partial}\n\n[Output truncated at #{@max_output_size} bytes]"}
{:error, reason} ->
{:error, "Command failed: #{inspect(reason)}"}
{output, 0} ->
{:ok, output}
{output, exit_code} ->
{:ok, "Exit code: #{exit_code}\n#{output}"}
end
end
# Whitelist of env vars safe to forward to the shell. Everything else,
# including API keys, OAuth tokens, vault creds, and shell-loader hooks
# (LD_PRELOAD, DYLD_INSERT_LIBRARIES) is dropped.
@env_allowlist ~w(PATH HOME LANG LC_ALL TZ USER SHELL TERM)
defp scrubbed_env do
@env_allowlist
|> Enum.map(fn name -> {name, System.get_env(name)} end)
|> Enum.reject(fn {_, v} -> is_nil(v) end)
end
end