glove/tests/run/glove_run_arg_rule_test.cpp

117 lines
3.8 KiB
C++

// F1.1 acceptance: drives the freshly-built `glove` binary with both a
// passing and a denying `--allow-arg` rule against yams. Verifies the
// argument-level policy is wired all the way from CLI flag through the
// jsonpath_engine through the kernel into the contained agent.
#include <spawn.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
extern char** environ;
// LeakSanitizer cannot inspect the contained fixture after Glove installs its Linux sandbox.
// Keep LSan on the parent while the child retains ASan/UBSan with leak detection disabled.
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
# define GLOVE_TEST_ADDRESS_SANITIZER 1
# endif
#endif
#if defined(__SANITIZE_ADDRESS__) && !defined(GLOVE_TEST_ADDRESS_SANITIZER)
# define GLOVE_TEST_ADDRESS_SANITIZER 1
#endif
#ifndef GLOVE_BIN
# error "GLOVE_BIN must be defined"
#endif
#ifndef GLOVE_SYNTHETIC_AGENT_BIN
# error "GLOVE_SYNTHETIC_AGENT_BIN must be defined"
#endif
#ifndef GLOVE_YAMS_BIN
# error "GLOVE_YAMS_BIN must be defined"
#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(const std::string& allow_arg) -> int {
std::vector<std::string> argv_owned{
GLOVE_BIN,
"run",
#if defined(GLOVE_TEST_ADDRESS_SANITIZER)
"--read",
GLOVE_SYNTHETIC_AGENT_BIN,
#endif
"--upstream",
std::string{"yams="} + GLOVE_YAMS_BIN + ",serve,--quiet",
"--allow",
"yams.mcp.echo",
"--allow-arg",
allow_arg,
"--",
#if defined(GLOVE_TEST_ADDRESS_SANITIZER)
"/usr/bin/env",
"ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:detect_leaks=0",
"UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1",
#endif
GLOVE_SYNTHETIC_AGENT_BIN,
"--mode=client",
};
std::vector<char*> argv;
argv.reserve(argv_owned.size() + 1);
for (auto& s : argv_owned) {
argv.push_back(s.data());
}
argv.push_back(nullptr);
::pid_t pid = -1;
if (::posix_spawn(&pid, GLOVE_BIN, nullptr, nullptr, argv.data(), environ) != 0) {
return -1;
}
int status = 0;
if (::waitpid(pid, &status, 0) < 0) {
return -1;
}
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}
auto run() -> int {
// Passing prefix: synthetic agent sends text="glove-says-hi", rule says
// it must start with "glove-says-". Allowed; agent exits 0.
{
const int code = run_glove("yams.mcp.echo:text=glove-says-");
if (code != 0) {
std::fprintf(stderr, "expected allow-path exit 0, got %d\n", code);
return 1;
}
}
// Denying prefix: rule requires text to start with "does-not-match-",
// which the agent's call ("glove-says-hi") fails. Policy denies. The
// synthetic agent in client mode reads the denied response, finds no
// "glove-says-hi" in the content, exits 15.
{
const int code = run_glove("yams.mcp.echo:text=does-not-match-");
if (code != 15) {
std::fprintf(stderr, "expected deny-path exit 15, got %d\n", code);
return 1;
}
}
return 0;
}
} // namespace
auto main() -> int {
return run();
}