Files
MosisService/SANDBOX_MILESTONE_3.md

358 lines
8.6 KiB
Markdown

# 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 <string>
#include <vector>
#include <mutex>
#include <chrono>
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<AuditEntry> GetEntries(size_t count = 100) const;
std::vector<AuditEntry> GetEntriesForApp(const std::string& app_id,
size_t count = 100) const;
std::vector<AuditEntry> 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<AuditEntry> 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 <string>
#include <unordered_map>
#include <mutex>
#include <chrono>
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<std::string, RateLimitConfig> m_configs;
std::unordered_map<std::string, Bucket> 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