implement Milestone 9: Network HTTP with SSRF prevention
This commit is contained in:
@@ -21,6 +21,8 @@ add_library(mosis-sandbox STATIC
|
||||
../src/main/cpp/sandbox/crypto_api.cpp
|
||||
../src/main/cpp/sandbox/virtual_fs.cpp
|
||||
../src/main/cpp/sandbox/database_manager.cpp
|
||||
../src/main/cpp/sandbox/http_validator.cpp
|
||||
../src/main/cpp/sandbox/network_manager.cpp
|
||||
)
|
||||
target_include_directories(mosis-sandbox PUBLIC
|
||||
../src/main/cpp/sandbox
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "crypto_api.h"
|
||||
#include "virtual_fs.h"
|
||||
#include "database_manager.h"
|
||||
#include "http_validator.h"
|
||||
#include "network_manager.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@@ -1469,6 +1471,202 @@ bool Test_DatabaseLastInsertAndChanges(std::string& error_msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MILESTONE 9: NETWORK HTTP TESTS
|
||||
//=============================================================================
|
||||
|
||||
bool Test_NetworkBlocksPrivateIP(std::string& error_msg) {
|
||||
mosis::NetworkManager manager("test.app");
|
||||
manager.ClearDomainRestrictions();
|
||||
|
||||
std::string err;
|
||||
|
||||
// All these should be blocked
|
||||
std::vector<std::string> private_urls = {
|
||||
"https://127.0.0.1/api",
|
||||
"https://10.0.0.1/api",
|
||||
"https://192.168.1.1/api",
|
||||
"https://172.16.0.1/api",
|
||||
"https://169.254.169.254/latest/meta-data/",
|
||||
"https://localhost/api",
|
||||
"https://0.0.0.0/api"
|
||||
};
|
||||
|
||||
for (const auto& url : private_urls) {
|
||||
mosis::HttpRequest req;
|
||||
req.url = url;
|
||||
auto response = manager.Request(req, err);
|
||||
if (response.status_code != 0 || err.empty()) {
|
||||
error_msg = "Expected " + url + " to be blocked, but it wasn't";
|
||||
return false;
|
||||
}
|
||||
err.clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkBlocksPlainHttp(std::string& error_msg) {
|
||||
mosis::NetworkManager manager("test.app");
|
||||
manager.ClearDomainRestrictions();
|
||||
|
||||
std::string err;
|
||||
mosis::HttpRequest req;
|
||||
req.url = "http://example.com/api"; // No HTTPS
|
||||
|
||||
auto response = manager.Request(req, err);
|
||||
EXPECT_TRUE(response.status_code == 0);
|
||||
EXPECT_TRUE(err.find("HTTPS") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkRequiresHttps(std::string& error_msg) {
|
||||
mosis::HttpValidator validator;
|
||||
std::string err;
|
||||
|
||||
// HTTPS should validate
|
||||
auto parsed = validator.Validate("https://example.com/api", err);
|
||||
EXPECT_TRUE(parsed.has_value());
|
||||
EXPECT_TRUE(parsed->scheme == "https");
|
||||
|
||||
// HTTP should fail validation
|
||||
err.clear();
|
||||
parsed = validator.Validate("http://example.com/api", err);
|
||||
EXPECT_FALSE(parsed.has_value());
|
||||
EXPECT_TRUE(err.find("HTTPS") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkEnforcesDomainWhitelist(std::string& error_msg) {
|
||||
mosis::NetworkManager manager("test.app");
|
||||
|
||||
// Set allowed domains
|
||||
manager.SetAllowedDomains({"api.example.com", "cdn.example.com"});
|
||||
|
||||
std::string err;
|
||||
|
||||
// Allowed domain should validate
|
||||
auto parsed = manager.GetValidator().Validate("https://api.example.com/data", err);
|
||||
EXPECT_TRUE(parsed.has_value());
|
||||
|
||||
// Disallowed domain should fail
|
||||
err.clear();
|
||||
parsed = manager.GetValidator().Validate("https://evil.com/steal", err);
|
||||
EXPECT_FALSE(parsed.has_value());
|
||||
EXPECT_TRUE(err.find("allowed") != std::string::npos ||
|
||||
err.find("whitelist") != std::string::npos ||
|
||||
err.find("not in allowed") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkUrlParsing(std::string& error_msg) {
|
||||
mosis::HttpValidator validator;
|
||||
std::string err;
|
||||
|
||||
// Full URL with port
|
||||
auto parsed = validator.Validate("https://api.example.com:8443/path/to/resource?key=value", err);
|
||||
EXPECT_TRUE(parsed.has_value());
|
||||
EXPECT_TRUE(parsed->scheme == "https");
|
||||
EXPECT_TRUE(parsed->host == "api.example.com");
|
||||
EXPECT_TRUE(parsed->port == 8443);
|
||||
EXPECT_TRUE(parsed->path == "/path/to/resource");
|
||||
EXPECT_TRUE(parsed->query == "?key=value");
|
||||
|
||||
// Default port
|
||||
err.clear();
|
||||
parsed = validator.Validate("https://example.com/api", err);
|
||||
EXPECT_TRUE(parsed.has_value());
|
||||
EXPECT_TRUE(parsed->port == 443);
|
||||
|
||||
// IP address
|
||||
err.clear();
|
||||
parsed = validator.Validate("https://192.0.2.1/api", err); // TEST-NET-1, documentation IP
|
||||
EXPECT_TRUE(parsed.has_value());
|
||||
EXPECT_TRUE(parsed->is_ip_address);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkBlocksMetadata(std::string& error_msg) {
|
||||
mosis::NetworkManager manager("test.app");
|
||||
manager.ClearDomainRestrictions();
|
||||
|
||||
std::string err;
|
||||
|
||||
// AWS metadata
|
||||
mosis::HttpRequest req;
|
||||
req.url = "https://169.254.169.254/latest/meta-data/";
|
||||
auto response = manager.Request(req, err);
|
||||
EXPECT_TRUE(response.status_code == 0);
|
||||
|
||||
// GCP metadata hostname
|
||||
err.clear();
|
||||
req.url = "https://metadata.google.internal/computeMetadata/v1/";
|
||||
response = manager.Request(req, err);
|
||||
EXPECT_TRUE(response.status_code == 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkRequestLimits(std::string& error_msg) {
|
||||
mosis::NetworkLimits limits;
|
||||
limits.max_request_body = 1024; // 1 KB for testing
|
||||
|
||||
mosis::NetworkManager manager("test.app", limits);
|
||||
manager.ClearDomainRestrictions();
|
||||
|
||||
std::string err;
|
||||
mosis::HttpRequest req;
|
||||
req.url = "https://example.com/api";
|
||||
req.method = "POST";
|
||||
req.body = std::string(2048, 'X'); // 2 KB - exceeds limit
|
||||
|
||||
auto response = manager.Request(req, err);
|
||||
EXPECT_TRUE(response.status_code == 0);
|
||||
EXPECT_TRUE(err.find("size") != std::string::npos ||
|
||||
err.find("limit") != std::string::npos ||
|
||||
err.find("large") != std::string::npos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Test_NetworkLuaIntegration(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
mosis::NetworkManager manager("test.app");
|
||||
manager.ClearDomainRestrictions();
|
||||
mosis::RegisterNetworkAPI(sandbox.GetState(), &manager);
|
||||
|
||||
std::string script = R"lua(
|
||||
-- Test that network global exists
|
||||
if not network then
|
||||
error("network global not found")
|
||||
end
|
||||
if not network.request then
|
||||
error("network.request not found")
|
||||
end
|
||||
|
||||
-- Test validation rejection (private IP)
|
||||
local response, err = network.request({
|
||||
url = "https://127.0.0.1/api"
|
||||
})
|
||||
if response then
|
||||
error("expected private IP to be blocked")
|
||||
end
|
||||
)lua";
|
||||
|
||||
bool ok = sandbox.LoadString(script, "network_test");
|
||||
if (!ok) {
|
||||
error_msg = "Lua test failed: " + sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MAIN
|
||||
//=============================================================================
|
||||
@@ -1591,6 +1789,16 @@ int main(int argc, char* argv[]) {
|
||||
harness.AddTest("DatabaseInvalidNames", Test_DatabaseInvalidNames);
|
||||
harness.AddTest("DatabaseLastInsertAndChanges", Test_DatabaseLastInsertAndChanges);
|
||||
|
||||
// Milestone 9: Network HTTP
|
||||
harness.AddTest("NetworkBlocksPrivateIP", Test_NetworkBlocksPrivateIP);
|
||||
harness.AddTest("NetworkBlocksPlainHttp", Test_NetworkBlocksPlainHttp);
|
||||
harness.AddTest("NetworkRequiresHttps", Test_NetworkRequiresHttps);
|
||||
harness.AddTest("NetworkEnforcesDomainWhitelist", Test_NetworkEnforcesDomainWhitelist);
|
||||
harness.AddTest("NetworkUrlParsing", Test_NetworkUrlParsing);
|
||||
harness.AddTest("NetworkBlocksMetadata", Test_NetworkBlocksMetadata);
|
||||
harness.AddTest("NetworkRequestLimits", Test_NetworkRequestLimits);
|
||||
harness.AddTest("NetworkLuaIntegration", Test_NetworkLuaIntegration);
|
||||
|
||||
// Run tests
|
||||
auto results = harness.Run(filter);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user