move docs to docs/ folder, merge architecture files, update references
This commit is contained in:
357
docs/SANDBOX_MILESTONE_3.md
Normal file
357
docs/SANDBOX_MILESTONE_3.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user