Safe shell

This commit is contained in:
2026-05-12 14:06:08 -05:00
parent e7df4cd50f
commit 5d6a102b30
4 changed files with 28 additions and 15 deletions
+2 -1
View File
@@ -51,7 +51,8 @@ int main(int argc, char** argv) {
master_registery.register_tool(make_file_read_tool());
master_registery.register_tool(make_grep_tool());
#if !defined(_WIN32)
master_registery.register_tool(make_shell_tool());
master_registery.register_tool(make_shell_tool(true)); // run_shell (safe)
master_registery.register_tool(make_shell_tool(false)); // run_dangerous_shell
#endif
// Add more base tools here:
+10 -10
View File
@@ -1,42 +1,42 @@
#!/bin/bash
# test_tool_shell.sh - Fully validates the run_shell tool
# Mock response: ask for run_shell
tool_call run_shell '{"command":"echo Hello from shell","timeout_seconds":5}' >> mock_responses.txt
# Mock response: ask for run_dangerous_shell
tool_call run_dangerous_shell '{"command":"echo Hello from shell","timeout_seconds":5}' >> mock_responses.txt
be_done 'Shell done' >> mock_responses.txt
OUTPUT=$("${DRHARNESS}" -F chat.json -n -m "Run shell command" -t run_shell -d < "mock_responses.txt" 2>/dev/null)
OUTPUT=$("${DRHARNESS}" -F chat.json -n -m "Run shell command" -t run_dangerous_shell -d < "mock_responses.txt" 2>/dev/null)
if [ "${OUTPUT}" != "Shell done" ]; then
echo "FAIL: run_shell tool - expected 'Shell done', got '${OUTPUT}'"
echo "FAIL: run_dangerous_shell tool - expected 'Shell done', got '${OUTPUT}'"
exit 1
fi
RESULT=$(dr_result chat.json 2)
if ! echo "$RESULT" | grep -q '^Hello from shell$' ; then
echo "FAIL: run_shell tool - expected 'Hello from shell' in output"
echo "FAIL: run_dangerous_shell tool - expected 'Hello from shell' in output"
echo "Got: $RESULT"
exit 1
fi
# Test error: invalid command (exit code != 0)
rm -f chat.json
tool_call run_shell '{"command":"exit 42"}' >> mock_responses2.txt
tool_call run_dangerous_shell '{"command":"exit 42"}' >> mock_responses2.txt
be_done 'Error handled' >> mock_responses2.txt
"${DRHARNESS}" -F chat.json -n -m "Run invalid shell" -t run_shell -d < "mock_responses2.txt" >/dev/null 2>&1
"${DRHARNESS}" -F chat.json -n -m "Run invalid shell" -t run_dangerous_shell -d < "mock_responses2.txt" >/dev/null 2>&1
TOOL_RESULT=$(dr_result chat.json 2)
if ! echo "${TOOL_RESULT}" | grep -q "\[exit 42\]"; then
echo "FAIL: run_shell tool - expected exit code in output"
echo "FAIL: run_dangerous_shell tool - expected exit code in output"
echo "Got: ${TOOL_RESULT}"
exit 1
fi
tool_call run_shell '{"command":"cat /dev/urandom > /dev/null","timeout_seconds":1}' >> mock_responses3.txt
tool_call run_dangerous_shell '{"command":"cat /dev/urandom > /dev/null","timeout_seconds":1}' >> mock_responses3.txt
be_done 'done' >> mock_responses3.txt
# If we hit the timeout of the timeout utility then we fail
timeout 5s "${DRHARNESS}" -F chat3.json -n -m "This is a test" -t run_shell -d < "mock_responses3.txt"
timeout 5s "${DRHARNESS}" -F chat3.json -n -m "This is a test" -t run_dangerous_shell -d < "mock_responses3.txt"
echo "PASS: tool_shell"
+15 -3
View File
@@ -2,6 +2,7 @@
#include "json_types.hpp"
#include <array>
#include <cstdio>
#include <iostream>
#include <stdexcept>
#include <string>
#include <sys/wait.h>
@@ -10,9 +11,11 @@
#include <signal.h>
#include <cstring>
Tool make_shell_tool() {
Tool make_shell_tool(bool safe) {
std::string tool_name = safe ? "run_shell" : "run_dangerous_shell";
return Tool{
.name = "run_shell",
.name = tool_name,
.description = "Run a shell command and return its stdout and stderr. "
"Use for inspecting files, running programs, or querying the system.",
.parameters_schema = {
@@ -32,9 +35,18 @@ Tool make_shell_tool() {
.convert_params = [](const nlohmann::json& args) -> std::any {
return ShellToolParams(args.get<ShellToolParams>());
},
.execute = [](const std::any& args) -> std::string {
.execute = [safe](const std::any& args) -> std::string {
auto params = std::any_cast<ShellToolParams>(args);
if (safe) {
std::cerr << "Approve command: " << params.command << " ? [y/N]: ";
char response = 0;
std::cin.get(response);
if (response != 'y' && response != 'Y') {
return "Execution cancelled by user.";
}
}
// 1. Create a pipe for stdout
int pipefd[2];
if (pipe(pipefd) < 0) {
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once
#include "tool.hpp"
Tool make_shell_tool();
Tool make_shell_tool(bool safe);