90 lines
3.8 KiB
C++
90 lines
3.8 KiB
C++
// Stage 3 (macOS): `glove exec` runs a real, self-driving agent contained with
|
|
// stdio inherited and no MCP kernel. We exercise the platform entry point
|
|
// exec_contained directly and assert the two containment guarantees that matter
|
|
// for an agent like pi: (1) HOME is redirected into the writable workspace so
|
|
// the agent's state writes land inside the sandbox; (2) a write outside the
|
|
// workspace is denied; (3) host credential-bearing environment variables are
|
|
// not inherited.
|
|
|
|
#include "glove/container/profile.hpp"
|
|
#include "glove/container/spawner.hpp"
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
auto run() -> int {
|
|
REQUIRE(::setenv("GLOVE_TEST_HOST_SECRET", "must-not-cross", 1) == 0);
|
|
std::error_code ec;
|
|
auto base =
|
|
std::filesystem::temp_directory_path() / ("glove_exec_test_" + std::to_string(::getpid()));
|
|
auto workspace = base / "ws";
|
|
std::filesystem::create_directories(workspace, ec);
|
|
REQUIRE(!ec);
|
|
// A path OUTSIDE the workspace that must be denied. It has to be somewhere
|
|
// the profile does NOT allow — the host $HOME, not /var/folders (which is a
|
|
// permitted scratch dir). The test cleans it up if it somehow appears.
|
|
const char* host_home = std::getenv("HOME");
|
|
REQUIRE(host_home != nullptr && *host_home != '\0');
|
|
std::filesystem::path forbidden =
|
|
std::filesystem::path{host_home} / (".glove_exec_forbidden_" + std::to_string(::getpid()));
|
|
|
|
glove::container::profile prof;
|
|
prof.filesystem.push_back({.path = workspace.string(), .writable = true});
|
|
// HOME is the scratch-home the runner would set; point it at the workspace.
|
|
prof.home_dir = workspace.string();
|
|
|
|
// The agent: record its $HOME (which glove should have pointed at the
|
|
// workspace), and attempt a write outside it.
|
|
std::string script = "printf '%s' \"$HOME\" > \"$HOME/home_marker\"; ";
|
|
script += "if env | grep '^GLOVE_TEST_HOST_SECRET=' >/dev/null; then ";
|
|
script += "printf inherited > \"$HOME/env_marker\"; else ";
|
|
script += "printf scrubbed > \"$HOME/env_marker\"; fi; ";
|
|
script += "touch '" + forbidden.string() + "' 2>/dev/null || true";
|
|
|
|
auto code = glove::container::exec_contained(prof, {"/bin/sh", "-c", script});
|
|
REQUIRE(code.has_value());
|
|
REQUIRE(*code == 0);
|
|
|
|
// (1) HOME was the workspace, and the agent wrote its marker there.
|
|
auto marker = workspace / "home_marker";
|
|
REQUIRE(std::filesystem::exists(marker));
|
|
std::ifstream in{marker};
|
|
std::string home_seen;
|
|
std::getline(in, home_seen);
|
|
std::fprintf(stderr, "agent $HOME = %s\n", home_seen.c_str());
|
|
REQUIRE(std::filesystem::equivalent(home_seen, workspace, ec));
|
|
REQUIRE(!ec);
|
|
|
|
std::ifstream env_in{workspace / "env_marker"};
|
|
std::string env_seen;
|
|
std::getline(env_in, env_seen);
|
|
REQUIRE(env_seen == "scrubbed");
|
|
|
|
// (2) the write outside the workspace was denied.
|
|
const bool escaped = std::filesystem::exists(forbidden);
|
|
std::filesystem::remove_all(base, ec);
|
|
REQUIRE(!escaped);
|
|
REQUIRE(::unsetenv("GLOVE_TEST_HOST_SECRET") == 0);
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main() -> int {
|
|
return run();
|
|
}
|