# Milestone 3: Audit Logging & Rate Limiting **Status**: Complete ✓ **Goal**: Track security events and prevent API abuse. --- ## Overview This milestone adds security event logging and rate limiting to prevent abuse. The audit log tracks permission checks, sandbox violations, and resource usage. The rate limiter uses a token bucket algorithm to limit API call frequency. ### Key Deliverables 1. **AuditLog class** - Security event logging with ring buffer 2. **RateLimiter class** - Token bucket rate limiting 3. **Thread safety** - Concurrent access support --- ## File Structure ``` src/main/cpp/sandbox/ ├── lua_sandbox.h # (existing) ├── lua_sandbox.cpp # (existing) ├── permission_gate.h # (existing) ├── permission_gate.cpp # (existing) ├── audit_log.h # NEW - Audit logging ├── audit_log.cpp # NEW - Implementation ├── rate_limiter.h # NEW - Rate limiting └── rate_limiter.cpp # NEW - Implementation ``` --- ## Implementation Details ### 1. AuditEvent Enum ```cpp // audit_log.h enum class AuditEvent { // Lifecycle AppStart, AppStop, // Permissions PermissionCheck, PermissionGranted, PermissionDenied, // Network NetworkRequest, NetworkBlocked, // Storage FileAccess, FileBlocked, DatabaseAccess, // Hardware CameraAccess, MicrophoneAccess, LocationAccess, // Security SandboxViolation, ResourceLimitHit, RateLimitHit, // Other Custom }; ``` ### 2. AuditLog Class ```cpp // audit_log.h #pragma once #include #include #include #include namespace mosis { enum class AuditEvent { /* ... */ }; struct AuditEntry { std::chrono::system_clock::time_point timestamp; AuditEvent event; std::string app_id; std::string details; bool success; }; class AuditLog { public: explicit AuditLog(size_t max_entries = 10000); // Log an event void Log(AuditEvent event, const std::string& app_id, const std::string& details = "", bool success = true); // Query entries std::vector GetEntries(size_t count = 100) const; std::vector GetEntriesForApp(const std::string& app_id, size_t count = 100) const; std::vector GetEntriesByEvent(AuditEvent event, size_t count = 100) const; // Statistics size_t GetTotalEntries() const; size_t CountEvents(AuditEvent event, const std::string& app_id = "") const; // Clear void Clear(); private: mutable std::mutex m_mutex; std::vector m_entries; size_t m_max_entries; size_t m_write_index = 0; size_t m_total_logged = 0; }; // Global audit log (singleton pattern) AuditLog& GetAuditLog(); } // namespace mosis ``` ### 3. RateLimiter Class (Token Bucket) ```cpp // rate_limiter.h #pragma once #include #include #include #include namespace mosis { struct RateLimitConfig { double tokens_per_second; // Refill rate double max_tokens; // Bucket capacity }; class RateLimiter { public: // Default limits for common operations RateLimiter(); // Check if operation is allowed (consumes token if yes) bool Check(const std::string& app_id, const std::string& operation); // Check without consuming bool CanProceed(const std::string& app_id, const std::string& operation) const; // Configure limits for an operation void SetLimit(const std::string& operation, const RateLimitConfig& config); // Get current token count double GetTokens(const std::string& app_id, const std::string& operation) const; // Reset an app's tokens (e.g., on app restart) void ResetApp(const std::string& app_id); private: struct Bucket { double tokens; std::chrono::steady_clock::time_point last_refill; }; void Refill(Bucket& bucket, const RateLimitConfig& config) const; Bucket& GetBucket(const std::string& app_id, const std::string& operation); mutable std::mutex m_mutex; std::unordered_map m_configs; std::unordered_map m_buckets; // Key: app_id:operation }; // Global rate limiter RateLimiter& GetRateLimiter(); } // namespace mosis ``` ### 4. Default Rate Limits ```cpp // rate_limiter.cpp RateLimiter::RateLimiter() { // Network SetLimit("network.request", {10.0, 100.0}); // 10/sec, burst 100 SetLimit("network.websocket", {2.0, 10.0}); // 2/sec, burst 10 // Storage SetLimit("storage.read", {100.0, 500.0}); // 100/sec, burst 500 SetLimit("storage.write", {20.0, 100.0}); // 20/sec, burst 100 SetLimit("database.query", {50.0, 200.0}); // 50/sec, burst 200 // Hardware SetLimit("camera.capture", {30.0, 30.0}); // 30 fps max SetLimit("microphone.record", {1.0, 1.0}); // 1 session at a time SetLimit("location.request", {1.0, 5.0}); // 1/sec, burst 5 // Timers SetLimit("timer.create", {10.0, 100.0}); // 10/sec, burst 100 } ``` --- ## Test Cases ### Test 1: Audit Log Basic ```cpp bool Test_AuditLogBasic(std::string& error_msg) { mosis::AuditLog log(1000); log.Log(mosis::AuditEvent::AppStart, "test.app", "App started"); log.Log(mosis::AuditEvent::PermissionCheck, "test.app", "camera", true); log.Log(mosis::AuditEvent::PermissionDenied, "test.app", "microphone", false); auto entries = log.GetEntries(10); EXPECT_TRUE(entries.size() == 3); auto app_entries = log.GetEntriesForApp("test.app", 10); EXPECT_TRUE(app_entries.size() == 3); return true; } ``` ### Test 2: Audit Log Ring Buffer ```cpp bool Test_AuditLogRingBuffer(std::string& error_msg) { mosis::AuditLog log(100); // Small buffer // Log more than capacity for (int i = 0; i < 200; i++) { log.Log(mosis::AuditEvent::Custom, "test.app", std::to_string(i)); } // Should only have latest 100 auto entries = log.GetEntries(200); EXPECT_TRUE(entries.size() == 100); // Total logged should be 200 EXPECT_TRUE(log.GetTotalEntries() == 200); return true; } ``` ### Test 3: Rate Limiter Basic ```cpp bool Test_RateLimiterBasic(std::string& error_msg) { mosis::RateLimiter limiter; // Should succeed initially (has tokens) EXPECT_TRUE(limiter.Check("test.app", "network.request")); return true; } ``` ### Test 4: Rate Limiter Exhaustion ```cpp bool Test_RateLimiterExhaustion(std::string& error_msg) { mosis::RateLimiter limiter; limiter.SetLimit("test.op", {0.0, 5.0}); // 5 tokens, no refill // Use all tokens for (int i = 0; i < 5; i++) { EXPECT_TRUE(limiter.Check("test.app", "test.op")); } // Should be denied now EXPECT_FALSE(limiter.Check("test.app", "test.op")); return true; } ``` ### Test 5: Rate Limiter Refill ```cpp bool Test_RateLimiterRefill(std::string& error_msg) { mosis::RateLimiter limiter; limiter.SetLimit("test.op", {1000.0, 1.0}); // 1000/sec, max 1 // Use the token EXPECT_TRUE(limiter.Check("test.app", "test.op")); EXPECT_FALSE(limiter.Check("test.app", "test.op")); // Wait a bit for refill (1ms = 1 token at 1000/sec) std::this_thread::sleep_for(std::chrono::milliseconds(2)); // Should have token again EXPECT_TRUE(limiter.Check("test.app", "test.op")); return true; } ``` ### Test 6: App Isolation ```cpp bool Test_RateLimiterAppIsolation(std::string& error_msg) { mosis::RateLimiter limiter; limiter.SetLimit("test.op", {0.0, 1.0}); // 1 token, no refill // App 1 uses its token EXPECT_TRUE(limiter.Check("app1", "test.op")); EXPECT_FALSE(limiter.Check("app1", "test.op")); // App 2 should still have its token EXPECT_TRUE(limiter.Check("app2", "test.op")); return true; } ``` --- ## Acceptance Criteria All tests must pass: - [x] `Test_AuditLogBasic` - Logging and retrieval work - [x] `Test_AuditLogRingBuffer` - Ring buffer wraps correctly - [x] `Test_AuditLogThreadSafe` - Thread safety verified - [x] `Test_RateLimiterBasic` - Basic rate limiting works - [x] `Test_RateLimiterExhaustion` - Tokens exhaust correctly - [x] `Test_RateLimiterRefill` - Tokens refill over time - [x] `Test_RateLimiterAppIsolation` - Apps have separate buckets - [x] `Test_RateLimiterReset` - App reset clears buckets - [x] `Test_RateLimiterNoConfig` - Unconfigured ops allowed --- ## Next Steps After Milestone 3 passes: 1. Integrate AuditLog into LuaSandbox and PermissionGate 2. Integrate RateLimiter into API implementations 3. Milestone 4: Safe Path & Require