8.6 KiB
8.6 KiB
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
- AuditLog class - Security event logging with ring buffer
- RateLimiter class - Token bucket rate limiting
- 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
// 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
// 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)
// 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
// 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
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
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
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
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
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
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:
Test_AuditLogBasic- Logging and retrieval workTest_AuditLogRingBuffer- Ring buffer wraps correctlyTest_AuditLogThreadSafe- Thread safety verifiedTest_RateLimiterBasic- Basic rate limiting worksTest_RateLimiterExhaustion- Tokens exhaust correctlyTest_RateLimiterRefill- Tokens refill over timeTest_RateLimiterAppIsolation- Apps have separate bucketsTest_RateLimiterReset- App reset clears bucketsTest_RateLimiterNoConfig- Unconfigured ops allowed
Next Steps
After Milestone 3 passes:
- Integrate AuditLog into LuaSandbox and PermissionGate
- Integrate RateLimiter into API implementations
- Milestone 4: Safe Path & Require