// Direct-agent defaults must not silently expose the caller's current // directory, place glove-managed state in the selected workspace, or let an // agent tamper with its own audit trail. #include #include #include #include #include #include #include #ifndef GLOVE_BIN # error "GLOVE_BIN must point at the glove executable" #endif 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_glove(std::vector argv_owned) -> int { std::vector argv; argv.reserve(argv_owned.size() + 1); for (auto& value : argv_owned) { argv.push_back(value.data()); } argv.push_back(nullptr); const ::pid_t child = ::fork(); if (child < 0) { return -1; } if (child == 0) { ::execv(GLOVE_BIN, argv.data()); std::_Exit(127); } int status = 0; if (::waitpid(child, &status, 0) != child) { return -1; } return WIFEXITED(status) ? WEXITSTATUS(status) : -1; } auto run() -> int { std::error_code ec; const auto base = std::filesystem::temp_directory_path() / ("glove_perimeter_" + std::to_string(::getpid())); const auto workspace = base / "workspace"; const auto marker = std::filesystem::current_path() / ("glove-caller-secret-" + std::to_string(::getpid())); std::filesystem::create_directories(workspace, ec); REQUIRE(!ec); { std::ofstream out{marker}; out << "not for the agent\n"; REQUIRE(out.good()); } // With no --workspace or --read, even an absolute path passed in argv is // not readable. The command exits 42 if the boundary is too broad. REQUIRE( run_glove( {GLOVE_BIN, "exec", "--", "/bin/sh", "-c", "test ! -r \"$1\" || exit 42", "glove-test", marker.string()} ) == 0 ); // HOME/TMPDIR are private runtime state, not hidden mutations inside the // user's project tree. REQUIRE( run_glove( {GLOVE_BIN, "exec", "--workspace", workspace.string(), "--", "/bin/sh", "-c", "case \"$HOME\" in \"$1\"/*) exit 42;; esac", "glove-test", workspace.string()} ) == 0 ); REQUIRE(!std::filesystem::exists(workspace / ".glove-home")); // An append-only audit destination cannot sit below any path granted to // the agent, even if the operator names it explicitly. const auto exposed_audit = workspace / "audit.jsonl"; REQUIRE( run_glove( {GLOVE_BIN, "exec", "--workspace", workspace.string(), "--audit-log", exposed_audit.string(), "--", "/usr/bin/true"} ) == 1 ); REQUIRE(!std::filesystem::exists(exposed_audit)); std::filesystem::remove(marker, ec); std::filesystem::remove_all(base, ec); return 0; } } // namespace auto main() -> int { return run(); }