# Milestone 9: Network - HTTP **Status**: Complete **Goal**: Secure HTTP requests with domain filtering and SSRF prevention. --- ## Overview This milestone implements secure HTTP networking for Lua apps: - HTTPS required (no plain HTTP) - SSRF prevention (block private IPs, localhost, metadata endpoints) - Domain whitelist from app manifest - Request/response size limits - Concurrent request limits ### Key Deliverables 1. **HttpValidator class** - URL parsing, domain validation, IP blocking 2. **NetworkManager class** - HTTP client with limits 3. **Lua network API** - `network.request()` function 4. **Desktop mock server** - For testing without network --- ## File Structure ``` src/main/cpp/sandbox/ ├── http_validator.h # NEW - URL validation ├── http_validator.cpp # NEW - SSRF prevention ├── network_manager.h # NEW - HTTP client wrapper └── network_manager.cpp # NEW - Request execution ``` --- ## Implementation Details ### 1. URL Validation Rules ``` Allowed: https://api.example.com/path https://192.0.2.1/api (public IP) Blocked: http://example.com/... # No plain HTTP https://127.0.0.1/... # Localhost https://localhost/... # Localhost name https://10.0.0.1/... # Private IP (10.x.x.x) https://192.168.1.1/... # Private IP (192.168.x.x) https://172.16.0.1/... # Private IP (172.16-31.x.x) https://169.254.169.254/... # AWS metadata https://[::1]/... # IPv6 localhost https://[fc00::1]/... # IPv6 private https://0.0.0.0/... # All interfaces file:///etc/passwd # File scheme ftp://ftp.example.com/... # Non-HTTP schemes ``` ### 2. HttpValidator Class ```cpp // http_validator.h #pragma once #include #include #include namespace mosis { struct ParsedUrl { std::string scheme; // "https" std::string host; // "api.example.com" or "192.0.2.1" uint16_t port; // 443 std::string path; // "/api/data" std::string query; // "?key=value" bool is_ip_address; // true if host is IP }; class HttpValidator { public: HttpValidator(); // Set allowed domains (from app manifest) void SetAllowedDomains(const std::vector& domains); // Clear domain restrictions (for testing) void ClearDomainRestrictions(); // Validate URL // Returns parsed URL on success, sets error on failure std::optional Validate(const std::string& url, std::string& error); private: std::vector m_allowed_domains; bool m_domain_restrictions_enabled; bool IsPrivateIP(const std::string& ip); bool IsLocalhostIP(const std::string& ip); bool IsMetadataIP(const std::string& ip); bool IsDomainAllowed(const std::string& host); std::optional ParseUrl(const std::string& url); }; } // namespace mosis ``` ### 3. NetworkManager Class ```cpp // network_manager.h #pragma once #include #include #include #include #include #include #include "http_validator.h" struct lua_State; namespace mosis { struct HttpRequest { std::string url; std::string method = "GET"; std::map headers; std::string body; int timeout_ms = 30000; }; struct HttpResponse { int status_code = 0; std::map headers; std::string body; std::string error; }; struct NetworkLimits { size_t max_request_body = 10 * 1024 * 1024; // 10 MB size_t max_response_body = 50 * 1024 * 1024; // 50 MB int max_timeout_ms = 60000; // 60 seconds int max_concurrent_requests = 6; int default_timeout_ms = 30000; }; class NetworkManager { public: NetworkManager(const std::string& app_id, const NetworkLimits& limits = NetworkLimits{}); ~NetworkManager(); // Configure domain restrictions void SetAllowedDomains(const std::vector& domains); void ClearDomainRestrictions(); // Synchronous request (for testing) HttpResponse Request(const HttpRequest& request, std::string& error); // Stats int GetActiveRequestCount() const; // Get validator for testing HttpValidator& GetValidator() { return m_validator; } private: std::string m_app_id; NetworkLimits m_limits; HttpValidator m_validator; std::atomic m_active_requests{0}; std::mutex m_mutex; HttpResponse ExecuteRequest(const ParsedUrl& parsed, const HttpRequest& request); }; // Register network.* APIs as globals void RegisterNetworkAPI(lua_State* L, NetworkManager* manager); } // namespace mosis ``` ### 4. Private IP Ranges (SSRF Prevention) | Range | Description | |-------|-------------| | `127.0.0.0/8` | Loopback | | `10.0.0.0/8` | Private Class A | | `172.16.0.0/12` | Private Class B | | `192.168.0.0/16` | Private Class C | | `169.254.0.0/16` | Link-local | | `169.254.169.254` | Cloud metadata | | `0.0.0.0` | All interfaces | | `::1` | IPv6 loopback | | `fc00::/7` | IPv6 private | | `fe80::/10` | IPv6 link-local | ### 5. Lua API ```lua -- Simple GET request local response, err = network.request({ url = "https://api.example.com/data" }) if not response then print("Error: " .. err) return end print("Status:", response.status) print("Body:", response.body) -- POST with headers and body local response = network.request({ url = "https://api.example.com/submit", method = "POST", headers = { ["Content-Type"] = "application/json", ["Authorization"] = "Bearer token123" }, body = '{"key": "value"}', timeout = 30000 -- 30 seconds }) -- Response structure -- response.status - HTTP status code (200, 404, etc.) -- response.headers - Response headers table -- response.body - Response body string -- response.error - Error message (if request failed) ``` ### 6. Desktop Implementation For desktop testing, we use cpp-httplib (header-only HTTP client). On Android, we would use HttpURLConnection through JNI, but for the sandbox tests, we implement a mock HTTP server that returns predefined responses. ### 7. Mock Server for Testing ```cpp // Test helper - mock HTTP responses class MockHttpServer { public: void AddResponse(const std::string& url, const HttpResponse& response); std::optional GetResponse(const std::string& url); void Clear(); private: std::map m_responses; }; ``` --- ## Test Cases ### Test 1: Block Private IPs ```cpp bool Test_NetworkBlocksPrivateIP(std::string& error_msg) { mosis::NetworkManager manager("test.app"); manager.ClearDomainRestrictions(); std::string err; // All these should be blocked std::vector 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); EXPECT_TRUE(response.status_code == 0); // Request should fail EXPECT_TRUE(err.find("blocked") != std::string::npos || err.find("private") != std::string::npos || err.find("localhost") != std::string::npos); } return true; } ``` ### Test 2: Block Plain HTTP ```cpp 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; } ``` ### Test 3: Require HTTPS ```cpp bool Test_NetworkRequiresHttps(std::string& error_msg) { mosis::NetworkManager manager("test.app"); manager.ClearDomainRestrictions(); // HTTPS URL with mock validator should parse successfully std::string err; auto parsed = manager.GetValidator().Validate("https://example.com/api", err); EXPECT_TRUE(parsed.has_value()); EXPECT_TRUE(parsed->scheme == "https"); // HTTP should fail validation parsed = manager.GetValidator().Validate("http://example.com/api", err); EXPECT_FALSE(parsed.has_value()); return true; } ``` ### Test 4: Domain Whitelist ```cpp 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 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); return true; } ``` ### Test 5: URL Parsing ```cpp bool Test_NetworkUrlParsing(std::string& error_msg) { mosis::HttpValidator validator; std::string err; // Full URL 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 parsed = validator.Validate("https://example.com/api", err); EXPECT_TRUE(parsed.has_value()); EXPECT_TRUE(parsed->port == 443); return true; } ``` ### Test 6: Block Metadata Endpoints ```cpp 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 req.url = "https://metadata.google.internal/computeMetadata/v1/"; response = manager.Request(req, err); EXPECT_TRUE(response.status_code == 0); return true; } ``` ### Test 7: Request Limits ```cpp 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; } ``` ### Test 8: Lua Integration ```cpp 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; } ``` --- ## Acceptance Criteria All tests must pass: - [x] `Test_NetworkBlocksPrivateIP` - Private IPs blocked - [x] `Test_NetworkBlocksPlainHttp` - Plain HTTP rejected - [x] `Test_NetworkRequiresHttps` - HTTPS required - [x] `Test_NetworkEnforcesDomainWhitelist` - Domain restrictions work - [x] `Test_NetworkUrlParsing` - URL parsing correct - [x] `Test_NetworkBlocksMetadata` - Cloud metadata blocked - [x] `Test_NetworkRequestLimits` - Size limits enforced - [x] `Test_NetworkLuaIntegration` - Lua API works --- ## Dependencies - Milestone 1 (LuaSandbox) - Milestone 2 (PermissionGate) - Milestone 3 (RateLimiter, AuditLog) --- ## Notes ### Desktop vs Android Implementation For the sandbox-test project (desktop), we focus on URL validation and request structure validation. Actual network requests would go through cpp-httplib or a mock server. On Android, the real implementation would: 1. Call through JNI to Java HttpURLConnection 2. Use Android's network security config 3. Integrate with the permission system ### Security Considerations 1. **DNS Rebinding**: In production, re-validate IP after DNS resolution 2. **Redirects**: Follow redirects only to allowed domains, max 5 redirects 3. **Content-Type**: Validate response content-type matches expected 4. **Timeouts**: Both connection and read timeouts --- ## Next Steps After Milestone 9 passes: 1. Milestone 10: Network - WebSocket