diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fea5845..c684d6a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -63,6 +63,17 @@ else() target_compile_definitions(mosis-core PUBLIC MOSIS_PLATFORM_LINUX) endif() +# OpenSSL for crypto on non-Windows platforms +if(NOT WIN32) + find_package(OpenSSL QUIET) + if(OpenSSL_FOUND) + target_link_libraries(mosis-core PRIVATE OpenSSL::Crypto) + target_compile_definitions(mosis-core PRIVATE MOSIS_HAS_OPENSSL) + else() + message(WARNING "OpenSSL not found - crypto functions will be stubs") + endif() +endif() + # Lua is required - parent project must provide it if(TARGET lua_static) target_link_libraries(mosis-core PUBLIC lua_static) diff --git a/core/src/sandbox/crypto_api.cpp b/core/src/sandbox/crypto_api.cpp index 25eb49b..25be2d7 100644 --- a/core/src/sandbox/crypto_api.cpp +++ b/core/src/sandbox/crypto_api.cpp @@ -9,6 +9,9 @@ #include #include #pragma comment(lib, "bcrypt.lib") +#elif defined(MOSIS_HAS_OPENSSL) +#include +#include #endif namespace mosis { @@ -162,9 +165,68 @@ std::string ComputeHMAC(HashAlgorithm algo, const std::string& key, const std::s return result; } +#elif defined(MOSIS_HAS_OPENSSL) + +//============================================================================= +// HASHING (OpenSSL) +//============================================================================= + +static std::string BytesToHex(const unsigned char* data, size_t len) { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (size_t i = 0; i < len; i++) { + oss << std::setw(2) << static_cast(data[i]); + } + return oss.str(); +} + +static const EVP_MD* GetOpenSSLAlgorithm(HashAlgorithm algo) { + switch (algo) { + case HashAlgorithm::SHA256: return EVP_sha256(); + case HashAlgorithm::SHA512: return EVP_sha512(); + case HashAlgorithm::SHA1: return EVP_sha1(); + case HashAlgorithm::MD5: return EVP_md5(); + default: return EVP_sha256(); + } +} + +std::string ComputeHash(HashAlgorithm algo, const std::string& data) { + const EVP_MD* md = GetOpenSSLAlgorithm(algo); + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len = 0; + + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + if (!ctx) return ""; + + if (EVP_DigestInit_ex(ctx, md, nullptr) != 1 || + EVP_DigestUpdate(ctx, data.data(), data.size()) != 1 || + EVP_DigestFinal_ex(ctx, hash, &hash_len) != 1) { + EVP_MD_CTX_free(ctx); + return ""; + } + + EVP_MD_CTX_free(ctx); + return BytesToHex(hash, hash_len); +} + +std::string ComputeHMAC(HashAlgorithm algo, const std::string& key, const std::string& data) { + const EVP_MD* md = GetOpenSSLAlgorithm(algo); + unsigned char hmac_result[EVP_MAX_MD_SIZE]; + unsigned int hmac_len = 0; + + unsigned char* result = HMAC(md, + key.data(), static_cast(key.size()), + reinterpret_cast(data.data()), + data.size(), + hmac_result, &hmac_len); + + if (!result) return ""; + return BytesToHex(hmac_result, hmac_len); +} + #else -// Stub implementations for non-Windows (would need OpenSSL or similar) +// Stub implementations when no crypto library is available std::string ComputeHash(HashAlgorithm algo, const std::string& data) { (void)algo; (void)data; diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt index 8f85134..25030fa 100644 --- a/src/main/cpp/CMakeLists.txt +++ b/src/main/cpp/CMakeLists.txt @@ -11,10 +11,11 @@ set(AIDL_EXE "${ANDROID_SDK}/build-tools/36.1.0/aidl.exe") # Find Lua from vcpkg find_package(Lua REQUIRED) -# Find nlohmann_json, minizip and sqlite3 for app management and sandbox +# Find nlohmann_json, minizip, sqlite3 and openssl for app management and sandbox find_package(nlohmann_json CONFIG REQUIRED) find_package(minizip CONFIG REQUIRED) find_package(unofficial-sqlite3 CONFIG REQUIRED) +find_package(OpenSSL REQUIRED) # Fetch RmlUi from GitHub with Lua bindings enabled include(FetchContent) @@ -31,12 +32,30 @@ set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE) set(RMLUI_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(rmlui) -#get_cmake_property(_variableNames VARIABLES) -#list(SORT _variableNames) -#foreach(_variableName ${_variableNames}) -# message(STATUS "${_variableName}=${${_variableName}}") -#endforeach() +#============================================================================== +# Mosis Core Library (shared sandbox APIs) +#============================================================================== +# Enable optional features for Android +set(MOSIS_ENABLE_DATABASE ON CACHE BOOL "" FORCE) +set(MOSIS_ENABLE_NETWORK ON CACHE BOOL "" FORCE) +# Add core library from project root +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../core ${CMAKE_CURRENT_BINARY_DIR}/mosis-core) + +# Configure core library for Android +target_compile_definitions(mosis-core PUBLIC MOSIS_PLATFORM_ANDROID) +target_compile_definitions(mosis-core PRIVATE MOSIS_HAS_OPENSSL) +target_include_directories(mosis-core PUBLIC ${LUA_INCLUDE_DIR}) +target_link_libraries(mosis-core PUBLIC + ${LUA_LIBRARIES} + nlohmann_json::nlohmann_json + unofficial::sqlite3::sqlite3 + OpenSSL::Crypto +) + +#============================================================================== +# AIDL Generated Files +#============================================================================== add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/com/omixlab/mosis/IMosisService.cpp" COMMAND ${AIDL_EXE} --lang=ndk --min_sdk_version=36 -o ${CMAKE_CURRENT_BINARY_DIR} -h ${CMAKE_CURRENT_BINARY_DIR} -I ..\\aidl ..\\aidl\\com\\omixlab\\mosis\\IMosisService.aidl @@ -53,36 +72,38 @@ add_custom_command( VERBATIM ) +#============================================================================== +# Mosis Service (Android) +#============================================================================== add_library(mosis-service SHARED + # Entry point and core mosis-service.cpp + kernel.cpp + + # AIDL generated ${CMAKE_CURRENT_BINARY_DIR}/com/omixlab/mosis/IMosisService.cpp ${CMAKE_CURRENT_BINARY_DIR}/com/omixlab/mosis/IMosisListener.cpp + + # Platform-specific utilities assets_manager.cpp shader.cpp external_texture.cpp quad.cpp egl_context.cpp render_target.cpp - logger.cpp - kernel.cpp + + # OpenGL ES glad/src/egl.c glad/src/gles2.c RmlUi_Renderer_GL3.cpp + + # App management (uses core + Android-specific features) apps/app_manager.cpp apps/app_api.cpp apps/update_service.cpp - sandbox/lua_sandbox.cpp - sandbox/permission_gate.cpp - sandbox/audit_log.cpp - sandbox/rate_limiter.cpp - sandbox/path_sandbox.cpp - sandbox/timer_manager.cpp - sandbox/json_api.cpp - sandbox/crypto_api.cpp - sandbox/virtual_fs.cpp - sandbox/database_manager.cpp - sandbox/http_validator.cpp - sandbox/network_manager.cpp + + # Android-specific sandbox components (hardware interfaces) + sandbox/sandbox_manager.cpp sandbox/websocket_manager.cpp sandbox/camera_interface.cpp sandbox/microphone_interface.cpp @@ -92,27 +113,41 @@ add_library(mosis-service SHARED sandbox/bluetooth_interface.cpp sandbox/contacts_interface.cpp sandbox/message_bus.cpp - sandbox/sandbox_manager.cpp ) + target_compile_definitions(mosis-service PUBLIC RMLUI_NUM_MSAA_SAMPLES=2 RMLUI_GL3_CUSTOM_LOADER= + MOSIS_PLATFORM_ANDROID ) + target_include_directories(mosis-service PUBLIC ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${BINDER_DIR} glad/include + # Core library headers + ${CMAKE_CURRENT_SOURCE_DIR}/../../../core/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../core/include/mosis/sandbox ) + target_link_libraries(mosis-service + # Core library (provides sandbox APIs) + mosis-core + # Android system libraries android log binder_ndk EGL GLESv2 nativewindow + # RmlUi rmlui rmlui_lua + # Dependencies nlohmann_json::nlohmann_json minizip::minizip unofficial::sqlite3::sqlite3 ${LUA_LIBRARIES} ) +#============================================================================== +# Mosis Test Library +#============================================================================== add_library(mosis-test SHARED ${CMAKE_CURRENT_BINARY_DIR}/com/omixlab/mosis/IMosisService.cpp ${CMAKE_CURRENT_BINARY_DIR}/com/omixlab/mosis/IMosisListener.cpp @@ -123,16 +158,20 @@ add_library(mosis-test SHARED quad.cpp egl_context.cpp render_target.cpp - logger.cpp glad/src/egl.c glad/src/gles2.c ) + target_include_directories(mosis-test PUBLIC ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${BINDER_DIR} glad/include + # Core library includes + ${CMAKE_CURRENT_SOURCE_DIR}/../../../core/include ) + target_link_libraries(mosis-test + mosis-core android log binder_ndk EGL GLESv2 nativewindow ) diff --git a/src/main/cpp/egl_context.cpp b/src/main/cpp/egl_context.cpp index 4d36c99..e132150 100644 --- a/src/main/cpp/egl_context.cpp +++ b/src/main/cpp/egl_context.cpp @@ -1,5 +1,5 @@ #include "egl_context.h" -#include "logger.h" +#include #include #include #include diff --git a/src/main/cpp/external_texture.cpp b/src/main/cpp/external_texture.cpp index 7117216..6cb7ff5 100644 --- a/src/main/cpp/external_texture.cpp +++ b/src/main/cpp/external_texture.cpp @@ -1,7 +1,7 @@ #include "external_texture.h" #include #include -#include "logger.h" +#include bool ExternalTexture::create(AHardwareBuffer *hardwareBuffer) { diff --git a/src/main/cpp/logger.cpp b/src/main/cpp/logger.cpp deleted file mode 100644 index 04052b6..0000000 --- a/src/main/cpp/logger.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "logger.h" -#include -#define LOG_TAG "MosisOS" -#include - -void Logger::Log(const std::string &message) -{ - ALOGI("%s", message.c_str()); -} diff --git a/src/main/cpp/logger.h b/src/main/cpp/logger.h deleted file mode 100644 index 458744a..0000000 --- a/src/main/cpp/logger.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include -#include - -class Logger -{ -public: - static void Log(const std::string& message); - - // Printf-style logging - static void LogF(const char* level, const char* fmt, ...) { - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - Log(std::string("[") + level + "] " + buffer); - } -}; - -// Undefine conflicting syslog macros if present -#ifdef LOG_DEBUG -#undef LOG_DEBUG -#endif -#ifdef LOG_INFO -#undef LOG_INFO -#endif -#ifdef LOG_WARN -#undef LOG_WARN -#endif -#ifdef LOG_ERROR -#undef LOG_ERROR -#endif - -// Logging macros for convenience (printf-style) -#define LOG_DEBUG(fmt, ...) Logger::LogF("DEBUG", fmt __VA_OPT__(,) __VA_ARGS__) -#define LOG_INFO(fmt, ...) Logger::LogF("INFO", fmt __VA_OPT__(,) __VA_ARGS__) -#define LOG_WARN(fmt, ...) Logger::LogF("WARN", fmt __VA_OPT__(,) __VA_ARGS__) -#define LOG_ERROR(fmt, ...) Logger::LogF("ERROR", fmt __VA_OPT__(,) __VA_ARGS__) diff --git a/src/main/cpp/mosis-service.cpp b/src/main/cpp/mosis-service.cpp index 01070f2..d069215 100644 --- a/src/main/cpp/mosis-service.cpp +++ b/src/main/cpp/mosis-service.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "logger.h" +#include #include "kernel.h" #include "assets_manager.h" #include diff --git a/src/main/cpp/mosis-test.cpp b/src/main/cpp/mosis-test.cpp index 117bc16..6a5fa2b 100644 --- a/src/main/cpp/mosis-test.cpp +++ b/src/main/cpp/mosis-test.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "logger.h" +#include #include "egl_context.h" #include "assets_manager.h" #include "shader.h" diff --git a/src/main/cpp/render_target.cpp b/src/main/cpp/render_target.cpp index e9545b0..71b1abd 100644 --- a/src/main/cpp/render_target.cpp +++ b/src/main/cpp/render_target.cpp @@ -1,7 +1,7 @@ #include "render_target.h" #include #include -#include "logger.h" +#include bool RenderTarget::create(uint32_t width, uint32_t height) { glGenFramebuffers(1, &m_framebuffer); diff --git a/src/main/cpp/sandbox/audit_log.cpp b/src/main/cpp/sandbox/audit_log.cpp deleted file mode 100644 index 0002311..0000000 --- a/src/main/cpp/sandbox/audit_log.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include "audit_log.h" - -#include - -namespace mosis { - -//============================================================================= -// CONSTRUCTOR -//============================================================================= - -AuditLog::AuditLog(size_t max_entries) - : m_max_entries(max_entries) -{ - m_entries.resize(max_entries); -} - -//============================================================================= -// LOGGING -//============================================================================= - -void AuditLog::Log(AuditEvent event, const std::string& app_id, - const std::string& details, bool success) { - std::lock_guard lock(m_mutex); - - AuditEntry entry{ - .timestamp = std::chrono::system_clock::now(), - .event = event, - .app_id = app_id, - .details = details, - .success = success - }; - - m_entries[m_write_index] = std::move(entry); - m_write_index = (m_write_index + 1) % m_max_entries; - m_total_logged++; - - if (m_total_logged > m_max_entries) { - m_wrapped = true; - } -} - -//============================================================================= -// QUERIES -//============================================================================= - -std::vector AuditLog::GetEntries(size_t count) const { - std::lock_guard lock(m_mutex); - - std::vector result; - size_t stored = GetStoredEntries(); - count = std::min(count, stored); - - result.reserve(count); - - // Read from most recent backwards - for (size_t i = 0; i < count; i++) { - size_t idx = (m_write_index + m_max_entries - 1 - i) % m_max_entries; - result.push_back(m_entries[idx]); - } - - return result; -} - -std::vector AuditLog::GetEntriesForApp(const std::string& app_id, - size_t count) const { - std::lock_guard lock(m_mutex); - - std::vector result; - result.reserve(count); - - size_t stored = GetStoredEntries(); - - for (size_t i = 0; i < stored && result.size() < count; i++) { - size_t idx = (m_write_index + m_max_entries - 1 - i) % m_max_entries; - if (m_entries[idx].app_id == app_id) { - result.push_back(m_entries[idx]); - } - } - - return result; -} - -std::vector AuditLog::GetEntriesByEvent(AuditEvent event, - size_t count) const { - std::lock_guard lock(m_mutex); - - std::vector result; - result.reserve(count); - - size_t stored = GetStoredEntries(); - - for (size_t i = 0; i < stored && result.size() < count; i++) { - size_t idx = (m_write_index + m_max_entries - 1 - i) % m_max_entries; - if (m_entries[idx].event == event) { - result.push_back(m_entries[idx]); - } - } - - return result; -} - -//============================================================================= -// STATISTICS -//============================================================================= - -size_t AuditLog::GetTotalEntries() const { - std::lock_guard lock(m_mutex); - return m_total_logged; -} - -size_t AuditLog::GetStoredEntries() const { - // Note: caller should hold lock - if (m_wrapped) { - return m_max_entries; - } - return m_write_index; -} - -size_t AuditLog::CountEvents(AuditEvent event, const std::string& app_id) const { - std::lock_guard lock(m_mutex); - - size_t count = 0; - size_t stored = GetStoredEntries(); - - for (size_t i = 0; i < stored; i++) { - const auto& entry = m_entries[i]; - if (entry.event == event) { - if (app_id.empty() || entry.app_id == app_id) { - count++; - } - } - } - - return count; -} - -//============================================================================= -// CLEAR -//============================================================================= - -void AuditLog::Clear() { - std::lock_guard lock(m_mutex); - m_write_index = 0; - m_total_logged = 0; - m_wrapped = false; - // Clear all entries - for (auto& entry : m_entries) { - entry = AuditEntry{}; - } -} - -//============================================================================= -// UTILITIES -//============================================================================= - -const char* AuditLog::EventToString(AuditEvent event) { - switch (event) { - case AuditEvent::AppStart: return "AppStart"; - case AuditEvent::AppStop: return "AppStop"; - case AuditEvent::PermissionCheck: return "PermissionCheck"; - case AuditEvent::PermissionGranted: return "PermissionGranted"; - case AuditEvent::PermissionDenied: return "PermissionDenied"; - case AuditEvent::NetworkRequest: return "NetworkRequest"; - case AuditEvent::NetworkBlocked: return "NetworkBlocked"; - case AuditEvent::FileAccess: return "FileAccess"; - case AuditEvent::FileBlocked: return "FileBlocked"; - case AuditEvent::DatabaseAccess: return "DatabaseAccess"; - case AuditEvent::CameraAccess: return "CameraAccess"; - case AuditEvent::MicrophoneAccess: return "MicrophoneAccess"; - case AuditEvent::LocationAccess: return "LocationAccess"; - case AuditEvent::SandboxViolation: return "SandboxViolation"; - case AuditEvent::ResourceLimitHit: return "ResourceLimitHit"; - case AuditEvent::RateLimitHit: return "RateLimitHit"; - case AuditEvent::Custom: return "Custom"; - default: return "Unknown"; - } -} - -//============================================================================= -// GLOBAL INSTANCE -//============================================================================= - -AuditLog& GetAuditLog() { - static AuditLog instance(10000); - return instance; -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/audit_log.h b/src/main/cpp/sandbox/audit_log.h deleted file mode 100644 index c42333a..0000000 --- a/src/main/cpp/sandbox/audit_log.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace mosis { - -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 -}; - -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 (returns most recent first) - 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 GetStoredEntries() const; - size_t CountEvents(AuditEvent event, const std::string& app_id = "") const; - - // Clear all entries - void Clear(); - - // Convert event to string for logging - static const char* EventToString(AuditEvent event); - -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; - bool m_wrapped = false; -}; - -// Global audit log (singleton) -AuditLog& GetAuditLog(); - -} // namespace mosis - -// Convenience alias -using AuditLog = mosis::AuditLog; -using AuditEvent = mosis::AuditEvent; -using AuditEntry = mosis::AuditEntry; diff --git a/src/main/cpp/sandbox/camera_interface.cpp b/src/main/cpp/sandbox/camera_interface.cpp index d3980cd..c53bd6b 100644 --- a/src/main/cpp/sandbox/camera_interface.cpp +++ b/src/main/cpp/sandbox/camera_interface.cpp @@ -1,5 +1,5 @@ #include "camera_interface.h" -#include "permission_gate.h" +#include #include namespace mosis { diff --git a/src/main/cpp/sandbox/contacts_interface.cpp b/src/main/cpp/sandbox/contacts_interface.cpp index f51c063..1be02cb 100644 --- a/src/main/cpp/sandbox/contacts_interface.cpp +++ b/src/main/cpp/sandbox/contacts_interface.cpp @@ -2,7 +2,7 @@ // Milestone 17: Contact access with granular read/write permissions #include "contacts_interface.h" -#include "lua_sandbox.h" +#include #include #include #include diff --git a/src/main/cpp/sandbox/crypto_api.cpp b/src/main/cpp/sandbox/crypto_api.cpp deleted file mode 100644 index 25eb49b..0000000 --- a/src/main/cpp/sandbox/crypto_api.cpp +++ /dev/null @@ -1,393 +0,0 @@ -#include "crypto_api.h" - -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#pragma comment(lib, "bcrypt.lib") -#endif - -namespace mosis { - -//============================================================================= -// SECURE RANDOM -//============================================================================= - -SecureRandom::SecureRandom() - : m_gen(m_rd()) { -} - -std::string SecureRandom::GetBytes(size_t count) { - std::lock_guard lock(m_mutex); - - std::string result(count, '\0'); - for (size_t i = 0; i < count; i++) { - result[i] = static_cast(m_gen() & 0xFF); - } - return result; -} - -int64_t SecureRandom::GetInt(int64_t min, int64_t max) { - std::lock_guard lock(m_mutex); - std::uniform_int_distribution dist(min, max); - return dist(m_gen); -} - -double SecureRandom::GetDouble() { - std::lock_guard lock(m_mutex); - std::uniform_real_distribution dist(0.0, 1.0); - return dist(m_gen); -} - -//============================================================================= -// HASHING (Windows BCrypt) -//============================================================================= - -#ifdef _WIN32 - -static std::string BytesToHex(const unsigned char* data, size_t len) { - std::ostringstream oss; - oss << std::hex << std::setfill('0'); - for (size_t i = 0; i < len; i++) { - oss << std::setw(2) << static_cast(data[i]); - } - return oss.str(); -} - -static LPCWSTR GetBCryptAlgorithm(HashAlgorithm algo) { - switch (algo) { - case HashAlgorithm::SHA256: return BCRYPT_SHA256_ALGORITHM; - case HashAlgorithm::SHA512: return BCRYPT_SHA512_ALGORITHM; - case HashAlgorithm::SHA1: return BCRYPT_SHA1_ALGORITHM; - case HashAlgorithm::MD5: return BCRYPT_MD5_ALGORITHM; - default: return BCRYPT_SHA256_ALGORITHM; - } -} - -std::string ComputeHash(HashAlgorithm algo, const std::string& data) { - BCRYPT_ALG_HANDLE hAlg = nullptr; - BCRYPT_HASH_HANDLE hHash = nullptr; - NTSTATUS status; - std::string result; - - status = BCryptOpenAlgorithmProvider(&hAlg, GetBCryptAlgorithm(algo), nullptr, 0); - if (!BCRYPT_SUCCESS(status)) { - return ""; - } - - DWORD hashLength = 0; - DWORD resultLength = 0; - status = BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PUCHAR)&hashLength, - sizeof(hashLength), &resultLength, 0); - if (!BCRYPT_SUCCESS(status)) { - BCryptCloseAlgorithmProvider(hAlg, 0); - return ""; - } - - std::vector hashBuffer(hashLength); - - status = BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0); - if (!BCRYPT_SUCCESS(status)) { - BCryptCloseAlgorithmProvider(hAlg, 0); - return ""; - } - - status = BCryptHashData(hHash, (PUCHAR)data.data(), static_cast(data.size()), 0); - if (!BCRYPT_SUCCESS(status)) { - BCryptDestroyHash(hHash); - BCryptCloseAlgorithmProvider(hAlg, 0); - return ""; - } - - status = BCryptFinishHash(hHash, hashBuffer.data(), hashLength, 0); - if (BCRYPT_SUCCESS(status)) { - result = BytesToHex(hashBuffer.data(), hashLength); - } - - BCryptDestroyHash(hHash); - BCryptCloseAlgorithmProvider(hAlg, 0); - - return result; -} - -std::string ComputeHMAC(HashAlgorithm algo, const std::string& key, const std::string& data) { - BCRYPT_ALG_HANDLE hAlg = nullptr; - BCRYPT_HASH_HANDLE hHash = nullptr; - NTSTATUS status; - std::string result; - - status = BCryptOpenAlgorithmProvider(&hAlg, GetBCryptAlgorithm(algo), nullptr, - BCRYPT_ALG_HANDLE_HMAC_FLAG); - if (!BCRYPT_SUCCESS(status)) { - return ""; - } - - DWORD hashLength = 0; - DWORD resultLength = 0; - status = BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PUCHAR)&hashLength, - sizeof(hashLength), &resultLength, 0); - if (!BCRYPT_SUCCESS(status)) { - BCryptCloseAlgorithmProvider(hAlg, 0); - return ""; - } - - std::vector hashBuffer(hashLength); - - status = BCryptCreateHash(hAlg, &hHash, nullptr, 0, - (PUCHAR)key.data(), static_cast(key.size()), 0); - if (!BCRYPT_SUCCESS(status)) { - BCryptCloseAlgorithmProvider(hAlg, 0); - return ""; - } - - status = BCryptHashData(hHash, (PUCHAR)data.data(), static_cast(data.size()), 0); - if (!BCRYPT_SUCCESS(status)) { - BCryptDestroyHash(hHash); - BCryptCloseAlgorithmProvider(hAlg, 0); - return ""; - } - - status = BCryptFinishHash(hHash, hashBuffer.data(), hashLength, 0); - if (BCRYPT_SUCCESS(status)) { - result = BytesToHex(hashBuffer.data(), hashLength); - } - - BCryptDestroyHash(hHash); - BCryptCloseAlgorithmProvider(hAlg, 0); - - return result; -} - -#else - -// Stub implementations for non-Windows (would need OpenSSL or similar) -std::string ComputeHash(HashAlgorithm algo, const std::string& data) { - (void)algo; - (void)data; - return ""; -} - -std::string ComputeHMAC(HashAlgorithm algo, const std::string& key, const std::string& data) { - (void)algo; - (void)key; - (void)data; - return ""; -} - -#endif - -//============================================================================= -// LUA CRYPTO API -//============================================================================= - -static const char* CRYPTO_RNG_KEY = "__mosis_crypto_rng"; - -static SecureRandom* GetRng(lua_State* L) { - lua_getfield(L, LUA_REGISTRYINDEX, CRYPTO_RNG_KEY); - if (lua_islightuserdata(L, -1)) { - SecureRandom* rng = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - return rng; - } - lua_pop(L, 1); - - // Create a default RNG if none registered - static SecureRandom default_rng; - return &default_rng; -} - -// crypto.randomBytes(n) -> string -static int lua_crypto_randomBytes(lua_State* L) { - lua_Integer n = luaL_checkinteger(L, 1); - - if (n < 0) { - return luaL_error(L, "crypto.randomBytes: count must be non-negative"); - } - if (n > 1024) { - return luaL_error(L, "crypto.randomBytes: count must not exceed 1024"); - } - - SecureRandom* rng = GetRng(L); - std::string bytes = rng->GetBytes(static_cast(n)); - - lua_pushlstring(L, bytes.data(), bytes.size()); - return 1; -} - -static HashAlgorithm ParseAlgorithm(const char* name) { - if (strcmp(name, "sha256") == 0) return HashAlgorithm::SHA256; - if (strcmp(name, "sha512") == 0) return HashAlgorithm::SHA512; - if (strcmp(name, "sha1") == 0) return HashAlgorithm::SHA1; - if (strcmp(name, "md5") == 0) return HashAlgorithm::MD5; - return HashAlgorithm::SHA256; // Default -} - -// crypto.hash(algorithm, data) -> string -static int lua_crypto_hash(lua_State* L) { - const char* algo_name = luaL_checkstring(L, 1); - size_t data_len; - const char* data = luaL_checklstring(L, 2, &data_len); - - // Limit input size - if (data_len > 10 * 1024 * 1024) { - return luaL_error(L, "crypto.hash: input too large (max 10MB)"); - } - - HashAlgorithm algo = ParseAlgorithm(algo_name); - std::string result = ComputeHash(algo, std::string(data, data_len)); - - if (result.empty()) { - return luaL_error(L, "crypto.hash: failed to compute hash"); - } - - lua_pushstring(L, result.c_str()); - return 1; -} - -// crypto.hmac(algorithm, key, data) -> string -static int lua_crypto_hmac(lua_State* L) { - const char* algo_name = luaL_checkstring(L, 1); - size_t key_len; - const char* key = luaL_checklstring(L, 2, &key_len); - size_t data_len; - const char* data = luaL_checklstring(L, 3, &data_len); - - // Limit input sizes - if (key_len > 1024) { - return luaL_error(L, "crypto.hmac: key too large (max 1KB)"); - } - if (data_len > 10 * 1024 * 1024) { - return luaL_error(L, "crypto.hmac: data too large (max 10MB)"); - } - - HashAlgorithm algo = ParseAlgorithm(algo_name); - std::string result = ComputeHMAC(algo, std::string(key, key_len), - std::string(data, data_len)); - - if (result.empty()) { - return luaL_error(L, "crypto.hmac: failed to compute HMAC"); - } - - lua_pushstring(L, result.c_str()); - return 1; -} - -// Helper to set a global in the real _G (bypassing any proxy) -static void SetGlobalInRealG(lua_State* L, const char* name) { - // Stack: value to set as global - - // Get _G (might be a proxy) - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - - // Check if it has a metatable with __index (proxy pattern) - if (lua_getmetatable(L, -1)) { - lua_getfield(L, -1, "__index"); - if (lua_istable(L, -1)) { - // Found real _G through proxy's __index - // Stack: value, proxy, mt, real_G - lua_pushvalue(L, -4); // Copy value - lua_setfield(L, -2, name); // real_G[name] = value - lua_pop(L, 4); // pop real_G, mt, proxy, original value - return; - } - lua_pop(L, 2); // pop __index, metatable - } - - // No proxy, set directly in _G - // Stack: value, _G - lua_pushvalue(L, -2); // Copy value - lua_setfield(L, -2, name); // _G[name] = value - lua_pop(L, 2); // pop _G, original value -} - -void RegisterCryptoAPI(lua_State* L) { - // Create crypto table - lua_newtable(L); - - lua_pushcfunction(L, lua_crypto_randomBytes); - lua_setfield(L, -2, "randomBytes"); - - lua_pushcfunction(L, lua_crypto_hash); - lua_setfield(L, -2, "hash"); - - lua_pushcfunction(L, lua_crypto_hmac); - lua_setfield(L, -2, "hmac"); - - // Set as global (bypassing proxy) - SetGlobalInRealG(L, "crypto"); -} - -//============================================================================= -// SECURE MATH.RANDOM -//============================================================================= - -static const char* MATH_RNG_KEY = "__mosis_math_rng"; - -// math.random([m [, n]]) - secure version -static int lua_secure_random(lua_State* L) { - lua_getfield(L, LUA_REGISTRYINDEX, MATH_RNG_KEY); - if (!lua_islightuserdata(L, -1)) { - lua_pop(L, 1); - return luaL_error(L, "math.random: RNG not initialized"); - } - SecureRandom* rng = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - int nargs = lua_gettop(L); - - if (nargs == 0) { - // Return double in [0.0, 1.0) - lua_pushnumber(L, rng->GetDouble()); - return 1; - } else if (nargs == 1) { - // Return integer in [1, n] - lua_Integer n = luaL_checkinteger(L, 1); - if (n < 1) { - return luaL_error(L, "math.random: interval is empty"); - } - lua_pushinteger(L, rng->GetInt(1, n)); - return 1; - } else { - // Return integer in [m, n] - lua_Integer m = luaL_checkinteger(L, 1); - lua_Integer n = luaL_checkinteger(L, 2); - if (m > n) { - return luaL_error(L, "math.random: interval is empty"); - } - lua_pushinteger(L, rng->GetInt(m, n)); - return 1; - } -} - -void RegisterSecureMathRandom(lua_State* L, SecureRandom* rng) { - // Store RNG in registry - lua_pushlightuserdata(L, rng); - lua_setfield(L, LUA_REGISTRYINDEX, MATH_RNG_KEY); - - // Also store for crypto API - lua_pushlightuserdata(L, rng); - lua_setfield(L, LUA_REGISTRYINDEX, CRYPTO_RNG_KEY); - - // Get the math table - lua_getglobal(L, "math"); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - return; - } - - // Replace math.random with secure version - lua_pushcfunction(L, lua_secure_random); - lua_setfield(L, -2, "random"); - - // Remove math.randomseed - lua_pushnil(L); - lua_setfield(L, -2, "randomseed"); - - lua_pop(L, 1); // Pop math table -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/crypto_api.h b/src/main/cpp/sandbox/crypto_api.h deleted file mode 100644 index a0fee8f..0000000 --- a/src/main/cpp/sandbox/crypto_api.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -struct lua_State; - -namespace mosis { - -// Per-app cryptographically secure RNG -class SecureRandom { -public: - SecureRandom(); - - // Get random bytes as binary string - std::string GetBytes(size_t count); - - // Get random integer in range [min, max] - int64_t GetInt(int64_t min, int64_t max); - - // Get random double in range [0.0, 1.0) - double GetDouble(); - -private: - std::random_device m_rd; - std::mt19937_64 m_gen; - std::mutex m_mutex; -}; - -// Hash algorithms supported -enum class HashAlgorithm { - SHA256, - SHA512, - SHA1, - MD5 -}; - -// Compute hash of data -std::string ComputeHash(HashAlgorithm algo, const std::string& data); - -// Compute HMAC of data with key -std::string ComputeHMAC(HashAlgorithm algo, const std::string& key, const std::string& data); - -// Register crypto.* APIs as globals -void RegisterCryptoAPI(lua_State* L); - -// Register secure math.random replacement (removes math.randomseed) -void RegisterSecureMathRandom(lua_State* L, SecureRandom* rng); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/database_manager.cpp b/src/main/cpp/sandbox/database_manager.cpp deleted file mode 100644 index a6a0d8b..0000000 --- a/src/main/cpp/sandbox/database_manager.cpp +++ /dev/null @@ -1,598 +0,0 @@ -#include "database_manager.h" -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace mosis { - -// Helper to set a global in the real _G (bypassing any proxy) -static void SetGlobalInRealG(lua_State* L, const char* name) { - // Stack: value to set as global - - // Get _G (might be a proxy) - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - - // Check if it has a metatable with __index (proxy pattern) - if (lua_getmetatable(L, -1)) { - lua_getfield(L, -1, "__index"); - if (lua_istable(L, -1)) { - // Found real _G through proxy's __index - // Stack: value, proxy, mt, real_G - lua_pushvalue(L, -4); // Copy value - lua_setfield(L, -2, name); // real_G[name] = value - lua_pop(L, 4); // pop real_G, mt, proxy, original value - return; - } - lua_pop(L, 2); // pop __index, metatable - } - - // No proxy, set directly in _G - // Stack: value, _G - lua_pushvalue(L, -2); // Copy value - lua_setfield(L, -2, name); // _G[name] = value - lua_pop(L, 2); // pop _G, original value -} - -// ============================================================================ -// DatabaseManager -// ============================================================================ - -DatabaseManager::DatabaseManager(const std::string& app_id, - const std::string& app_root, - const DatabaseLimits& limits) - : m_app_id(app_id) - , m_app_root(app_root) - , m_limits(limits) { -} - -DatabaseManager::~DatabaseManager() { - CloseAll(); -} - -bool DatabaseManager::ValidateName(const std::string& name, std::string& error) { - if (name.empty()) { - error = "Database name cannot be empty"; - return false; - } - - if (name.length() > 64) { - error = "Database name too long (max 64 characters)"; - return false; - } - - // Check for path traversal - if (name.find("..") != std::string::npos) { - error = "Database name contains invalid path traversal"; - return false; - } - - // Check for path separators - if (name.find('/') != std::string::npos || name.find('\\') != std::string::npos) { - error = "Database name cannot contain path separators"; - return false; - } - - // Only allow alphanumeric, underscore, hyphen - for (char c : name) { - if (!std::isalnum(static_cast(c)) && c != '_' && c != '-') { - error = "Database name contains invalid characters (only alphanumeric, underscore, hyphen allowed)"; - return false; - } - } - - return true; -} - -std::string DatabaseManager::ResolvePath(const std::string& name) { - fs::path db_dir = fs::path(m_app_root) / "db"; - return (db_dir / (name + ".db")).string(); -} - -std::shared_ptr DatabaseManager::Open(const std::string& name, std::string& error) { - // Validate name - if (!ValidateName(name, error)) { - return nullptr; - } - - // Check if already open - auto it = m_databases.find(name); - if (it != m_databases.end() && it->second->IsOpen()) { - return it->second; - } - - // Check max databases limit - if (m_databases.size() >= static_cast(m_limits.max_databases_per_app)) { - error = "Maximum number of open databases reached"; - return nullptr; - } - - // Resolve path and ensure directory exists - std::string db_path = ResolvePath(name); - fs::path parent = fs::path(db_path).parent_path(); - - std::error_code ec; - fs::create_directories(parent, ec); - if (ec) { - error = "Failed to create database directory: " + ec.message(); - return nullptr; - } - - // Open SQLite database - sqlite3* db = nullptr; - int rc = sqlite3_open(db_path.c_str(), &db); - if (rc != SQLITE_OK) { - error = "Failed to open database: " + std::string(sqlite3_errmsg(db)); - sqlite3_close(db); - return nullptr; - } - - // Create handle - auto handle = std::make_shared(db, db_path, m_limits); - m_databases[name] = handle; - return handle; -} - -void DatabaseManager::CloseAll() { - for (auto& [name, handle] : m_databases) { - if (handle) { - handle->Close(); - } - } - m_databases.clear(); -} - -size_t DatabaseManager::GetOpenDatabaseCount() const { - size_t count = 0; - for (const auto& [name, handle] : m_databases) { - if (handle && handle->IsOpen()) { - count++; - } - } - return count; -} - -// ============================================================================ -// DatabaseHandle -// ============================================================================ - -DatabaseHandle::DatabaseHandle(sqlite3* db, const std::string& path, const DatabaseLimits& limits) - : m_db(db) - , m_path(path) - , m_limits(limits) { - - if (m_db) { - // Set up authorizer - sqlite3_set_authorizer(m_db, Authorizer, this); - - // Set busy timeout - sqlite3_busy_timeout(m_db, m_limits.max_query_time_ms); - } -} - -DatabaseHandle::~DatabaseHandle() { - Close(); -} - -void DatabaseHandle::Close() { - if (m_db) { - sqlite3_close(m_db); - m_db = nullptr; - } -} - -int DatabaseHandle::Authorizer(void* user_data, int action, const char* arg1, - const char* arg2, const char* arg3, const char* arg4) { - (void)user_data; - (void)arg3; - (void)arg4; - - switch (action) { - case SQLITE_ATTACH: - case SQLITE_DETACH: - // Block attaching/detaching databases - return SQLITE_DENY; - - case SQLITE_PRAGMA: { - // Allow safe pragmas only - if (arg1) { - std::string pragma(arg1); - // Convert to lowercase for comparison - std::transform(pragma.begin(), pragma.end(), pragma.begin(), - [](unsigned char c) { return std::tolower(c); }); - - // Whitelist of safe pragmas - if (pragma == "table_info" || - pragma == "index_list" || - pragma == "index_info" || - pragma == "foreign_keys" || - pragma == "foreign_key_list" || - pragma == "database_list" || - pragma == "table_list" || - pragma == "integrity_check" || - pragma == "quick_check") { - return SQLITE_OK; - } - // Block all other pragmas - return SQLITE_DENY; - } - return SQLITE_DENY; - } - - case SQLITE_FUNCTION: { - // Block dangerous functions - if (arg2) { - std::string func(arg2); - std::transform(func.begin(), func.end(), func.begin(), - [](unsigned char c) { return std::tolower(c); }); - if (func == "load_extension") { - return SQLITE_DENY; - } - } - return SQLITE_OK; - } - - default: - return SQLITE_OK; - } -} - -bool DatabaseHandle::BindParameters(void* stmt_ptr, const std::vector& params, std::string& error) { - sqlite3_stmt* stmt = static_cast(stmt_ptr); - - for (size_t i = 0; i < params.size(); i++) { - int idx = static_cast(i + 1); // SQLite parameters are 1-indexed - int rc = SQLITE_OK; - - std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - rc = sqlite3_bind_null(stmt, idx); - } else if constexpr (std::is_same_v) { - rc = sqlite3_bind_int64(stmt, idx, arg); - } else if constexpr (std::is_same_v) { - rc = sqlite3_bind_double(stmt, idx, arg); - } else if constexpr (std::is_same_v) { - rc = sqlite3_bind_text(stmt, idx, arg.c_str(), static_cast(arg.size()), SQLITE_TRANSIENT); - } else if constexpr (std::is_same_v>) { - rc = sqlite3_bind_blob(stmt, idx, arg.data(), static_cast(arg.size()), SQLITE_TRANSIENT); - } - }, params[i]); - - if (rc != SQLITE_OK) { - error = "Failed to bind parameter " + std::to_string(i) + ": " + sqlite3_errmsg(m_db); - return false; - } - } - return true; -} - -bool DatabaseHandle::Execute(const std::string& sql, const std::vector& params, std::string& error) { - if (!m_db) { - error = "Database not open"; - return false; - } - - sqlite3_stmt* stmt = nullptr; - int rc = sqlite3_prepare_v2(m_db, sql.c_str(), static_cast(sql.size()), &stmt, nullptr); - if (rc != SQLITE_OK) { - error = "SQL prepare error: " + std::string(sqlite3_errmsg(m_db)); - return false; - } - - if (!BindParameters(stmt, params, error)) { - sqlite3_finalize(stmt); - return false; - } - - rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - - if (rc != SQLITE_DONE && rc != SQLITE_ROW) { - error = "SQL execution error: " + std::string(sqlite3_errmsg(m_db)); - return false; - } - - return true; -} - -std::optional DatabaseHandle::Query(const std::string& sql, const std::vector& params, - std::string& error) { - if (!m_db) { - error = "Database not open"; - return std::nullopt; - } - - sqlite3_stmt* stmt = nullptr; - int rc = sqlite3_prepare_v2(m_db, sql.c_str(), static_cast(sql.size()), &stmt, nullptr); - if (rc != SQLITE_OK) { - error = "SQL prepare error: " + std::string(sqlite3_errmsg(m_db)); - return std::nullopt; - } - - if (!BindParameters(stmt, params, error)) { - sqlite3_finalize(stmt); - return std::nullopt; - } - - SqlResult result; - int row_count = 0; - - while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { - if (row_count >= m_limits.max_result_rows) { - error = "Result row limit exceeded"; - sqlite3_finalize(stmt); - return std::nullopt; - } - - int col_count = sqlite3_column_count(stmt); - SqlRow row; - row.reserve(col_count); - - for (int i = 0; i < col_count; i++) { - int type = sqlite3_column_type(stmt, i); - switch (type) { - case SQLITE_NULL: - row.push_back(nullptr); - break; - case SQLITE_INTEGER: - row.push_back(sqlite3_column_int64(stmt, i)); - break; - case SQLITE_FLOAT: - row.push_back(sqlite3_column_double(stmt, i)); - break; - case SQLITE_TEXT: { - const char* text = reinterpret_cast(sqlite3_column_text(stmt, i)); - int len = sqlite3_column_bytes(stmt, i); - row.push_back(std::string(text, len)); - break; - } - case SQLITE_BLOB: { - const uint8_t* data = static_cast(sqlite3_column_blob(stmt, i)); - int len = sqlite3_column_bytes(stmt, i); - row.push_back(std::vector(data, data + len)); - break; - } - } - } - - result.push_back(std::move(row)); - row_count++; - } - - sqlite3_finalize(stmt); - - if (rc != SQLITE_DONE) { - error = "SQL query error: " + std::string(sqlite3_errmsg(m_db)); - return std::nullopt; - } - - return result; -} - -int64_t DatabaseHandle::GetLastInsertRowId() const { - if (!m_db) return 0; - return sqlite3_last_insert_rowid(m_db); -} - -int DatabaseHandle::GetChanges() const { - if (!m_db) return 0; - return sqlite3_changes(m_db); -} - -// ============================================================================ -// Lua API -// ============================================================================ - -struct LuaDatabaseHandle { - std::shared_ptr handle; -}; - -static int Lua_DatabaseHandle_Execute(lua_State* L) { - LuaDatabaseHandle* lh = static_cast(luaL_checkudata(L, 1, "DatabaseHandle")); - if (!lh->handle || !lh->handle->IsOpen()) { - lua_pushboolean(L, 0); - lua_pushstring(L, "Database not open"); - return 2; - } - - const char* sql = luaL_checkstring(L, 2); - - // Get parameters from optional table - std::vector params; - if (lua_gettop(L) >= 3 && lua_istable(L, 3)) { - lua_pushnil(L); - while (lua_next(L, 3) != 0) { - if (lua_isnil(L, -1)) { - params.push_back(nullptr); - } else if (lua_isinteger(L, -1)) { - params.push_back(static_cast(lua_tointeger(L, -1))); - } else if (lua_isnumber(L, -1)) { - params.push_back(lua_tonumber(L, -1)); - } else if (lua_isstring(L, -1)) { - size_t len; - const char* str = lua_tolstring(L, -1, &len); - params.push_back(std::string(str, len)); - } else if (lua_isboolean(L, -1)) { - params.push_back(static_cast(lua_toboolean(L, -1))); - } - lua_pop(L, 1); - } - } - - std::string error; - if (lh->handle->Execute(sql, params, error)) { - lua_pushboolean(L, 1); - return 1; - } else { - lua_pushboolean(L, 0); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -static int Lua_DatabaseHandle_Query(lua_State* L) { - LuaDatabaseHandle* lh = static_cast(luaL_checkudata(L, 1, "DatabaseHandle")); - if (!lh->handle || !lh->handle->IsOpen()) { - lua_pushnil(L); - lua_pushstring(L, "Database not open"); - return 2; - } - - const char* sql = luaL_checkstring(L, 2); - - // Get parameters from optional table - std::vector params; - if (lua_gettop(L) >= 3 && lua_istable(L, 3)) { - lua_pushnil(L); - while (lua_next(L, 3) != 0) { - if (lua_isnil(L, -1)) { - params.push_back(nullptr); - } else if (lua_isinteger(L, -1)) { - params.push_back(static_cast(lua_tointeger(L, -1))); - } else if (lua_isnumber(L, -1)) { - params.push_back(lua_tonumber(L, -1)); - } else if (lua_isstring(L, -1)) { - size_t len; - const char* str = lua_tolstring(L, -1, &len); - params.push_back(std::string(str, len)); - } else if (lua_isboolean(L, -1)) { - params.push_back(static_cast(lua_toboolean(L, -1))); - } - lua_pop(L, 1); - } - } - - std::string error; - auto result = lh->handle->Query(sql, params, error); - - if (!result.has_value()) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - // Create result table - lua_createtable(L, static_cast(result->size()), 0); - int row_idx = 1; - for (const auto& row : *result) { - lua_createtable(L, static_cast(row.size()), 0); - int col_idx = 1; - for (const auto& val : row) { - std::visit([L](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - lua_pushnil(L); - } else if constexpr (std::is_same_v) { - lua_pushinteger(L, arg); - } else if constexpr (std::is_same_v) { - lua_pushnumber(L, arg); - } else if constexpr (std::is_same_v) { - lua_pushlstring(L, arg.c_str(), arg.size()); - } else if constexpr (std::is_same_v>) { - lua_pushlstring(L, reinterpret_cast(arg.data()), arg.size()); - } - }, val); - lua_rawseti(L, -2, col_idx++); - } - lua_rawseti(L, -2, row_idx++); - } - - return 1; -} - -static int Lua_DatabaseHandle_LastInsertId(lua_State* L) { - LuaDatabaseHandle* lh = static_cast(luaL_checkudata(L, 1, "DatabaseHandle")); - if (!lh->handle) { - lua_pushinteger(L, 0); - return 1; - } - lua_pushinteger(L, lh->handle->GetLastInsertRowId()); - return 1; -} - -static int Lua_DatabaseHandle_Changes(lua_State* L) { - LuaDatabaseHandle* lh = static_cast(luaL_checkudata(L, 1, "DatabaseHandle")); - if (!lh->handle) { - lua_pushinteger(L, 0); - return 1; - } - lua_pushinteger(L, lh->handle->GetChanges()); - return 1; -} - -static int Lua_DatabaseHandle_Close(lua_State* L) { - LuaDatabaseHandle* lh = static_cast(luaL_checkudata(L, 1, "DatabaseHandle")); - if (lh->handle) { - lh->handle->Close(); - } - return 0; -} - -static int Lua_DatabaseHandle_GC(lua_State* L) { - LuaDatabaseHandle* lh = static_cast(luaL_checkudata(L, 1, "DatabaseHandle")); - lh->~LuaDatabaseHandle(); - return 0; -} - -static const luaL_Reg DatabaseHandle_methods[] = { - {"execute", Lua_DatabaseHandle_Execute}, - {"query", Lua_DatabaseHandle_Query}, - {"lastInsertId", Lua_DatabaseHandle_LastInsertId}, - {"changes", Lua_DatabaseHandle_Changes}, - {"close", Lua_DatabaseHandle_Close}, - {nullptr, nullptr} -}; - -static int Lua_Database_Open(lua_State* L) { - DatabaseManager* manager = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* name = luaL_checkstring(L, 1); - - std::string error; - auto handle = manager->Open(name, error); - - if (!handle) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - // Create userdata - LuaDatabaseHandle* lh = static_cast(lua_newuserdata(L, sizeof(LuaDatabaseHandle))); - new (lh) LuaDatabaseHandle{handle}; - - luaL_getmetatable(L, "DatabaseHandle"); - lua_setmetatable(L, -2); - - return 1; -} - -void RegisterDatabaseAPI(lua_State* L, DatabaseManager* manager) { - // Create DatabaseHandle metatable - luaL_newmetatable(L, "DatabaseHandle"); - - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); - - lua_pushcfunction(L, Lua_DatabaseHandle_GC); - lua_setfield(L, -2, "__gc"); - - luaL_setfuncs(L, DatabaseHandle_methods, 0); - lua_pop(L, 1); - - // Create database table - lua_newtable(L); - - // database.open - lua_pushlightuserdata(L, manager); - lua_pushcclosure(L, Lua_Database_Open, 1); - lua_setfield(L, -2, "open"); - - // Set as global - SetGlobalInRealG(L, "database"); -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/database_manager.h b/src/main/cpp/sandbox/database_manager.h deleted file mode 100644 index 11a972a..0000000 --- a/src/main/cpp/sandbox/database_manager.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -struct sqlite3; -struct lua_State; - -namespace mosis { - -// SQL value types -using SqlValue = std::variant>; -using SqlRow = std::vector; -using SqlResult = std::vector; - -struct DatabaseLimits { - size_t max_database_size = 50 * 1024 * 1024; // 50 MB per database - int max_databases_per_app = 5; // Max open databases - int max_query_time_ms = 5000; // 5 second query timeout - int max_result_rows = 10000; // Max rows returned -}; - -class DatabaseHandle; - -class DatabaseManager { -public: - DatabaseManager(const std::string& app_id, - const std::string& app_root, - const DatabaseLimits& limits = DatabaseLimits{}); - ~DatabaseManager(); - - // Database operations - std::shared_ptr Open(const std::string& name, std::string& error); - void CloseAll(); - - // Stats - size_t GetOpenDatabaseCount() const; - -private: - std::string m_app_id; - std::string m_app_root; - DatabaseLimits m_limits; - std::unordered_map> m_databases; - - std::string ResolvePath(const std::string& name); - bool ValidateName(const std::string& name, std::string& error); -}; - -class DatabaseHandle { -public: - DatabaseHandle(sqlite3* db, const std::string& path, const DatabaseLimits& limits); - ~DatabaseHandle(); - - // Execute (INSERT, UPDATE, DELETE, CREATE, etc.) - bool Execute(const std::string& sql, const std::vector& params, std::string& error); - - // Query (SELECT) - std::optional Query(const std::string& sql, const std::vector& params, - std::string& error); - - // Get last insert rowid - int64_t GetLastInsertRowId() const; - - // Get affected rows - int GetChanges() const; - - bool IsOpen() const { return m_db != nullptr; } - void Close(); - -private: - sqlite3* m_db; - std::string m_path; - DatabaseLimits m_limits; - - static int Authorizer(void* user_data, int action, const char* arg1, - const char* arg2, const char* arg3, const char* arg4); - - bool BindParameters(void* stmt, const std::vector& params, std::string& error); -}; - -// Register database.* APIs as globals -void RegisterDatabaseAPI(lua_State* L, DatabaseManager* manager); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/http_validator.cpp b/src/main/cpp/sandbox/http_validator.cpp deleted file mode 100644 index 6a15647..0000000 --- a/src/main/cpp/sandbox/http_validator.cpp +++ /dev/null @@ -1,388 +0,0 @@ -#include "http_validator.h" -#include -#include -#include -#include - -namespace mosis { - -HttpValidator::HttpValidator() - : m_domain_restrictions_enabled(false) -{ -} - -void HttpValidator::SetAllowedDomains(const std::vector& domains) { - m_allowed_domains = domains; - m_domain_restrictions_enabled = !domains.empty(); -} - -void HttpValidator::ClearDomainRestrictions() { - m_allowed_domains.clear(); - m_domain_restrictions_enabled = false; -} - -std::optional HttpValidator::Validate(const std::string& url, std::string& error) { - // Parse URL - auto parsed = ParseUrl(url); - if (!parsed) { - error = "Invalid URL format"; - return std::nullopt; - } - - // Must be HTTPS or WSS - if (parsed->scheme != "https" && parsed->scheme != "wss") { - error = "HTTPS or WSS required, got: " + parsed->scheme; - return std::nullopt; - } - - // Check for localhost names - if (IsLocalhostName(parsed->host)) { - error = "localhost blocked for security"; - return std::nullopt; - } - - // Check for metadata hostnames - if (IsMetadataHostname(parsed->host)) { - error = "Cloud metadata hostname blocked for security"; - return std::nullopt; - } - - // Check if it's an IP address and validate - if (parsed->is_ip_address) { - if (IsBlockedIP(parsed->host)) { - error = "IP address blocked: private, localhost, or metadata endpoint"; - return std::nullopt; - } - } - - // Check domain whitelist - if (m_domain_restrictions_enabled && !IsDomainAllowed(parsed->host)) { - error = "Domain not in allowed list: " + parsed->host; - return std::nullopt; - } - - return parsed; -} - -bool HttpValidator::IsIPv4Address(const std::string& host) { - // Simple IPv4 pattern: numbers and dots - if (host.empty()) return false; - - int dots = 0; - int num_start = 0; - for (size_t i = 0; i <= host.length(); i++) { - if (i == host.length() || host[i] == '.') { - if (i == (size_t)num_start) return false; // Empty segment - std::string segment = host.substr(num_start, i - num_start); - // Check if segment is a valid number 0-255 - if (segment.empty() || segment.length() > 3) return false; - for (char c : segment) { - if (!std::isdigit(static_cast(c))) return false; - } - int val = std::stoi(segment); - if (val < 0 || val > 255) return false; - if (i < host.length()) { - dots++; - num_start = static_cast(i) + 1; - } - } - } - - return dots == 3; -} - -bool HttpValidator::IsIPv6Address(const std::string& host) { - // IPv6 addresses in URLs are enclosed in brackets: [::1] - if (host.length() < 2) return false; - if (host.front() == '[' && host.back() == ']') { - return true; // Simplified check - bracket notation means IPv6 - } - // Also check for raw IPv6 (contains colons, no dots or limited dots) - int colons = std::count(host.begin(), host.end(), ':'); - int dots = std::count(host.begin(), host.end(), '.'); - return colons >= 2 && dots <= 3; // IPv6 has multiple colons -} - -bool HttpValidator::IsPrivateIPv4(const std::string& ip) { - // Parse IPv4 octets - std::array octets{}; - if (sscanf(ip.c_str(), "%d.%d.%d.%d", &octets[0], &octets[1], &octets[2], &octets[3]) != 4) { - return false; - } - - // 0.0.0.0 - all interfaces - if (octets[0] == 0 && octets[1] == 0 && octets[2] == 0 && octets[3] == 0) { - return true; - } - - // 127.0.0.0/8 - loopback - if (octets[0] == 127) { - return true; - } - - // 10.0.0.0/8 - private Class A - if (octets[0] == 10) { - return true; - } - - // 172.16.0.0/12 - private Class B (172.16.0.0 - 172.31.255.255) - if (octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31) { - return true; - } - - // 192.168.0.0/16 - private Class C - if (octets[0] == 192 && octets[1] == 168) { - return true; - } - - // 169.254.0.0/16 - link-local - if (octets[0] == 169 && octets[1] == 254) { - return true; - } - - return false; -} - -bool HttpValidator::IsPrivateIPv6(const std::string& ip) { - std::string addr = ip; - // Remove brackets if present - if (!addr.empty() && addr.front() == '[') addr = addr.substr(1); - if (!addr.empty() && addr.back() == ']') addr.pop_back(); - - // Convert to lowercase for comparison - std::transform(addr.begin(), addr.end(), addr.begin(), - [](unsigned char c) { return std::tolower(c); }); - - // ::1 - loopback - if (addr == "::1" || addr == "0:0:0:0:0:0:0:1") { - return true; - } - - // :: - unspecified (equivalent to 0.0.0.0) - if (addr == "::" || addr == "0:0:0:0:0:0:0:0") { - return true; - } - - // fc00::/7 - unique local addresses (fc00:: to fdff::) - if (addr.length() >= 2) { - char first = addr[0]; - char second = addr.length() > 1 ? addr[1] : '0'; - if (first == 'f' && (second == 'c' || second == 'd')) { - return true; - } - } - - // fe80::/10 - link-local - if (addr.rfind("fe80:", 0) == 0 || addr.rfind("fe8", 0) == 0 || - addr.rfind("fe9", 0) == 0 || addr.rfind("fea", 0) == 0 || - addr.rfind("feb", 0) == 0) { - return true; - } - - return false; -} - -bool HttpValidator::IsLocalhostIP(const std::string& host) { - // IPv4 localhost - if (IsIPv4Address(host)) { - std::array octets{}; - if (sscanf(host.c_str(), "%d.%d.%d.%d", &octets[0], &octets[1], &octets[2], &octets[3]) == 4) { - return octets[0] == 127; - } - } - - // IPv6 localhost - std::string addr = host; - if (!addr.empty() && addr.front() == '[') addr = addr.substr(1); - if (!addr.empty() && addr.back() == ']') addr.pop_back(); - std::transform(addr.begin(), addr.end(), addr.begin(), - [](unsigned char c) { return std::tolower(c); }); - - return addr == "::1" || addr == "0:0:0:0:0:0:0:1"; -} - -bool HttpValidator::IsMetadataIP(const std::string& host) { - // AWS/Azure/GCP metadata endpoint - if (host == "169.254.169.254") { - return true; - } - - // GCP alternate - if (host == "metadata.google.internal") { - return true; - } - - return false; -} - -bool HttpValidator::IsBlockedIP(const std::string& host) { - if (IsIPv4Address(host)) { - return IsPrivateIPv4(host) || IsMetadataIP(host); - } - - if (IsIPv6Address(host)) { - return IsPrivateIPv6(host); - } - - return false; -} - -bool HttpValidator::IsDomainAllowed(const std::string& host) { - if (!m_domain_restrictions_enabled) { - return true; - } - - std::string lower_host = host; - std::transform(lower_host.begin(), lower_host.end(), lower_host.begin(), - [](unsigned char c) { return std::tolower(c); }); - - for (const auto& domain : m_allowed_domains) { - std::string lower_domain = domain; - std::transform(lower_domain.begin(), lower_domain.end(), lower_domain.begin(), - [](unsigned char c) { return std::tolower(c); }); - - // Exact match - if (lower_host == lower_domain) { - return true; - } - - // Subdomain match (e.g., "api.example.com" matches "example.com") - if (lower_host.length() > lower_domain.length()) { - size_t pos = lower_host.length() - lower_domain.length(); - if (lower_host[pos - 1] == '.' && - lower_host.substr(pos) == lower_domain) { - return true; - } - } - } - - return false; -} - -bool HttpValidator::IsLocalhostName(const std::string& host) { - std::string lower = host; - std::transform(lower.begin(), lower.end(), lower.begin(), - [](unsigned char c) { return std::tolower(c); }); - - // Common localhost names - if (lower == "localhost") return true; - if (lower == "localhost.localdomain") return true; - - // Ends with .localhost - if (lower.length() > 10 && lower.substr(lower.length() - 10) == ".localhost") { - return true; - } - - return false; -} - -bool HttpValidator::IsMetadataHostname(const std::string& host) { - std::string lower = host; - std::transform(lower.begin(), lower.end(), lower.begin(), - [](unsigned char c) { return std::tolower(c); }); - - // GCP metadata - if (lower == "metadata.google.internal") return true; - if (lower == "metadata") return true; - - // Azure metadata - if (lower == "metadata.azure.internal") return true; - - return false; -} - -std::optional HttpValidator::ParseUrl(const std::string& url) { - ParsedUrl result; - result.port = 443; // Default HTTPS port - result.is_ip_address = false; - - // Find scheme - size_t scheme_end = url.find("://"); - if (scheme_end == std::string::npos) { - return std::nullopt; - } - - result.scheme = url.substr(0, scheme_end); - std::transform(result.scheme.begin(), result.scheme.end(), result.scheme.begin(), - [](unsigned char c) { return std::tolower(c); }); - - // Start of authority - size_t auth_start = scheme_end + 3; - if (auth_start >= url.length()) { - return std::nullopt; - } - - // Find end of authority (path starts with /) - size_t path_start = url.find('/', auth_start); - std::string authority; - if (path_start == std::string::npos) { - authority = url.substr(auth_start); - result.path = "/"; - } else { - authority = url.substr(auth_start, path_start - auth_start); - - // Find query string - size_t query_start = url.find('?', path_start); - if (query_start != std::string::npos) { - result.path = url.substr(path_start, query_start - path_start); - result.query = url.substr(query_start); - } else { - result.path = url.substr(path_start); - } - } - - if (authority.empty()) { - return std::nullopt; - } - - // Parse authority for host and port - // Handle IPv6 addresses in brackets - if (authority[0] == '[') { - size_t bracket_end = authority.find(']'); - if (bracket_end == std::string::npos) { - return std::nullopt; // Malformed IPv6 - } - result.host = authority.substr(0, bracket_end + 1); - result.is_ip_address = true; - - // Check for port after bracket - if (bracket_end + 1 < authority.length()) { - if (authority[bracket_end + 1] == ':') { - std::string port_str = authority.substr(bracket_end + 2); - try { - result.port = static_cast(std::stoi(port_str)); - } catch (...) { - return std::nullopt; - } - } - } - } else { - // Regular host or IPv4 - size_t port_pos = authority.rfind(':'); - if (port_pos != std::string::npos) { - result.host = authority.substr(0, port_pos); - std::string port_str = authority.substr(port_pos + 1); - try { - result.port = static_cast(std::stoi(port_str)); - } catch (...) { - return std::nullopt; - } - } else { - result.host = authority; - } - - // Check if it's an IP address - result.is_ip_address = IsIPv4Address(result.host) || IsIPv6Address(result.host); - } - - // Default port based on scheme - if ((result.scheme == "https" || result.scheme == "wss") && result.port == 0) { - result.port = 443; - } else if ((result.scheme == "http" || result.scheme == "ws") && result.port == 0) { - result.port = 80; - } - - return result; -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/http_validator.h b/src/main/cpp/sandbox/http_validator.h deleted file mode 100644 index 79b3334..0000000 --- a/src/main/cpp/sandbox/http_validator.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#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 literal -}; - -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; - - // IP address validation - bool IsIPv4Address(const std::string& host); - bool IsIPv6Address(const std::string& host); - bool IsPrivateIPv4(const std::string& ip); - bool IsPrivateIPv6(const std::string& ip); - bool IsLocalhostIP(const std::string& host); - bool IsMetadataIP(const std::string& host); - bool IsBlockedIP(const std::string& host); - - // Domain validation - bool IsDomainAllowed(const std::string& host); - bool IsLocalhostName(const std::string& host); - bool IsMetadataHostname(const std::string& host); - - // URL parsing - std::optional ParseUrl(const std::string& url); -}; - -} // namespace mosis diff --git a/src/main/cpp/sandbox/json_api.cpp b/src/main/cpp/sandbox/json_api.cpp deleted file mode 100644 index b635944..0000000 --- a/src/main/cpp/sandbox/json_api.cpp +++ /dev/null @@ -1,369 +0,0 @@ -#include "json_api.h" - -#include -#include -#include -#include - -using json = nlohmann::json; - -namespace mosis { - -// Registry key for storing limits -static const char* JSON_LIMITS_KEY = "__mosis_json_limits"; - -// Get limits from registry -static JsonLimits GetLimits(lua_State* L) { - lua_getfield(L, LUA_REGISTRYINDEX, JSON_LIMITS_KEY); - if (lua_islightuserdata(L, -1)) { - JsonLimits* limits = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - return *limits; - } - lua_pop(L, 1); - return JsonLimits{}; -} - -//============================================================================= -// JSON DECODE -//============================================================================= - -// Custom exception for JSON errors (thrown instead of luaL_error to allow catching) -class JsonError : public std::runtime_error { -public: - explicit JsonError(const std::string& msg) : std::runtime_error(msg) {} -}; - -// Forward declaration -static void JsonToLua(lua_State* L, const json& j, const JsonLimits& limits, int depth); - -static void JsonToLua(lua_State* L, const json& j, const JsonLimits& limits, int depth) { - if (depth > limits.max_depth) { - throw JsonError("maximum depth exceeded"); - } - - switch (j.type()) { - case json::value_t::null: - lua_pushnil(L); - break; - - case json::value_t::boolean: - lua_pushboolean(L, j.get() ? 1 : 0); - break; - - case json::value_t::number_integer: - case json::value_t::number_unsigned: - lua_pushinteger(L, j.get()); - break; - - case json::value_t::number_float: - lua_pushnumber(L, j.get()); - break; - - case json::value_t::string: { - const std::string& s = j.get_ref(); - if (s.size() > limits.max_string_length) { - throw JsonError("string too large"); - } - lua_pushlstring(L, s.c_str(), s.size()); - break; - } - - case json::value_t::array: { - if (j.size() > limits.max_array_size) { - throw JsonError("array size limit exceeded"); - } - lua_createtable(L, static_cast(j.size()), 0); - int i = 1; - for (const auto& elem : j) { - JsonToLua(L, elem, limits, depth + 1); - lua_rawseti(L, -2, i++); - } - break; - } - - case json::value_t::object: { - if (j.size() > limits.max_object_size) { - throw JsonError("object size limit exceeded"); - } - lua_createtable(L, 0, static_cast(j.size())); - for (auto it = j.begin(); it != j.end(); ++it) { - if (it.key().size() > limits.max_string_length) { - throw JsonError("key too large"); - } - lua_pushlstring(L, it.key().c_str(), it.key().size()); - JsonToLua(L, it.value(), limits, depth + 1); - lua_rawset(L, -3); - } - break; - } - - default: - lua_pushnil(L); - break; - } -} - -// json.decode(str) -> table|nil, error -static int lua_json_decode(lua_State* L) { - size_t len; - const char* str = luaL_checklstring(L, 1, &len); - - JsonLimits limits = GetLimits(L); - - if (len > limits.max_output_size) { - lua_pushnil(L); - lua_pushstring(L, "input too large"); - return 2; - } - - try { - json j = json::parse(str, str + len); - JsonToLua(L, j, limits, 0); - return 1; - } catch (const JsonError& e) { - lua_pushnil(L); - lua_pushstring(L, e.what()); - return 2; - } catch (const json::parse_error& e) { - lua_pushnil(L); - lua_pushstring(L, e.what()); - return 2; - } catch (const std::exception& e) { - lua_pushnil(L); - lua_pushstring(L, e.what()); - return 2; - } -} - -//============================================================================= -// JSON ENCODE -//============================================================================= - -// Forward declaration -static json LuaToJson(lua_State* L, int index, const JsonLimits& limits, - int depth, std::unordered_set& visited, - size_t& output_size); - -static json LuaToJson(lua_State* L, int index, const JsonLimits& limits, - int depth, std::unordered_set& visited, - size_t& output_size) { - if (depth > limits.max_depth) { - throw JsonError("maximum depth exceeded"); - } - - if (output_size > limits.max_output_size) { - throw JsonError("output size limit exceeded"); - } - - int type = lua_type(L, index); - - switch (type) { - case LUA_TNIL: - return nullptr; - - case LUA_TBOOLEAN: - return lua_toboolean(L, index) != 0; - - case LUA_TNUMBER: - if (lua_isinteger(L, index)) { - return lua_tointeger(L, index); - } - return lua_tonumber(L, index); - - case LUA_TSTRING: { - size_t len; - const char* s = lua_tolstring(L, index, &len); - if (len > limits.max_string_length) { - throw JsonError("string too large"); - } - output_size += len + 2; // Approximate: string + quotes - return std::string(s, len); - } - - case LUA_TTABLE: { - // Check for cycles - const void* ptr = lua_topointer(L, index); - if (visited.find(ptr) != visited.end()) { - throw JsonError("circular reference detected"); - } - visited.insert(ptr); - - // Determine if array or object by checking keys - bool is_array = true; - size_t array_len = 0; - - lua_pushnil(L); - while (lua_next(L, index) != 0) { - if (lua_type(L, -2) == LUA_TNUMBER && lua_isinteger(L, -2)) { - lua_Integer key = lua_tointeger(L, -2); - if (key >= 1) { - array_len = std::max(array_len, static_cast(key)); - } else { - is_array = false; - } - } else { - is_array = false; - } - lua_pop(L, 1); - } - - // Verify array is contiguous - if (is_array && array_len > 0) { - for (size_t i = 1; i <= array_len; i++) { - lua_rawgeti(L, index, static_cast(i)); - if (lua_isnil(L, -1)) { - is_array = false; - } - lua_pop(L, 1); - if (!is_array) break; - } - } - - if (is_array && array_len > 0) { - if (array_len > limits.max_array_size) { - throw JsonError("array size limit exceeded"); - } - json arr = json::array(); - for (size_t i = 1; i <= array_len; i++) { - lua_rawgeti(L, index, static_cast(i)); - arr.push_back(LuaToJson(L, lua_gettop(L), limits, depth + 1, visited, output_size)); - lua_pop(L, 1); - } - visited.erase(ptr); - return arr; - } else { - // Object - json obj = json::object(); - size_t key_count = 0; - - lua_pushnil(L); - while (lua_next(L, index) != 0) { - key_count++; - if (key_count > limits.max_object_size) { - throw JsonError("object size limit exceeded"); - } - - // Get key as string - std::string key; - if (lua_type(L, -2) == LUA_TSTRING) { - size_t len; - const char* s = lua_tolstring(L, -2, &len); - key = std::string(s, len); - } else if (lua_type(L, -2) == LUA_TNUMBER) { - if (lua_isinteger(L, -2)) { - key = std::to_string(lua_tointeger(L, -2)); - } else { - key = std::to_string(lua_tonumber(L, -2)); - } - } else { - lua_pop(L, 2); - throw JsonError("unsupported key type"); - } - - output_size += key.size() + 3; // key + quotes + colon - - obj[key] = LuaToJson(L, lua_gettop(L), limits, depth + 1, visited, output_size); - lua_pop(L, 1); - } - - visited.erase(ptr); - return obj; - } - } - - case LUA_TFUNCTION: - case LUA_TUSERDATA: - case LUA_TTHREAD: - case LUA_TLIGHTUSERDATA: - throw JsonError(std::string("unsupported type '") + lua_typename(L, type) + "'"); - - default: - return nullptr; - } -} - -// json.encode(table) -> string|nil, error -static int lua_json_encode(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - - JsonLimits limits = GetLimits(L); - std::unordered_set visited; - size_t output_size = 0; - - try { - json j = LuaToJson(L, 1, limits, 0, visited, output_size); - std::string result = j.dump(); - - if (result.size() > limits.max_output_size) { - lua_pushnil(L); - lua_pushstring(L, "output size limit exceeded"); - return 2; - } - - lua_pushlstring(L, result.c_str(), result.size()); - return 1; - } catch (const JsonError& e) { - lua_pushnil(L); - lua_pushstring(L, e.what()); - return 2; - } catch (const std::exception& e) { - lua_pushnil(L); - lua_pushstring(L, e.what()); - return 2; - } -} - -//============================================================================= -// REGISTRATION -//============================================================================= - -// Helper to set a global in the real _G (bypassing any proxy) -static void SetGlobalInRealG(lua_State* L, const char* name) { - // Stack: value to set as global - - // Get _G (might be a proxy) - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - - // Check if it has a metatable with __index (proxy pattern) - if (lua_getmetatable(L, -1)) { - lua_getfield(L, -1, "__index"); - if (lua_istable(L, -1)) { - // Found real _G through proxy's __index - // Stack: value, proxy, mt, real_G - lua_pushvalue(L, -4); // Copy value - lua_setfield(L, -2, name); // real_G[name] = value - lua_pop(L, 4); // pop real_G, mt, proxy, original value - return; - } - lua_pop(L, 2); // pop __index, metatable - } - - // No proxy, set directly in _G - // Stack: value, _G - lua_pushvalue(L, -2); // Copy value - lua_setfield(L, -2, name); // _G[name] = value - lua_pop(L, 2); // pop _G, original value -} - -void RegisterJsonAPI(lua_State* L, const JsonLimits& limits) { - // Store limits in registry (allocate static storage) - static JsonLimits stored_limits; - stored_limits = limits; - lua_pushlightuserdata(L, &stored_limits); - lua_setfield(L, LUA_REGISTRYINDEX, JSON_LIMITS_KEY); - - // Create json table - lua_newtable(L); - - lua_pushcfunction(L, lua_json_decode); - lua_setfield(L, -2, "decode"); - - lua_pushcfunction(L, lua_json_encode); - lua_setfield(L, -2, "encode"); - - // Set as global (bypassing proxy) - SetGlobalInRealG(L, "json"); -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/json_api.h b/src/main/cpp/sandbox/json_api.h deleted file mode 100644 index 5f59d94..0000000 --- a/src/main/cpp/sandbox/json_api.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -struct lua_State; - -namespace mosis { - -// Configuration limits for JSON operations -struct JsonLimits { - int max_depth = 32; // Maximum nesting depth - size_t max_string_length = 1 * 1024 * 1024; // 1 MB per string - size_t max_output_size = 10 * 1024 * 1024; // 10 MB total output - size_t max_array_size = 100000; // Max elements in array - size_t max_object_size = 10000; // Max keys in object -}; - -// Register json.encode() and json.decode() as globals -void RegisterJsonAPI(lua_State* L, const JsonLimits& limits = JsonLimits{}); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/lua_sandbox.cpp b/src/main/cpp/sandbox/lua_sandbox.cpp deleted file mode 100644 index 60f9c4c..0000000 --- a/src/main/cpp/sandbox/lua_sandbox.cpp +++ /dev/null @@ -1,448 +0,0 @@ -#include "lua_sandbox.h" - -#include -#include -#include -#include -#include -#include -#include - -namespace mosis { - -//============================================================================= -// ALLOCATOR -//============================================================================= - -void* LuaSandbox::SandboxAlloc(void* ud, void* ptr, size_t osize, size_t nsize) { - auto* sandbox = static_cast(ud); - - // Calculate new usage - // osize is the old size (0 for new allocations) - // nsize is the new size (0 for frees) - size_t new_usage = sandbox->m_memory_used - osize + nsize; - - // Check limit (only when allocating, not freeing) - if (nsize > 0 && new_usage > sandbox->m_limits.memory_bytes) { - // Allocation would exceed limit - return nullptr to signal failure - // Lua will raise a memory error - return nullptr; - } - - // Update tracking - sandbox->m_memory_used = new_usage; - - // Free operation - if (nsize == 0) { - free(ptr); - return nullptr; - } - - // Alloc or realloc - return realloc(ptr, nsize); -} - -//============================================================================= -// INSTRUCTION HOOK -//============================================================================= - -void LuaSandbox::InstructionHook(lua_State* L, lua_Debug* ar) { - (void)ar; // Unused - - // Get sandbox pointer from registry - lua_getfield(L, LUA_REGISTRYINDEX, "__mosis_sandbox"); - auto* sandbox = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - if (!sandbox) return; - - // Increment by hook interval (called every 1000 instructions) - sandbox->m_instructions_used += 1000; - - // Check limit - if (sandbox->m_instructions_used > sandbox->m_limits.instructions_per_call) { - luaL_error(L, "instruction limit exceeded"); - } -} - -//============================================================================= -// SAFE PRINT -//============================================================================= - -int LuaSandbox::SafePrint(lua_State* L) { - int n = lua_gettop(L); // number of arguments - lua_getglobal(L, "tostring"); - - for (int i = 1; i <= n; i++) { - if (i > 1) std::cout << "\t"; - - lua_pushvalue(L, -1); // push tostring - lua_pushvalue(L, i); // push argument - lua_call(L, 1, 1); // call tostring - - const char* s = lua_tostring(L, -1); - if (s) { - std::cout << s; - } - lua_pop(L, 1); - } - std::cout << std::endl; - - return 0; -} - -//============================================================================= -// CONSTRUCTOR / DESTRUCTOR -//============================================================================= - -LuaSandbox::LuaSandbox(const SandboxContext& context, const SandboxLimits& limits) - : m_context(context), m_limits(limits) { - - // Create Lua state with custom allocator - m_L = lua_newstate(SandboxAlloc, this); - if (!m_L) { - m_last_error = "Failed to create Lua state"; - return; - } - - // Store sandbox pointer in registry for hooks to access - lua_pushlightuserdata(m_L, this); - lua_setfield(m_L, LUA_REGISTRYINDEX, "__mosis_sandbox"); - - // Setup the sandbox - SetupSandbox(); -} - -LuaSandbox::~LuaSandbox() { - if (m_L) { - lua_close(m_L); - m_L = nullptr; - } -} - -//============================================================================= -// SETUP -//============================================================================= - -void LuaSandbox::SetupSandbox() { - // Open safe standard libraries - luaL_openlibs(m_L); - - // Remove dangerous globals FIRST - RemoveDangerousGlobals(); - - // Setup safe replacements - SetupSafeGlobals(); - - // Protect metatables - ProtectBuiltinTables(); - - // Setup instruction hook for CPU limiting - SetupInstructionHook(); -} - -void LuaSandbox::RemoveDangerousGlobals() { - // List of dangerous globals to remove - const char* dangerous_globals[] = { - // Code execution from files/strings - "dofile", - "loadfile", - "load", - "loadstring", // Lua 5.1 compat - - // Raw access (bypasses metatables) - "rawget", - "rawset", - "rawequal", - "rawlen", - - // Metatable manipulation - // Note: We keep getmetatable but protect the actual metatables - // setmetatable is removed to prevent modifications - "setmetatable", - - // GC manipulation - "collectgarbage", - - // Dangerous libraries - "os", - "io", - "debug", - "package", - - // LuaJIT / FFI (if present) - "ffi", - "jit", - "newproxy", - - // Module system (we'll add safe version later) - "require", - - nullptr - }; - - for (const char** p = dangerous_globals; *p; ++p) { - lua_pushnil(m_L); - lua_setglobal(m_L, *p); - } - - // Remove string.dump (can create bytecode from functions) - lua_getglobal(m_L, "string"); - if (lua_istable(m_L, -1)) { - lua_pushnil(m_L); - lua_setfield(m_L, -2, "dump"); - } - lua_pop(m_L, 1); -} - -void LuaSandbox::SetupSafeGlobals() { - // Replace print with safe version - lua_pushcfunction(m_L, SafePrint); - lua_setglobal(m_L, "print"); - - // Setup safe require if app_path is set - if (!m_context.app_path.empty()) { - SetupSafeRequire(); - } -} - -//============================================================================= -// SAFE REQUIRE -//============================================================================= - -// Registry key for loaded modules cache -static const char* LOADED_KEY = "mosis.loaded_modules"; - -// Validate module name for require() - alphanumeric, underscore, dots only -static bool IsValidModuleName(const std::string& name) { - if (name.empty()) return false; - - for (size_t i = 0; i < name.length(); i++) { - char c = name[i]; - if (std::isalnum(static_cast(c))) continue; - if (c == '_') continue; - if (c == '.') { - if (i == 0 || i == name.length() - 1) return false; - if (i > 0 && name[i-1] == '.') return false; - continue; - } - return false; - } - - if (name.find("..") != std::string::npos) return false; - return true; -} - -// Convert module name to path (e.g., "ui.button" -> "scripts/ui/button.lua") -static std::string ModuleToPath(const std::string& module_name) { - std::string path = module_name; - std::replace(path.begin(), path.end(), '.', '/'); - return "scripts/" + path + ".lua"; -} - -int LuaSandbox::SafeRequire(lua_State* L) { - // Get module name - const char* module_name = luaL_checkstring(L, 1); - - // Validate module name - if (!IsValidModuleName(module_name)) { - return luaL_error(L, "invalid module name: %s", module_name); - } - - // Check cache first - lua_getfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - if (lua_istable(L, -1)) { - lua_getfield(L, -1, module_name); - if (!lua_isnil(L, -1)) { - return 1; // Return cached module - } - lua_pop(L, 1); - } - lua_pop(L, 1); - - // Get sandbox pointer from registry - lua_getfield(L, LUA_REGISTRYINDEX, "__mosis_sandbox"); - auto* sandbox = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - if (!sandbox) { - return luaL_error(L, "require not properly initialized"); - } - - // Build full path - std::string relative_path = ModuleToPath(module_name); - std::string full_path = sandbox->m_context.app_path; - if (!full_path.empty() && full_path.back() != '/' && full_path.back() != '\\') { - full_path += '/'; - } - full_path += relative_path; - - // Read the file - std::ifstream file(full_path); - if (!file.is_open()) { - return luaL_error(L, "module '%s' not found at '%s'", module_name, full_path.c_str()); - } - - std::stringstream buffer; - buffer << file.rdbuf(); - std::string source = buffer.str(); - file.close(); - - // Load as text only (no bytecode) - std::string chunk_name = "@" + std::string(module_name); - int status = luaL_loadbufferx(L, source.c_str(), source.size(), - chunk_name.c_str(), "t"); - - if (status != LUA_OK) { - return lua_error(L); - } - - // Execute the chunk - lua_call(L, 0, 1); - - // If chunk returned nil, use true as the module value - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - lua_pushboolean(L, 1); - } - - // Cache the result - lua_getfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - } - lua_pushvalue(L, -2); - lua_setfield(L, -2, module_name); - lua_pop(L, 1); - - return 1; -} - -void LuaSandbox::SetupSafeRequire() { - // Create loaded modules cache - lua_newtable(m_L); - lua_setfield(m_L, LUA_REGISTRYINDEX, LOADED_KEY); - - // Register require function - lua_pushcfunction(m_L, SafeRequire); - lua_setglobal(m_L, "require"); -} - -void LuaSandbox::ProtectBuiltinTables() { - // Protect string metatable - // When someone calls getmetatable(""), they get "string" instead of the real metatable - lua_pushstring(m_L, ""); - if (lua_getmetatable(m_L, -1)) { - lua_pushstring(m_L, "string"); - lua_setfield(m_L, -2, "__metatable"); - lua_pop(m_L, 1); // pop metatable - } - lua_pop(m_L, 1); // pop string - - // Freeze _G using a proxy pattern - // This is needed because __newindex only fires for NEW keys, not existing ones - // We create: empty_proxy -> metatable { __index = real_G, __newindex = error } - - // Get the current _G (with all our safe functions) - lua_pushglobaltable(m_L); // stack: real_G - - // Create a new empty table to be the proxy - lua_newtable(m_L); // stack: real_G, proxy - - // Create metatable for proxy - lua_newtable(m_L); // stack: real_G, proxy, mt - - // __metatable - prevent access to real metatable - lua_pushstring(m_L, "globals"); - lua_setfield(m_L, -2, "__metatable"); - - // __index - read from real_G - lua_pushvalue(m_L, -3); // push real_G - lua_setfield(m_L, -2, "__index"); - - // __newindex - block all writes - lua_pushcfunction(m_L, [](lua_State* L) -> int { - const char* key = lua_tostring(L, 2); - return luaL_error(L, "cannot modify global '%s'", key ? key : "(unknown)"); - }); - lua_setfield(m_L, -2, "__newindex"); - - // Set metatable on proxy: setmetatable(proxy, mt) - lua_setmetatable(m_L, -2); // stack: real_G, proxy - - // Now we need to replace _G with proxy - // In Lua 5.2+, we use lua_rawseti on the registry - lua_pushvalue(m_L, -1); // stack: real_G, proxy, proxy - lua_rawseti(m_L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); // stack: real_G, proxy - - // Also update _G variable in real_G to point to proxy - // This is critical: when code does _G.foo = bar, it accesses _G variable - lua_setfield(m_L, -2, "_G"); // stack: real_G (sets real_G["_G"] = proxy) - - lua_pop(m_L, 1); // pop real_G -} - -void LuaSandbox::SetupInstructionHook() { - // Set hook to fire every 1000 VM instructions - lua_sethook(m_L, InstructionHook, LUA_MASKCOUNT, 1000); -} - -//============================================================================= -// LOAD AND EXECUTE -//============================================================================= - -bool LuaSandbox::LoadString(const std::string& code, const std::string& chunk_name) { - if (!m_L) { - m_last_error = "Lua state not initialized"; - return false; - } - - // Reset instruction count for this execution - ResetInstructionCount(); - - // Load as TEXT ONLY - "t" mode rejects bytecode (starts with \x1bLua) - int result = luaL_loadbufferx(m_L, code.c_str(), code.size(), - chunk_name.c_str(), "t"); - - if (result != LUA_OK) { - m_last_error = lua_tostring(m_L, -1); - lua_pop(m_L, 1); - return false; - } - - // Execute the loaded chunk - result = lua_pcall(m_L, 0, 0, 0); - if (result != LUA_OK) { - m_last_error = lua_tostring(m_L, -1); - lua_pop(m_L, 1); - return false; - } - - m_last_error.clear(); - return true; -} - -bool LuaSandbox::LoadFile(const std::string& path) { - // Read file contents - std::ifstream f(path); - if (!f) { - m_last_error = "Cannot open file: " + path; - return false; - } - - std::stringstream ss; - ss << f.rdbuf(); - std::string code = ss.str(); - - // Load as string - return LoadString(code, "@" + path); -} - -void LuaSandbox::ResetInstructionCount() { - m_instructions_used = 0; -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/lua_sandbox.h b/src/main/cpp/sandbox/lua_sandbox.h deleted file mode 100644 index 3f16da1..0000000 --- a/src/main/cpp/sandbox/lua_sandbox.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include -#include -#include - -// Forward declare lua_State to avoid including lua.h in header -struct lua_State; -struct lua_Debug; - -namespace mosis { - -// Resource limits for sandbox -struct SandboxLimits { - size_t memory_bytes = 16 * 1024 * 1024; // 16 MB default - size_t max_string_size = 1 * 1024 * 1024; // 1 MB max string - size_t max_table_entries = 100000; // Prevent hash DoS - uint64_t instructions_per_call = 1000000; // ~10ms execution - int stack_depth = 200; // Recursion limit -}; - -// Context for sandbox (app identity, permissions, etc.) -struct SandboxContext { - std::string app_id; - std::string app_path; - std::vector permissions; - bool is_system_app = false; -}; - -// Isolated Lua execution environment -class LuaSandbox { -public: - explicit LuaSandbox(const SandboxContext& context, - const SandboxLimits& limits = {}); - ~LuaSandbox(); - - // Non-copyable, non-movable - LuaSandbox(const LuaSandbox&) = delete; - LuaSandbox& operator=(const LuaSandbox&) = delete; - LuaSandbox(LuaSandbox&&) = delete; - LuaSandbox& operator=(LuaSandbox&&) = delete; - - // Load and execute Lua code (text only, bytecode rejected) - bool LoadString(const std::string& code, const std::string& chunk_name = "chunk"); - bool LoadFile(const std::string& path); - - // State access - lua_State* GetState() const { return m_L; } - const std::string& GetLastError() const { return m_last_error; } - - // Resource usage - size_t GetMemoryUsed() const { return m_memory_used; } - uint64_t GetInstructionsUsed() const { return m_instructions_used; } - - // Context access - const SandboxContext& GetContext() const { return m_context; } - const SandboxLimits& GetLimits() const { return m_limits; } - const std::string& app_id() const { return m_context.app_id; } - - // Reset instruction counter (call before each event handler) - void ResetInstructionCount(); - - // Check if sandbox is in valid state - bool IsValid() const { return m_L != nullptr; } - -private: - // Setup functions - void SetupSandbox(); - void RemoveDangerousGlobals(); - void ProtectBuiltinTables(); - void SetupInstructionHook(); - void SetupSafeGlobals(); - void SetupSafeRequire(); - - // Allocator callback (static for C compatibility) - static void* SandboxAlloc(void* ud, void* ptr, size_t osize, size_t nsize); - - // Instruction hook callback (static for C compatibility) - static void InstructionHook(lua_State* L, lua_Debug* ar); - - // Safe print function - static int SafePrint(lua_State* L); - - // Safe require function - static int SafeRequire(lua_State* L); - - lua_State* m_L = nullptr; - SandboxContext m_context; - SandboxLimits m_limits; - - size_t m_memory_used = 0; - uint64_t m_instructions_used = 0; - std::string m_last_error; -}; - -} // namespace mosis - -// Convenience alias for tests -using SandboxContext = mosis::SandboxContext; -using SandboxLimits = mosis::SandboxLimits; -using LuaSandbox = mosis::LuaSandbox; diff --git a/src/main/cpp/sandbox/microphone_interface.cpp b/src/main/cpp/sandbox/microphone_interface.cpp index 68b2a36..c6fbe0e 100644 --- a/src/main/cpp/sandbox/microphone_interface.cpp +++ b/src/main/cpp/sandbox/microphone_interface.cpp @@ -1,5 +1,5 @@ #include "microphone_interface.h" -#include "permission_gate.h" +#include #include namespace mosis { diff --git a/src/main/cpp/sandbox/network_manager.cpp b/src/main/cpp/sandbox/network_manager.cpp deleted file mode 100644 index dbe3957..0000000 --- a/src/main/cpp/sandbox/network_manager.cpp +++ /dev/null @@ -1,249 +0,0 @@ -#include "network_manager.h" -#include -#include - -namespace mosis { - -NetworkManager::NetworkManager(const std::string& app_id, const NetworkLimits& limits) - : m_app_id(app_id) - , m_limits(limits) - , m_mock_mode(true) -{ -} - -NetworkManager::~NetworkManager() { -} - -void NetworkManager::SetAllowedDomains(const std::vector& domains) { - m_validator.SetAllowedDomains(domains); -} - -void NetworkManager::ClearDomainRestrictions() { - m_validator.ClearDomainRestrictions(); -} - -bool NetworkManager::ValidateRequest(const HttpRequest& request, std::string& error) { - // Validate URL - auto parsed = m_validator.Validate(request.url, error); - if (!parsed) { - return false; - } - - // Validate method - std::string method = request.method; - std::transform(method.begin(), method.end(), method.begin(), - [](unsigned char c) { return std::toupper(c); }); - - static const std::vector allowed_methods = { - "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS" - }; - - bool method_valid = false; - for (const auto& m : allowed_methods) { - if (method == m) { - method_valid = true; - break; - } - } - if (!method_valid) { - error = "Invalid HTTP method: " + request.method; - return false; - } - - // Validate request body size - if (request.body.size() > m_limits.max_request_body) { - error = "Request body too large: " + std::to_string(request.body.size()) + - " bytes (max " + std::to_string(m_limits.max_request_body) + ")"; - return false; - } - - // Validate timeout - if (request.timeout_ms > m_limits.max_timeout_ms) { - error = "Timeout too large: " + std::to_string(request.timeout_ms) + - "ms (max " + std::to_string(m_limits.max_timeout_ms) + "ms)"; - return false; - } - - // Check concurrent request limit - if (m_active_requests.load() >= m_limits.max_concurrent_requests) { - error = "Too many concurrent requests (max " + - std::to_string(m_limits.max_concurrent_requests) + ")"; - return false; - } - - return true; -} - -HttpResponse NetworkManager::Request(const HttpRequest& request, std::string& error) { - HttpResponse response; - - // Validate the request - if (!ValidateRequest(request, error)) { - response.error = error; - return response; - } - - // In mock mode, we don't actually make network calls - // This is for testing the validation logic - if (m_mock_mode) { - error = "Network requests disabled in mock mode"; - response.error = error; - return response; - } - - // Track active requests - m_active_requests++; - - // In a real implementation, we would make the HTTP request here - // For now, just return an error indicating no network implementation - error = "Network requests not implemented on this platform"; - response.error = error; - - m_active_requests--; - return response; -} - -int NetworkManager::GetActiveRequestCount() const { - return m_active_requests.load(); -} - -// Lua API implementation - -// Get NetworkManager from upvalue -static NetworkManager* GetManager(lua_State* L) { - return static_cast(lua_touserdata(L, lua_upvalueindex(1))); -} - -// network.request(options) -> response, error -static int L_network_request(lua_State* L) { - NetworkManager* manager = GetManager(L); - if (!manager) { - lua_pushnil(L); - lua_pushstring(L, "NetworkManager not available"); - return 2; - } - - // Expect table argument - luaL_checktype(L, 1, LUA_TTABLE); - - HttpRequest request; - - // Get URL (required) - lua_getfield(L, 1, "url"); - if (!lua_isstring(L, -1)) { - lua_pushnil(L); - lua_pushstring(L, "url is required and must be a string"); - return 2; - } - request.url = lua_tostring(L, -1); - lua_pop(L, 1); - - // Get method (optional, default GET) - lua_getfield(L, 1, "method"); - if (lua_isstring(L, -1)) { - request.method = lua_tostring(L, -1); - } - lua_pop(L, 1); - - // Get headers (optional) - lua_getfield(L, 1, "headers"); - if (lua_istable(L, -1)) { - lua_pushnil(L); - while (lua_next(L, -2) != 0) { - if (lua_isstring(L, -2) && lua_isstring(L, -1)) { - request.headers[lua_tostring(L, -2)] = lua_tostring(L, -1); - } - lua_pop(L, 1); - } - } - lua_pop(L, 1); - - // Get body (optional) - lua_getfield(L, 1, "body"); - if (lua_isstring(L, -1)) { - size_t len; - const char* body = lua_tolstring(L, -1, &len); - request.body = std::string(body, len); - } - lua_pop(L, 1); - - // Get timeout (optional) - lua_getfield(L, 1, "timeout"); - if (lua_isnumber(L, -1)) { - request.timeout_ms = static_cast(lua_tointeger(L, -1)); - } - lua_pop(L, 1); - - // Make request - std::string error; - HttpResponse response = manager->Request(request, error); - - if (!error.empty()) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - // Return response as table - lua_newtable(L); - - lua_pushinteger(L, response.status_code); - lua_setfield(L, -2, "status"); - - lua_pushstring(L, response.body.c_str()); - lua_setfield(L, -2, "body"); - - // Headers table - lua_newtable(L); - for (const auto& [key, value] : response.headers) { - lua_pushstring(L, value.c_str()); - lua_setfield(L, -2, key.c_str()); - } - lua_setfield(L, -2, "headers"); - - if (!response.error.empty()) { - lua_pushstring(L, response.error.c_str()); - lua_setfield(L, -2, "error"); - } - - return 1; // Return response table -} - -// Helper to set a global in the real _G (bypassing any proxy) -static void SetGlobalInRealG(lua_State* L, const char* name) { - // Stack: value to set as global - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - - // Check if it's a proxy with __index pointing to real _G - if (lua_getmetatable(L, -1)) { - lua_getfield(L, -1, "__index"); - if (lua_istable(L, -1)) { - // This is the real _G, set our value there - lua_pushvalue(L, -4); // Push the value - lua_setfield(L, -2, name); - lua_pop(L, 4); // Pop __index, metatable, proxy, (value already consumed) - return; - } - lua_pop(L, 2); // Pop __index and metatable - } - - // No proxy, set directly - lua_pushvalue(L, -2); // Push the value - lua_setfield(L, -2, name); - lua_pop(L, 2); // Pop globals table and original value -} - -void RegisterNetworkAPI(lua_State* L, NetworkManager* manager) { - // Create network table - lua_newtable(L); - - // Add request function with manager as upvalue - lua_pushlightuserdata(L, manager); - lua_pushcclosure(L, L_network_request, 1); - lua_setfield(L, -2, "request"); - - // Set as global - SetGlobalInRealG(L, "network"); -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/network_manager.h b/src/main/cpp/sandbox/network_manager.h deleted file mode 100644 index 1f547db..0000000 --- a/src/main/cpp/sandbox/network_manager.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#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 - // In test mode, validates but doesn't actually make network calls - HttpResponse Request(const HttpRequest& request, std::string& error); - - // Stats - int GetActiveRequestCount() const; - - // Access validator for testing - HttpValidator& GetValidator() { return m_validator; } - const HttpValidator& GetValidator() const { return m_validator; } - - // For testing: set mock mode (no actual network calls) - void SetMockMode(bool enabled) { m_mock_mode = enabled; } - bool IsMockMode() const { return m_mock_mode; } - -private: - std::string m_app_id; - NetworkLimits m_limits; - HttpValidator m_validator; - std::atomic m_active_requests{0}; - std::mutex m_mutex; - bool m_mock_mode = true; // Default to mock mode for tests - - // Validate request before sending - bool ValidateRequest(const HttpRequest& request, std::string& error); -}; - -// Register network.* APIs as globals -void RegisterNetworkAPI(lua_State* L, NetworkManager* manager); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/path_sandbox.cpp b/src/main/cpp/sandbox/path_sandbox.cpp deleted file mode 100644 index 562c33a..0000000 --- a/src/main/cpp/sandbox/path_sandbox.cpp +++ /dev/null @@ -1,344 +0,0 @@ -#include "path_sandbox.h" - -#include -#include -#include -#include -#include - -namespace mosis { - -//============================================================================= -// CONSTRUCTOR -//============================================================================= - -PathSandbox::PathSandbox(const std::string& app_path) - : m_app_path(app_path) -{ - // Normalize the app path - if (!m_app_path.empty()) { - // Ensure trailing separator for prefix matching - if (m_app_path.back() != '/' && m_app_path.back() != '\\') { - m_app_path += '/'; - } - // Normalize separators to forward slash - std::replace(m_app_path.begin(), m_app_path.end(), '\\', '/'); - } -} - -//============================================================================= -// PATH VALIDATION -//============================================================================= - -bool PathSandbox::ContainsTraversal(const std::string& path) { - std::string normalized = NormalizePath(path); - - // Check for .. anywhere in the path - size_t pos = 0; - while ((pos = normalized.find("..", pos)) != std::string::npos) { - // Make sure it's actually a parent directory reference, not part of a filename - bool at_start = (pos == 0); - bool before_is_sep = (pos > 0 && (normalized[pos-1] == '/' || normalized[pos-1] == '\\')); - - size_t after_pos = pos + 2; - bool at_end = (after_pos >= normalized.size()); - bool after_is_sep = (!at_end && (normalized[after_pos] == '/' || normalized[after_pos] == '\\')); - - if ((at_start || before_is_sep) && (at_end || after_is_sep)) { - return true; - } - pos++; - } - - return false; -} - -bool PathSandbox::IsAbsolutePath(const std::string& path) { - if (path.empty()) return false; - - // Unix absolute path - if (path[0] == '/') return true; - - // Windows absolute path (C:\ or C:/) - if (path.length() >= 2) { - char first = path[0]; - if (std::isalpha(static_cast(first)) && path[1] == ':') { - return true; - } - } - - // UNC path (\\server\share or //server/share) - if (path.length() >= 2) { - if ((path[0] == '\\' && path[1] == '\\') || - (path[0] == '/' && path[1] == '/')) { - return true; - } - } - - return false; -} - -std::string PathSandbox::NormalizePath(const std::string& path) { - std::string result = path; - - // Convert backslashes to forward slashes - std::replace(result.begin(), result.end(), '\\', '/'); - - // Remove leading ./ - while (result.length() >= 2 && result[0] == '.' && result[1] == '/') { - result = result.substr(2); - } - - // Remove duplicate slashes - std::string cleaned; - bool last_was_slash = false; - for (char c : result) { - if (c == '/') { - if (!last_was_slash) { - cleaned += c; - } - last_was_slash = true; - } else { - cleaned += c; - last_was_slash = false; - } - } - - return cleaned; -} - -bool PathSandbox::ValidatePath(const std::string& path, std::string& out_canonical) { - // Reject empty paths - if (path.empty()) { - return false; - } - - // Reject absolute paths - if (IsAbsolutePath(path)) { - return false; - } - - // Reject traversal attempts - if (ContainsTraversal(path)) { - return false; - } - - // Normalize and resolve the path - std::string normalized = NormalizePath(path); - std::string resolved = ResolvePath(normalized); - - // Use filesystem to get canonical path (resolves any remaining .) - try { - std::filesystem::path fs_path(resolved); - - // If the file exists, use canonical path for strict checking - if (std::filesystem::exists(fs_path)) { - std::filesystem::path canonical = std::filesystem::canonical(fs_path); - std::string canonical_str = canonical.string(); - std::replace(canonical_str.begin(), canonical_str.end(), '\\', '/'); - - // Verify the canonical path is still within app_path - std::string app_canonical = std::filesystem::canonical( - std::filesystem::path(m_app_path)).string(); - std::replace(app_canonical.begin(), app_canonical.end(), '\\', '/'); - if (!app_canonical.empty() && app_canonical.back() != '/') { - app_canonical += '/'; - } - - if (canonical_str.rfind(app_canonical, 0) != 0) { - return false; // Path escaped sandbox via symlink - } - - out_canonical = canonical_str; - } else { - // File doesn't exist, just use the resolved path - out_canonical = resolved; - } - } catch (const std::filesystem::filesystem_error&) { - // Filesystem error, use the resolved path as-is - out_canonical = resolved; - } - - return true; -} - -std::string PathSandbox::ResolvePath(const std::string& relative_path) { - std::string normalized = NormalizePath(relative_path); - - // Combine with app path - std::string result = m_app_path + normalized; - - return result; -} - -//============================================================================= -// MODULE NAME VALIDATION -//============================================================================= - -bool PathSandbox::IsValidModuleName(const std::string& name) { - if (name.empty()) { - return false; - } - - // Check each character - for (size_t i = 0; i < name.length(); i++) { - char c = name[i]; - - // Allow alphanumeric - if (std::isalnum(static_cast(c))) { - continue; - } - - // Allow underscore - if (c == '_') { - continue; - } - - // Allow dot for submodules, but not at start/end or consecutive - if (c == '.') { - if (i == 0 || i == name.length() - 1) { - return false; // Dot at start or end - } - if (i > 0 && name[i-1] == '.') { - return false; // Consecutive dots - } - continue; - } - - // Any other character is invalid - return false; - } - - // Reject names that look like traversal - if (name.find("..") != std::string::npos) { - return false; - } - - return true; -} - -std::string PathSandbox::ModuleToPath(const std::string& module_name) { - // Convert dots to path separators - std::string path = module_name; - std::replace(path.begin(), path.end(), '.', '/'); - - // Add scripts/ prefix and .lua suffix - return "scripts/" + path + ".lua"; -} - -//============================================================================= -// SAFE REQUIRE -//============================================================================= - -// Registry key for PathSandbox pointer -static const char* SANDBOX_KEY = "mosis.path_sandbox"; - -// Registry key for loaded modules cache -static const char* LOADED_KEY = "mosis.loaded_modules"; - -int SafeRequire(lua_State* L) { - // Get module name - const char* module_name = luaL_checkstring(L, 1); - - // Validate module name - if (!PathSandbox::IsValidModuleName(module_name)) { - return luaL_error(L, "invalid module name: %s", module_name); - } - - // Check cache first - lua_getfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - if (lua_istable(L, -1)) { - lua_getfield(L, -1, module_name); - if (!lua_isnil(L, -1)) { - // Module already loaded, return cached value - return 1; - } - lua_pop(L, 1); // Pop nil - } - lua_pop(L, 1); // Pop cache table (or nil if not exists) - - // Get PathSandbox from registry - lua_getfield(L, LUA_REGISTRYINDEX, SANDBOX_KEY); - if (!lua_islightuserdata(L, -1)) { - return luaL_error(L, "require not properly initialized"); - } - PathSandbox* sandbox = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - // Convert module name to path - std::string relative_path = PathSandbox::ModuleToPath(module_name); - - // Validate the path - std::string canonical; - if (!sandbox->ValidatePath(relative_path, canonical)) { - return luaL_error(L, "cannot load module '%s': path validation failed", module_name); - } - - // Read the file - std::ifstream file(canonical); - if (!file.is_open()) { - // Try with the resolved path directly (in case canonical check failed) - std::string resolved = sandbox->ResolvePath(relative_path); - file.open(resolved); - if (!file.is_open()) { - return luaL_error(L, "module '%s' not found", module_name); - } - } - - std::stringstream buffer; - buffer << file.rdbuf(); - std::string source = buffer.str(); - file.close(); - - // Load as text only (no bytecode) - std::string chunk_name = "@" + std::string(module_name); - int status = luaL_loadbufferx(L, source.c_str(), source.size(), - chunk_name.c_str(), "t"); - - if (status != LUA_OK) { - return lua_error(L); // Propagate error - } - - // Execute the chunk - lua_call(L, 0, 1); - - // If chunk returned nil, use true as the module value - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - lua_pushboolean(L, 1); - } - - // Cache the result - lua_getfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - if (!lua_istable(L, -1)) { - // Create cache table if it doesn't exist - lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_setfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - } - - // cache[module_name] = result - lua_pushvalue(L, -2); // Push the result - lua_setfield(L, -2, module_name); - lua_pop(L, 1); // Pop cache table - - // Return the module - return 1; -} - -void RegisterSafeRequire(lua_State* L, PathSandbox* sandbox) { - // Store PathSandbox pointer in registry - lua_pushlightuserdata(L, sandbox); - lua_setfield(L, LUA_REGISTRYINDEX, SANDBOX_KEY); - - // Create loaded modules cache - lua_newtable(L); - lua_setfield(L, LUA_REGISTRYINDEX, LOADED_KEY); - - // Register require function - lua_pushcfunction(L, SafeRequire); - lua_setglobal(L, "require"); -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/path_sandbox.h b/src/main/cpp/sandbox/path_sandbox.h deleted file mode 100644 index 3eb7d59..0000000 --- a/src/main/cpp/sandbox/path_sandbox.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include - -struct lua_State; - -namespace mosis { - -class PathSandbox { -public: - explicit PathSandbox(const std::string& app_path); - - // Validate a path is within the sandbox - // Returns true if valid, sets out_canonical to the resolved path - bool ValidatePath(const std::string& path, std::string& out_canonical); - - // Check if path contains traversal attempts (..) - static bool ContainsTraversal(const std::string& path); - - // Check if path is absolute - static bool IsAbsolutePath(const std::string& path); - - // Normalize path separators and remove redundant ./ components - static std::string NormalizePath(const std::string& path); - - // Validate module name for require() - alphanumeric, underscore, dots only - static bool IsValidModuleName(const std::string& name); - - // Convert module name to relative path (e.g., "ui.button" -> "scripts/ui/button.lua") - static std::string ModuleToPath(const std::string& module_name); - - // Get the app's base path - const std::string& GetAppPath() const { return m_app_path; } - - // Resolve a relative path to full path within sandbox - std::string ResolvePath(const std::string& relative_path); - -private: - std::string m_app_path; -}; - -// Safe require implementation for Lua -// Loads modules only from app_path/scripts/.lua -// Caches modules in registry -int SafeRequire(lua_State* L); - -// Register safe require as global "require" -// The PathSandbox pointer is stored in registry for use by SafeRequire -void RegisterSafeRequire(lua_State* L, PathSandbox* sandbox); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/permission_gate.cpp b/src/main/cpp/sandbox/permission_gate.cpp deleted file mode 100644 index 517031f..0000000 --- a/src/main/cpp/sandbox/permission_gate.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "permission_gate.h" -#include "lua_sandbox.h" - -#include -#include - -namespace mosis { - -//============================================================================= -// PERMISSION DATABASE -//============================================================================= - -static const std::unordered_map PERMISSIONS = { - // Normal permissions (auto-granted when declared) - {"internet", {PermissionCategory::Normal, "Access the internet"}}, - {"vibrate", {PermissionCategory::Normal, "Vibrate the device"}}, - {"wake_lock", {PermissionCategory::Normal, "Keep device awake"}}, - {"notifications", {PermissionCategory::Normal, "Show notifications"}}, - {"alarms", {PermissionCategory::Normal, "Set alarms"}}, - {"nfc", {PermissionCategory::Normal, "Use NFC"}}, - - // Dangerous permissions (require user consent) - {"camera", {PermissionCategory::Dangerous, "Access the camera"}}, - {"microphone", {PermissionCategory::Dangerous, "Record audio"}}, - {"location.fine", {PermissionCategory::Dangerous, "Access precise location"}}, - {"location.coarse", {PermissionCategory::Dangerous, "Access approximate location"}}, - {"contacts.read", {PermissionCategory::Dangerous, "Read contacts"}}, - {"contacts.write", {PermissionCategory::Dangerous, "Modify contacts"}}, - {"storage.external", {PermissionCategory::Dangerous, "Access external storage"}}, - {"storage.shared", {PermissionCategory::Dangerous, "Access shared storage"}}, - {"sensors.motion", {PermissionCategory::Dangerous, "Access motion sensors"}}, - {"bluetooth", {PermissionCategory::Dangerous, "Use Bluetooth"}}, - {"bluetooth.scan", {PermissionCategory::Dangerous, "Scan for Bluetooth devices"}}, - {"calendar.read", {PermissionCategory::Dangerous, "Read calendar"}}, - {"calendar.write", {PermissionCategory::Dangerous, "Modify calendar"}}, - {"phone.call", {PermissionCategory::Dangerous, "Make phone calls"}}, - {"phone.read_state", {PermissionCategory::Dangerous, "Read phone state"}}, - {"sms.read", {PermissionCategory::Dangerous, "Read SMS messages"}}, - {"sms.send", {PermissionCategory::Dangerous, "Send SMS messages"}}, - - // Signature permissions (system apps only) - {"system.settings", {PermissionCategory::Signature, "Modify system settings"}}, - {"system.install", {PermissionCategory::Signature, "Install apps"}}, - {"system.uninstall", {PermissionCategory::Signature, "Uninstall apps"}}, - {"system.admin", {PermissionCategory::Signature, "Device administrator"}}, - {"system.overlay", {PermissionCategory::Signature, "Display over other apps"}}, - {"system.wallpaper", {PermissionCategory::Signature, "Set wallpaper"}}, -}; - -//============================================================================= -// CONSTRUCTOR -//============================================================================= - -PermissionGate::PermissionGate(const SandboxContext& context) - : m_context(context) - , m_last_gesture(std::chrono::steady_clock::time_point::min()) -{ -} - -//============================================================================= -// PERMISSION INFO -//============================================================================= - -PermissionCategory PermissionGate::GetCategory(const std::string& permission) { - auto it = PERMISSIONS.find(permission); - if (it != PERMISSIONS.end()) { - return it->second.category; - } - // Unknown permissions default to Dangerous for safety - return PermissionCategory::Dangerous; -} - -const PermissionInfo* PermissionGate::GetPermissionInfo(const std::string& permission) { - auto it = PERMISSIONS.find(permission); - if (it != PERMISSIONS.end()) { - return &it->second; - } - return nullptr; -} - -//============================================================================= -// PERMISSION CHECKING -//============================================================================= - -bool PermissionGate::HasPermission(const std::string& permission) const { - auto category = GetCategory(permission); - - switch (category) { - case PermissionCategory::Normal: - return CheckNormalPermission(permission); - case PermissionCategory::Dangerous: - return CheckDangerousPermission(permission); - case PermissionCategory::Signature: - return CheckSignaturePermission(permission); - } - return false; -} - -bool PermissionGate::Check(lua_State* L, const std::string& permission) { - if (!HasPermission(permission)) { - luaL_error(L, "permission denied: %s", permission.c_str()); - return false; - } - return true; -} - -bool PermissionGate::IsDeclared(const std::string& permission) const { - const auto& declared = m_context.permissions; - return std::find(declared.begin(), declared.end(), permission) != declared.end(); -} - -bool PermissionGate::CheckNormalPermission(const std::string& permission) const { - // Normal permissions are auto-granted if declared in manifest - return IsDeclared(permission); -} - -bool PermissionGate::CheckDangerousPermission(const std::string& permission) const { - // Must be declared in manifest - if (!IsDeclared(permission)) { - return false; - } - - // System apps get dangerous permissions automatically - if (m_context.is_system_app) { - return true; - } - - // Regular apps need runtime grant - return m_runtime_grants.count(permission) > 0; -} - -bool PermissionGate::CheckSignaturePermission(const std::string& permission) const { - // Only system apps get signature permissions - if (!m_context.is_system_app) { - return false; - } - - // Must still be declared - return IsDeclared(permission); -} - -//============================================================================= -// USER GESTURE TRACKING -//============================================================================= - -void PermissionGate::RecordUserGesture() { - m_last_gesture = std::chrono::steady_clock::now(); -} - -bool PermissionGate::HasRecentUserGesture(int ms) const { - // If no gesture has been recorded, return false - if (m_last_gesture == std::chrono::steady_clock::time_point::min()) { - return false; - } - - auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast(now - m_last_gesture); - return elapsed.count() < ms; -} - -//============================================================================= -// RUNTIME GRANTS -//============================================================================= - -void PermissionGate::GrantPermission(const std::string& permission) { - // Can only grant dangerous permissions - auto category = GetCategory(permission); - if (category == PermissionCategory::Dangerous) { - m_runtime_grants.insert(permission); - } -} - -void PermissionGate::RevokePermission(const std::string& permission) { - m_runtime_grants.erase(permission); -} - -//============================================================================= -// QUERIES -//============================================================================= - -const std::vector& PermissionGate::GetDeclaredPermissions() const { - return m_context.permissions; -} - -std::vector PermissionGate::GetGrantedPermissions() const { - std::vector granted; - - for (const auto& perm : m_context.permissions) { - if (HasPermission(perm)) { - granted.push_back(perm); - } - } - - return granted; -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/permission_gate.h b/src/main/cpp/sandbox/permission_gate.h deleted file mode 100644 index 0160353..0000000 --- a/src/main/cpp/sandbox/permission_gate.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -struct lua_State; - -namespace mosis { - -struct SandboxContext; // Forward declaration - -enum class PermissionCategory { - Normal, // Auto-granted when declared (e.g., internet, vibrate) - Dangerous, // Requires user consent (e.g., camera, location) - Signature // System apps only (e.g., system.settings) -}; - -struct PermissionInfo { - PermissionCategory category; - std::string description; -}; - -class PermissionGate { -public: - explicit PermissionGate(const SandboxContext& context); - - // Check if app has permission (throws Lua error if not) - bool Check(lua_State* L, const std::string& permission); - - // Check without throwing (returns false if denied) - bool HasPermission(const std::string& permission) const; - - // Get permission category - static PermissionCategory GetCategory(const std::string& permission); - - // Get permission info (returns nullptr if unknown) - static const PermissionInfo* GetPermissionInfo(const std::string& permission); - - // User gesture tracking - void RecordUserGesture(); - bool HasRecentUserGesture(int ms = 5000) const; - - // Runtime permission grant (called after user consent) - void GrantPermission(const std::string& permission); - void RevokePermission(const std::string& permission); - - // Get all declared permissions - const std::vector& GetDeclaredPermissions() const; - - // Get all granted permissions - std::vector GetGrantedPermissions() const; - - // Check if permission is declared in manifest - bool IsDeclared(const std::string& permission) const; - -private: - const SandboxContext& m_context; - std::unordered_set m_runtime_grants; // Runtime-granted dangerous perms - std::chrono::steady_clock::time_point m_last_gesture; - - bool CheckNormalPermission(const std::string& permission) const; - bool CheckDangerousPermission(const std::string& permission) const; - bool CheckSignaturePermission(const std::string& permission) const; -}; - -} // namespace mosis - -// Convenience alias -using PermissionGate = mosis::PermissionGate; -using PermissionCategory = mosis::PermissionCategory; diff --git a/src/main/cpp/sandbox/rate_limiter.cpp b/src/main/cpp/sandbox/rate_limiter.cpp deleted file mode 100644 index 1eaf29a..0000000 --- a/src/main/cpp/sandbox/rate_limiter.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "rate_limiter.h" - -#include - -namespace mosis { - -//============================================================================= -// CONSTRUCTOR (with default limits) -//============================================================================= - -RateLimiter::RateLimiter() { - // Network operations - SetLimit("network.request", {10.0, 100.0}); // 10/sec, burst 100 - SetLimit("network.websocket", {2.0, 10.0}); // 2/sec, burst 10 - SetLimit("network.download", {5.0, 20.0}); // 5/sec, burst 20 - - // Storage operations - SetLimit("storage.read", {100.0, 500.0}); // 100/sec, burst 500 - SetLimit("storage.write", {20.0, 100.0}); // 20/sec, burst 100 - SetLimit("storage.delete", {10.0, 50.0}); // 10/sec, burst 50 - SetLimit("database.query", {50.0, 200.0}); // 50/sec, burst 200 - - // Hardware access - 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 - SetLimit("sensor.read", {60.0, 60.0}); // 60 Hz max - - // Timers - SetLimit("timer.create", {10.0, 100.0}); // 10/sec, burst 100 - - // Crypto - SetLimit("crypto.random", {100.0, 1000.0}); // 100/sec, burst 1000 - SetLimit("crypto.hash", {100.0, 1000.0}); // 100/sec, burst 1000 -} - -//============================================================================= -// CONFIGURATION -//============================================================================= - -void RateLimiter::SetLimit(const std::string& operation, const RateLimitConfig& config) { - std::lock_guard lock(m_mutex); - m_configs[operation] = config; -} - -const RateLimitConfig* RateLimiter::GetLimit(const std::string& operation) const { - std::lock_guard lock(m_mutex); - auto it = m_configs.find(operation); - if (it != m_configs.end()) { - return &it->second; - } - return nullptr; -} - -//============================================================================= -// CHECKING -//============================================================================= - -bool RateLimiter::Check(const std::string& app_id, const std::string& operation) { - std::lock_guard lock(m_mutex); - - // Find config - auto config_it = m_configs.find(operation); - if (config_it == m_configs.end()) { - // No limit configured, allow by default - return true; - } - - const auto& config = config_it->second; - auto& bucket = GetBucket(app_id, operation); - - // Refill based on elapsed time - Refill(bucket, config); - - // Check if we have a token - if (bucket.tokens >= 1.0) { - bucket.tokens -= 1.0; - return true; - } - - return false; -} - -bool RateLimiter::CanProceed(const std::string& app_id, const std::string& operation) const { - std::lock_guard lock(m_mutex); - - // Find config - auto config_it = m_configs.find(operation); - if (config_it == m_configs.end()) { - return true; // No limit - } - - const auto& config = config_it->second; - std::string key = MakeKey(app_id, operation); - - auto bucket_it = m_buckets.find(key); - if (bucket_it == m_buckets.end()) { - return true; // New bucket would have full tokens - } - - // Make a copy to check without modifying - Bucket bucket = bucket_it->second; - Refill(bucket, config); - - return bucket.tokens >= 1.0; -} - -double RateLimiter::GetTokens(const std::string& app_id, const std::string& operation) const { - std::lock_guard lock(m_mutex); - - std::string key = MakeKey(app_id, operation); - auto bucket_it = m_buckets.find(key); - - if (bucket_it == m_buckets.end()) { - // Check if there's a config - auto config_it = m_configs.find(operation); - if (config_it != m_configs.end()) { - return config_it->second.max_tokens; // Would start with full - } - return 0.0; - } - - // Find config to refill - auto config_it = m_configs.find(operation); - if (config_it == m_configs.end()) { - return bucket_it->second.tokens; - } - - // Make a copy to check without modifying - Bucket bucket = bucket_it->second; - Refill(bucket, config_it->second); - - return bucket.tokens; -} - -//============================================================================= -// RESET -//============================================================================= - -void RateLimiter::ResetApp(const std::string& app_id) { - std::lock_guard lock(m_mutex); - - // Find and remove all buckets for this app - std::string prefix = app_id + ":"; - for (auto it = m_buckets.begin(); it != m_buckets.end(); ) { - if (it->first.rfind(prefix, 0) == 0) { // starts with app_id: - it = m_buckets.erase(it); - } else { - ++it; - } - } -} - -void RateLimiter::ClearAll() { - std::lock_guard lock(m_mutex); - m_buckets.clear(); -} - -//============================================================================= -// INTERNAL -//============================================================================= - -void RateLimiter::Refill(Bucket& bucket, const RateLimitConfig& config) const { - auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration(now - bucket.last_refill); - - // Add tokens based on elapsed time - double new_tokens = bucket.tokens + (elapsed.count() * config.tokens_per_second); - - // Cap at max - bucket.tokens = std::min(new_tokens, config.max_tokens); - bucket.last_refill = now; -} - -RateLimiter::Bucket& RateLimiter::GetBucket(const std::string& app_id, - const std::string& operation) { - std::string key = MakeKey(app_id, operation); - - auto it = m_buckets.find(key); - if (it != m_buckets.end()) { - return it->second; - } - - // Create new bucket with full tokens - auto config_it = m_configs.find(operation); - double initial = (config_it != m_configs.end()) ? config_it->second.max_tokens : 1.0; - - m_buckets[key] = Bucket{ - .tokens = initial, - .last_refill = std::chrono::steady_clock::now() - }; - - return m_buckets[key]; -} - -std::string RateLimiter::MakeKey(const std::string& app_id, const std::string& operation) { - return app_id + ":" + operation; -} - -//============================================================================= -// GLOBAL INSTANCE -//============================================================================= - -RateLimiter& GetRateLimiter() { - static RateLimiter instance; - return instance; -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/rate_limiter.h b/src/main/cpp/sandbox/rate_limiter.h deleted file mode 100644 index a068e0b..0000000 --- a/src/main/cpp/sandbox/rate_limiter.h +++ /dev/null @@ -1,68 +0,0 @@ -#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 token - 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 config for an operation - const RateLimitConfig* GetLimit(const std::string& operation) const; - - // Get current token count for app+operation - double GetTokens(const std::string& app_id, const std::string& operation) const; - - // Reset all buckets for an app (e.g., on app restart) - void ResetApp(const std::string& app_id); - - // Clear all buckets - void ClearAll(); - -private: - struct Bucket { - double tokens; - std::chrono::steady_clock::time_point last_refill; - }; - - // Refill bucket based on elapsed time - void Refill(Bucket& bucket, const RateLimitConfig& config) const; - - // Get or create bucket for app+operation - Bucket& GetBucket(const std::string& app_id, const std::string& operation); - - // Get bucket key - static std::string MakeKey(const std::string& app_id, const std::string& operation); - - mutable std::mutex m_mutex; - std::unordered_map m_configs; - mutable std::unordered_map m_buckets; -}; - -// Global rate limiter (singleton) -RateLimiter& GetRateLimiter(); - -} // namespace mosis - -// Convenience alias -using RateLimiter = mosis::RateLimiter; -using RateLimitConfig = mosis::RateLimitConfig; diff --git a/src/main/cpp/sandbox/sandbox_manager.h b/src/main/cpp/sandbox/sandbox_manager.h index 267ee66..9679160 100644 --- a/src/main/cpp/sandbox/sandbox_manager.h +++ b/src/main/cpp/sandbox/sandbox_manager.h @@ -2,14 +2,19 @@ // Milestone 20: Kernel Integration #pragma once -#include "lua_sandbox.h" -#include "permission_gate.h" -#include "audit_log.h" -#include "rate_limiter.h" -#include "timer_manager.h" -#include "virtual_fs.h" -#include "database_manager.h" -#include "network_manager.h" +// Core library headers (shared across platforms) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Android-specific hardware interfaces #include "websocket_manager.h" #include "camera_interface.h" #include "microphone_interface.h" @@ -19,8 +24,6 @@ #include "bluetooth_interface.h" #include "contacts_interface.h" #include "message_bus.h" -#include "json_api.h" -#include "crypto_api.h" #include #include diff --git a/src/main/cpp/sandbox/timer_manager.cpp b/src/main/cpp/sandbox/timer_manager.cpp deleted file mode 100644 index a581bc4..0000000 --- a/src/main/cpp/sandbox/timer_manager.cpp +++ /dev/null @@ -1,440 +0,0 @@ -#include "timer_manager.h" - -#include -#include - -namespace mosis { - -//============================================================================= -// CONSTRUCTOR / DESTRUCTOR -//============================================================================= - -TimerManager::TimerManager() = default; - -TimerManager::~TimerManager() { - std::lock_guard lock(m_mutex); - - // Release all Lua callback references - for (auto& timer : m_timers) { - if (timer.callback_ref != LUA_NOREF && timer.L) { - luaL_unref(timer.L, LUA_REGISTRYINDEX, timer.callback_ref); - } - } - m_timers.clear(); -} - -//============================================================================= -// TIMER CREATION -//============================================================================= - -TimerId TimerManager::SetTimeout(lua_State* L, const std::string& app_id, - int callback_ref, int delay_ms) { - std::lock_guard lock(m_mutex); - - // Check per-app limit - if (m_app_timer_counts[app_id] >= MAX_TIMERS_PER_APP) { - // Release the callback reference since we're not using it - luaL_unref(L, LUA_REGISTRYINDEX, callback_ref); - return 0; - } - - // Clamp delay - if (delay_ms < MIN_TIMEOUT_MS) { - delay_ms = MIN_TIMEOUT_MS; - } - - Timer timer; - timer.id = m_next_id++; - timer.app_id = app_id; - timer.fire_time = std::chrono::steady_clock::now() + Duration(delay_ms); - timer.interval = Duration(0); - timer.callback_ref = callback_ref; - timer.L = L; - timer.cancelled = false; - timer.is_interval = false; - - m_timers.push_back(timer); - m_app_timer_counts[app_id]++; - m_app_timer_ids[app_id].insert(timer.id); - - return timer.id; -} - -TimerId TimerManager::SetInterval(lua_State* L, const std::string& app_id, - int callback_ref, int interval_ms) { - std::lock_guard lock(m_mutex); - - // Check per-app limit - if (m_app_timer_counts[app_id] >= MAX_TIMERS_PER_APP) { - luaL_unref(L, LUA_REGISTRYINDEX, callback_ref); - return 0; - } - - // Clamp interval to minimum - if (interval_ms < MIN_INTERVAL_MS) { - interval_ms = MIN_INTERVAL_MS; - } - - Timer timer; - timer.id = m_next_id++; - timer.app_id = app_id; - timer.fire_time = std::chrono::steady_clock::now() + Duration(interval_ms); - timer.interval = Duration(interval_ms); - timer.callback_ref = callback_ref; - timer.L = L; - timer.cancelled = false; - timer.is_interval = true; - - m_timers.push_back(timer); - m_app_timer_counts[app_id]++; - m_app_timer_ids[app_id].insert(timer.id); - - return timer.id; -} - -//============================================================================= -// TIMER CANCELLATION -//============================================================================= - -bool TimerManager::ClearTimer(const std::string& app_id, TimerId id) { - std::lock_guard lock(m_mutex); - - // Find the timer - auto it = std::find_if(m_timers.begin(), m_timers.end(), - [id, &app_id](const Timer& t) { - return t.id == id && t.app_id == app_id && !t.cancelled; - }); - - if (it == m_timers.end()) { - return false; - } - - // Mark as cancelled (will be removed during ProcessTimers) - it->cancelled = true; - - // Release the Lua callback reference - if (it->callback_ref != LUA_NOREF && it->L) { - luaL_unref(it->L, LUA_REGISTRYINDEX, it->callback_ref); - it->callback_ref = LUA_NOREF; - } - - // Update counts - if (m_app_timer_counts[app_id] > 0) { - m_app_timer_counts[app_id]--; - } - m_app_timer_ids[app_id].erase(id); - - return true; -} - -void TimerManager::ClearAppTimers(const std::string& app_id) { - std::lock_guard lock(m_mutex); - - // Get all timer IDs for this app - auto it = m_app_timer_ids.find(app_id); - if (it == m_app_timer_ids.end()) { - return; - } - - // Mark all timers as cancelled and release references - for (auto& timer : m_timers) { - if (timer.app_id == app_id && !timer.cancelled) { - timer.cancelled = true; - if (timer.callback_ref != LUA_NOREF && timer.L) { - luaL_unref(timer.L, LUA_REGISTRYINDEX, timer.callback_ref); - timer.callback_ref = LUA_NOREF; - } - } - } - - // Clear tracking - m_app_timer_counts[app_id] = 0; - m_app_timer_ids[app_id].clear(); -} - -//============================================================================= -// TIMER PROCESSING -//============================================================================= - -void TimerManager::FireTimer(Timer& timer) { - if (timer.cancelled || timer.callback_ref == LUA_NOREF || !timer.L) { - return; - } - - lua_State* L = timer.L; - - // Get the callback from registry - lua_rawgeti(L, LUA_REGISTRYINDEX, timer.callback_ref); - - if (lua_isfunction(L, -1)) { - // Call the callback with protected call - int result = lua_pcall(L, 0, 0, 0); - if (result != LUA_OK) { - // Log error but don't propagate - lua_pop(L, 1); - } - } else { - lua_pop(L, 1); - } -} - -void TimerManager::RescheduleInterval(Timer& timer) { - // Update fire time for next interval - timer.fire_time = std::chrono::steady_clock::now() + timer.interval; -} - -int TimerManager::ProcessTimers() { - // We need to be careful here - firing a timer might cause - // new timers to be added or timers to be cancelled - - std::vector to_fire; - std::vector to_reschedule; - std::vector to_remove; - - auto now = std::chrono::steady_clock::now(); - - { - std::lock_guard lock(m_mutex); - - // Find all timers that should fire - for (auto& timer : m_timers) { - if (timer.cancelled) { - to_remove.push_back(timer.id); - } else if (timer.fire_time <= now) { - to_fire.push_back(timer); - if (timer.is_interval) { - to_reschedule.push_back(timer.id); - } else { - to_remove.push_back(timer.id); - } - } - } - } - - // Fire timers outside the lock to allow callbacks to create new timers - int fired_count = 0; - for (auto& timer : to_fire) { - FireTimer(timer); - fired_count++; - } - - { - std::lock_guard lock(m_mutex); - - // Reschedule intervals - for (TimerId id : to_reschedule) { - auto it = std::find_if(m_timers.begin(), m_timers.end(), - [id](const Timer& t) { return t.id == id && !t.cancelled; }); - if (it != m_timers.end()) { - RescheduleInterval(*it); - } - } - - // Remove completed/cancelled timers - for (TimerId id : to_remove) { - auto it = std::find_if(m_timers.begin(), m_timers.end(), - [id](const Timer& t) { return t.id == id; }); - if (it != m_timers.end()) { - // Release reference if not already released - if (it->callback_ref != LUA_NOREF && it->L && !it->is_interval) { - luaL_unref(it->L, LUA_REGISTRYINDEX, it->callback_ref); - } - - // Update counts only for non-cancelled (timeout) timers - if (!it->cancelled && !it->is_interval) { - const std::string& app_id = it->app_id; - if (m_app_timer_counts[app_id] > 0) { - m_app_timer_counts[app_id]--; - } - m_app_timer_ids[app_id].erase(id); - } - - m_timers.erase(it); - } - } - } - - return fired_count; -} - -size_t TimerManager::GetTimerCount(const std::string& app_id) const { - std::lock_guard lock(m_mutex); - - auto it = m_app_timer_counts.find(app_id); - if (it == m_app_timer_counts.end()) { - return 0; - } - return it->second; -} - -//============================================================================= -// LUA API -//============================================================================= - -// Registry keys for storing manager pointer and app_id -static const char* TIMER_MANAGER_KEY = "__mosis_timer_manager"; -static const char* TIMER_APP_ID_KEY = "__mosis_timer_app_id"; - -// setTimeout(callback, delay_ms) -> timer_id -static int lua_setTimeout(lua_State* L) { - // Check arguments - luaL_checktype(L, 1, LUA_TFUNCTION); - int delay_ms = static_cast(luaL_checkinteger(L, 2)); - - // Get timer manager from registry - lua_getfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY); - if (!lua_islightuserdata(L, -1)) { - return luaL_error(L, "timer system not initialized"); - } - TimerManager* manager = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - // Get app_id from registry - lua_getfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY); - if (!lua_isstring(L, -1)) { - return luaL_error(L, "app_id not set"); - } - std::string app_id = lua_tostring(L, -1); - lua_pop(L, 1); - - // Store the callback in registry - lua_pushvalue(L, 1); // Push the callback - int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX); - - // Create the timer - TimerId id = manager->SetTimeout(L, app_id, callback_ref, delay_ms); - - if (id == 0) { - return luaL_error(L, "timer limit exceeded"); - } - - lua_pushinteger(L, static_cast(id)); - return 1; -} - -// clearTimeout(timer_id) -static int lua_clearTimeout(lua_State* L) { - TimerId id = static_cast(luaL_checkinteger(L, 1)); - - // Get timer manager from registry - lua_getfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY); - if (!lua_islightuserdata(L, -1)) { - return luaL_error(L, "timer system not initialized"); - } - TimerManager* manager = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - // Get app_id from registry - lua_getfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY); - if (!lua_isstring(L, -1)) { - return luaL_error(L, "app_id not set"); - } - std::string app_id = lua_tostring(L, -1); - lua_pop(L, 1); - - manager->ClearTimer(app_id, id); - - return 0; -} - -// setInterval(callback, interval_ms) -> timer_id -static int lua_setInterval(lua_State* L) { - // Check arguments - luaL_checktype(L, 1, LUA_TFUNCTION); - int interval_ms = static_cast(luaL_checkinteger(L, 2)); - - // Get timer manager from registry - lua_getfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY); - if (!lua_islightuserdata(L, -1)) { - return luaL_error(L, "timer system not initialized"); - } - TimerManager* manager = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - - // Get app_id from registry - lua_getfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY); - if (!lua_isstring(L, -1)) { - return luaL_error(L, "app_id not set"); - } - std::string app_id = lua_tostring(L, -1); - lua_pop(L, 1); - - // Store the callback in registry - lua_pushvalue(L, 1); // Push the callback - int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX); - - // Create the timer - TimerId id = manager->SetInterval(L, app_id, callback_ref, interval_ms); - - if (id == 0) { - return luaL_error(L, "timer limit exceeded"); - } - - lua_pushinteger(L, static_cast(id)); - return 1; -} - -// clearInterval(timer_id) -static int lua_clearInterval(lua_State* L) { - // Same as clearTimeout - return lua_clearTimeout(L); -} - -void RegisterTimerAPI(lua_State* L, TimerManager* manager, const std::string& app_id) { - // Store timer manager pointer in registry - lua_pushlightuserdata(L, manager); - lua_setfield(L, LUA_REGISTRYINDEX, TIMER_MANAGER_KEY); - - // Store app_id in registry - lua_pushstring(L, app_id.c_str()); - lua_setfield(L, LUA_REGISTRYINDEX, TIMER_APP_ID_KEY); - - // Get the real _G (not the proxy) - // We need to set these in the real global table that the proxy reads from - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - - // Check if we're dealing with a proxy (has __index metatable) - if (lua_getmetatable(L, -1)) { - lua_getfield(L, -1, "__index"); - if (lua_istable(L, -1)) { - // We have a proxy, use the __index table as the real _G - lua_remove(L, -2); // Remove metatable - lua_remove(L, -2); // Remove proxy - - // Now top of stack is real _G - lua_pushcfunction(L, lua_setTimeout); - lua_setfield(L, -2, "setTimeout"); - - lua_pushcfunction(L, lua_clearTimeout); - lua_setfield(L, -2, "clearTimeout"); - - lua_pushcfunction(L, lua_setInterval); - lua_setfield(L, -2, "setInterval"); - - lua_pushcfunction(L, lua_clearInterval); - lua_setfield(L, -2, "clearInterval"); - - lua_pop(L, 1); // Pop real _G - return; - } - lua_pop(L, 2); // Pop __index and metatable - } - - // No proxy, just use _G directly - lua_pop(L, 1); // Pop whatever we got from LUA_RIDX_GLOBALS - - // Register as globals - lua_pushcfunction(L, lua_setTimeout); - lua_setglobal(L, "setTimeout"); - - lua_pushcfunction(L, lua_clearTimeout); - lua_setglobal(L, "clearTimeout"); - - lua_pushcfunction(L, lua_setInterval); - lua_setglobal(L, "setInterval"); - - lua_pushcfunction(L, lua_clearInterval); - lua_setglobal(L, "clearInterval"); -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/timer_manager.h b/src/main/cpp/sandbox/timer_manager.h deleted file mode 100644 index 99585f5..0000000 --- a/src/main/cpp/sandbox/timer_manager.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -struct lua_State; - -namespace mosis { - -using TimerId = uint64_t; -using TimePoint = std::chrono::steady_clock::time_point; -using Duration = std::chrono::milliseconds; - -struct Timer { - TimerId id; - std::string app_id; - TimePoint fire_time; - Duration interval; // 0 for setTimeout, >0 for setInterval - int callback_ref; // Lua registry reference - lua_State* L; // Lua state that owns the callback - bool cancelled = false; - bool is_interval = false; -}; - -class TimerManager { -public: - TimerManager(); - ~TimerManager(); - - // Non-copyable - TimerManager(const TimerManager&) = delete; - TimerManager& operator=(const TimerManager&) = delete; - - // Create timers (returns timer ID, 0 on failure) - TimerId SetTimeout(lua_State* L, const std::string& app_id, - int callback_ref, int delay_ms); - TimerId SetInterval(lua_State* L, const std::string& app_id, - int callback_ref, int interval_ms); - - // Cancel timers - bool ClearTimer(const std::string& app_id, TimerId id); - - // Cancel all timers for an app (call on app stop) - void ClearAppTimers(const std::string& app_id); - - // Process timers (call from main loop) - // Returns number of timers fired - int ProcessTimers(); - - // Get timer count for an app - size_t GetTimerCount(const std::string& app_id) const; - - // Configuration - static constexpr size_t MAX_TIMERS_PER_APP = 100; - static constexpr int MIN_INTERVAL_MS = 10; - static constexpr int MIN_TIMEOUT_MS = 0; - -private: - TimerId m_next_id = 1; - - // All timers (we use a vector and sort/search as needed) - std::vector m_timers; - - // Track timer count per app - std::unordered_map m_app_timer_counts; - - // Track which timer IDs belong to which app (for fast cancellation) - std::unordered_map> m_app_timer_ids; - - mutable std::mutex m_mutex; - - void FireTimer(Timer& timer); - void RemoveTimer(TimerId id); - void RescheduleInterval(Timer& timer); -}; - -// Lua API registration -// Registers: setTimeout, clearTimeout, setInterval, clearInterval -void RegisterTimerAPI(lua_State* L, TimerManager* manager, const std::string& app_id); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/virtual_fs.cpp b/src/main/cpp/sandbox/virtual_fs.cpp deleted file mode 100644 index 0b782fc..0000000 --- a/src/main/cpp/sandbox/virtual_fs.cpp +++ /dev/null @@ -1,706 +0,0 @@ -#include "virtual_fs.h" - -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace mosis { - -//============================================================================= -// VIRTUALFS IMPLEMENTATION -//============================================================================= - -VirtualFS::VirtualFS(const std::string& app_id, - const std::string& app_root, - const VirtualFSLimits& limits) - : m_app_id(app_id) - , m_app_root(app_root) - , m_limits(limits) { - // Ensure app root exists - std::error_code ec; - fs::create_directories(m_app_root, ec); - - // Recalculate usage on startup - RecalculateUsage(); -} - -VirtualFS::~VirtualFS() { -} - -//============================================================================= -// PATH VALIDATION -//============================================================================= - -bool VirtualFS::IsValidPathChar(char c) { - // Allow alphanumeric, dash, underscore, dot, forward slash - return std::isalnum(static_cast(c)) || - c == '-' || c == '_' || c == '.' || c == '/'; -} - -int VirtualFS::GetPathDepth(const std::string& path) { - int depth = 0; - for (char c : path) { - if (c == '/') depth++; - } - return depth; -} - -bool VirtualFS::ValidatePath(const std::string& virtual_path, std::string& error) { - // Check length - if (virtual_path.length() > m_limits.max_path_length) { - error = "path too long"; - return false; - } - - // Must start with / - if (virtual_path.empty() || virtual_path[0] != '/') { - error = "path must start with /"; - return false; - } - - // Check valid prefix - bool valid_prefix = false; - if (virtual_path.find("/data/") == 0 || virtual_path == "/data") { - valid_prefix = true; - } else if (virtual_path.find("/cache/") == 0 || virtual_path == "/cache") { - valid_prefix = true; - } else if (virtual_path.find("/temp/") == 0 || virtual_path == "/temp") { - valid_prefix = true; - } else if (virtual_path.find("/shared/") == 0 || virtual_path == "/shared") { - // Check permission for shared - if (CheckPermission && !CheckPermission("storage.shared")) { - error = "permission denied: storage.shared required"; - return false; - } - valid_prefix = true; - } - - if (!valid_prefix) { - error = "invalid path prefix (must be /data/, /cache/, /temp/, or /shared/)"; - return false; - } - - // Check for path traversal - if (virtual_path.find("..") != std::string::npos) { - error = "path traversal not allowed"; - return false; - } - - // Check for double slashes (except at start) - if (virtual_path.find("//") != std::string::npos) { - error = "invalid path (double slashes)"; - return false; - } - - // Check all characters are valid - for (char c : virtual_path) { - if (!IsValidPathChar(c)) { - error = "invalid character in path"; - return false; - } - } - - // Check depth - if (GetPathDepth(virtual_path) > m_limits.max_path_depth) { - error = "path too deep"; - return false; - } - - return true; -} - -std::string VirtualFS::ResolvePath(const std::string& virtual_path) { - // Map virtual path to physical path - // /data/foo.txt -> /data/foo.txt - // /cache/bar.txt -> /cache/bar.txt - // /temp/baz.txt -> /temp/baz.txt - // /shared/x.txt -> /shared/x.txt - - fs::path base(m_app_root); - - // Remove leading slash and append - std::string relative = virtual_path.substr(1); // Remove leading / - - return (base / relative).string(); -} - -//============================================================================= -// FILE OPERATIONS -//============================================================================= - -bool VirtualFS::EnsureParentDir(const std::string& path) { - fs::path p(path); - fs::path parent = p.parent_path(); - - if (parent.empty()) return true; - - std::error_code ec; - fs::create_directories(parent, ec); - return !ec; -} - -void VirtualFS::UpdateUsage(int64_t delta) { - if (delta < 0 && static_cast(-delta) > m_used_bytes) { - m_used_bytes = 0; - } else { - m_used_bytes = static_cast(static_cast(m_used_bytes) + delta); - } -} - -bool VirtualFS::CheckQuota(size_t additional_bytes, std::string& error) { - if (m_used_bytes + additional_bytes > m_limits.max_quota_bytes) { - error = "quota exceeded"; - return false; - } - return true; -} - -std::optional VirtualFS::Read(const std::string& path, std::string& error) { - if (!ValidatePath(path, error)) { - return std::nullopt; - } - - std::string physical_path = ResolvePath(path); - - std::ifstream file(physical_path, std::ios::binary); - if (!file) { - error = "file not found"; - return std::nullopt; - } - - std::ostringstream ss; - ss << file.rdbuf(); - return ss.str(); -} - -bool VirtualFS::Write(const std::string& path, const std::string& data, std::string& error) { - if (!ValidatePath(path, error)) { - return false; - } - - // Check file size limit - if (data.size() > m_limits.max_file_size) { - error = "file size limit exceeded"; - return false; - } - - std::string physical_path = ResolvePath(path); - - // Get current file size if exists (for quota calculation) - size_t old_size = 0; - std::error_code ec; - if (fs::exists(physical_path, ec)) { - old_size = static_cast(fs::file_size(physical_path, ec)); - } - - // Check quota for net change - int64_t delta = static_cast(data.size()) - static_cast(old_size); - if (delta > 0 && !CheckQuota(static_cast(delta), error)) { - return false; - } - - // Ensure parent directory exists - if (!EnsureParentDir(physical_path)) { - error = "failed to create parent directory"; - return false; - } - - std::ofstream file(physical_path, std::ios::binary | std::ios::trunc); - if (!file) { - error = "failed to open file for writing"; - return false; - } - - file.write(data.data(), data.size()); - if (!file) { - error = "failed to write data"; - return false; - } - - file.close(); - UpdateUsage(delta); - return true; -} - -bool VirtualFS::Append(const std::string& path, const std::string& data, std::string& error) { - if (!ValidatePath(path, error)) { - return false; - } - - std::string physical_path = ResolvePath(path); - - // Get current file size - size_t current_size = 0; - std::error_code ec; - if (fs::exists(physical_path, ec)) { - current_size = static_cast(fs::file_size(physical_path, ec)); - } - - // Check file size limit - if (current_size + data.size() > m_limits.max_file_size) { - error = "file size limit exceeded"; - return false; - } - - // Check quota - if (!CheckQuota(data.size(), error)) { - return false; - } - - // Ensure parent directory exists - if (!EnsureParentDir(physical_path)) { - error = "failed to create parent directory"; - return false; - } - - std::ofstream file(physical_path, std::ios::binary | std::ios::app); - if (!file) { - error = "failed to open file for appending"; - return false; - } - - file.write(data.data(), data.size()); - if (!file) { - error = "failed to append data"; - return false; - } - - file.close(); - UpdateUsage(static_cast(data.size())); - return true; -} - -bool VirtualFS::Delete(const std::string& path, std::string& error) { - if (!ValidatePath(path, error)) { - return false; - } - - std::string physical_path = ResolvePath(path); - - std::error_code ec; - if (!fs::exists(physical_path, ec)) { - error = "file not found"; - return false; - } - - // Get size before deletion - size_t file_size = 0; - if (fs::is_regular_file(physical_path, ec)) { - file_size = static_cast(fs::file_size(physical_path, ec)); - } - - if (!fs::remove(physical_path, ec)) { - error = "failed to delete"; - return false; - } - - UpdateUsage(-static_cast(file_size)); - return true; -} - -bool VirtualFS::Exists(const std::string& path) { - std::string error; - if (!ValidatePath(path, error)) { - return false; - } - - std::string physical_path = ResolvePath(path); - std::error_code ec; - return fs::exists(physical_path, ec); -} - -std::optional> VirtualFS::List(const std::string& path, std::string& error) { - if (!ValidatePath(path, error)) { - return std::nullopt; - } - - std::string physical_path = ResolvePath(path); - - std::error_code ec; - if (!fs::exists(physical_path, ec) || !fs::is_directory(physical_path, ec)) { - error = "directory not found"; - return std::nullopt; - } - - std::vector entries; - for (const auto& entry : fs::directory_iterator(physical_path, ec)) { - entries.push_back(entry.path().filename().string()); - } - - if (ec) { - error = "failed to list directory"; - return std::nullopt; - } - - return entries; -} - -bool VirtualFS::MakeDir(const std::string& path, std::string& error) { - if (!ValidatePath(path, error)) { - return false; - } - - std::string physical_path = ResolvePath(path); - - std::error_code ec; - if (!fs::create_directories(physical_path, ec) && ec) { - error = "failed to create directory"; - return false; - } - - return true; -} - -std::optional VirtualFS::Stat(const std::string& path, std::string& error) { - if (!ValidatePath(path, error)) { - return std::nullopt; - } - - std::string physical_path = ResolvePath(path); - - std::error_code ec; - if (!fs::exists(physical_path, ec)) { - error = "file not found"; - return std::nullopt; - } - - FileStat stat; - stat.is_dir = fs::is_directory(physical_path, ec); - - if (stat.is_dir) { - stat.size = 0; - } else { - stat.size = static_cast(fs::file_size(physical_path, ec)); - } - - auto ftime = fs::last_write_time(physical_path, ec); - // Convert file_time_type to system_clock (portable workaround for clock_cast) - auto file_time_ns = ftime.time_since_epoch(); - auto sys_time_ns = std::chrono::duration_cast(file_time_ns); - stat.modified = sys_time_ns.count(); - - return stat; -} - -//============================================================================= -// CLEANUP -//============================================================================= - -void VirtualFS::DeleteDirectoryRecursive(const std::string& path) { - std::error_code ec; - fs::remove_all(path, ec); -} - -size_t VirtualFS::CalculateDirectorySize(const std::string& path) { - size_t total = 0; - std::error_code ec; - - if (!fs::exists(path, ec)) { - return 0; - } - - for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { - if (fs::is_regular_file(entry, ec)) { - total += static_cast(fs::file_size(entry, ec)); - } - } - - return total; -} - -void VirtualFS::RecalculateUsage() { - m_used_bytes = CalculateDirectorySize(m_app_root); -} - -void VirtualFS::ClearTemp() { - fs::path temp_path = fs::path(m_app_root) / "temp"; - - std::error_code ec; - if (fs::exists(temp_path, ec)) { - size_t temp_size = CalculateDirectorySize(temp_path.string()); - DeleteDirectoryRecursive(temp_path.string()); - UpdateUsage(-static_cast(temp_size)); - } -} - -void VirtualFS::ClearAll() { - DeleteDirectoryRecursive(m_app_root); - m_used_bytes = 0; -} - -//============================================================================= -// LUA API -//============================================================================= - -static const char* VFS_KEY = "__mosis_vfs"; - -static VirtualFS* GetVFS(lua_State* L) { - lua_getfield(L, LUA_REGISTRYINDEX, VFS_KEY); - if (lua_islightuserdata(L, -1)) { - VirtualFS* vfs = static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - return vfs; - } - lua_pop(L, 1); - return nullptr; -} - -// fs.read(path) -> content|nil, error -static int lua_fs_read(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushnil(L); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - std::string error; - - auto content = vfs->Read(path, error); - if (content) { - lua_pushlstring(L, content->data(), content->size()); - return 1; - } else { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// fs.write(path, data) -> bool, error -static int lua_fs_write(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushboolean(L, 0); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - size_t len; - const char* data = luaL_checklstring(L, 2, &len); - std::string error; - - if (vfs->Write(path, std::string(data, len), error)) { - lua_pushboolean(L, 1); - return 1; - } else { - lua_pushboolean(L, 0); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// fs.append(path, data) -> bool, error -static int lua_fs_append(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushboolean(L, 0); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - size_t len; - const char* data = luaL_checklstring(L, 2, &len); - std::string error; - - if (vfs->Append(path, std::string(data, len), error)) { - lua_pushboolean(L, 1); - return 1; - } else { - lua_pushboolean(L, 0); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// fs.delete(path) -> bool, error -static int lua_fs_delete(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushboolean(L, 0); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - std::string error; - - if (vfs->Delete(path, error)) { - lua_pushboolean(L, 1); - return 1; - } else { - lua_pushboolean(L, 0); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// fs.exists(path) -> bool -static int lua_fs_exists(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushboolean(L, 0); - return 1; - } - - const char* path = luaL_checkstring(L, 1); - lua_pushboolean(L, vfs->Exists(path) ? 1 : 0); - return 1; -} - -// fs.list(path) -> array|nil, error -static int lua_fs_list(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushnil(L); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - std::string error; - - auto entries = vfs->List(path, error); - if (entries) { - lua_createtable(L, static_cast(entries->size()), 0); - int i = 1; - for (const auto& name : *entries) { - lua_pushlstring(L, name.c_str(), name.size()); - lua_rawseti(L, -2, i++); - } - return 1; - } else { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// fs.mkdir(path) -> bool, error -static int lua_fs_mkdir(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushboolean(L, 0); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - std::string error; - - if (vfs->MakeDir(path, error)) { - lua_pushboolean(L, 1); - return 1; - } else { - lua_pushboolean(L, 0); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// fs.stat(path) -> {size, modified, isDir}|nil, error -static int lua_fs_stat(lua_State* L) { - VirtualFS* vfs = GetVFS(L); - if (!vfs) { - lua_pushnil(L); - lua_pushstring(L, "VirtualFS not initialized"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - std::string error; - - auto stat = vfs->Stat(path, error); - if (stat) { - lua_createtable(L, 0, 3); - - lua_pushinteger(L, static_cast(stat->size)); - lua_setfield(L, -2, "size"); - - lua_pushinteger(L, static_cast(stat->modified)); - lua_setfield(L, -2, "modified"); - - lua_pushboolean(L, stat->is_dir ? 1 : 0); - lua_setfield(L, -2, "isDir"); - - return 1; - } else { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } -} - -// Helper to set a global in the real _G (bypassing any proxy) -static void SetGlobalInRealG(lua_State* L, const char* name) { - // Stack: value to set as global - - // Get _G (might be a proxy) - lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - - // Check if it has a metatable with __index (proxy pattern) - if (lua_getmetatable(L, -1)) { - lua_getfield(L, -1, "__index"); - if (lua_istable(L, -1)) { - // Found real _G through proxy's __index - // Stack: value, proxy, mt, real_G - lua_pushvalue(L, -4); // Copy value - lua_setfield(L, -2, name); // real_G[name] = value - lua_pop(L, 4); // pop real_G, mt, proxy, original value - return; - } - lua_pop(L, 2); // pop __index, metatable - } - - // No proxy, set directly in _G - // Stack: value, _G - lua_pushvalue(L, -2); // Copy value - lua_setfield(L, -2, name); // _G[name] = value - lua_pop(L, 2); // pop _G, original value -} - -void RegisterVirtualFS(lua_State* L, VirtualFS* vfs) { - // Store VFS in registry - lua_pushlightuserdata(L, vfs); - lua_setfield(L, LUA_REGISTRYINDEX, VFS_KEY); - - // Create fs table - lua_newtable(L); - - lua_pushcfunction(L, lua_fs_read); - lua_setfield(L, -2, "read"); - - lua_pushcfunction(L, lua_fs_write); - lua_setfield(L, -2, "write"); - - lua_pushcfunction(L, lua_fs_append); - lua_setfield(L, -2, "append"); - - lua_pushcfunction(L, lua_fs_delete); - lua_setfield(L, -2, "delete"); - - lua_pushcfunction(L, lua_fs_exists); - lua_setfield(L, -2, "exists"); - - lua_pushcfunction(L, lua_fs_list); - lua_setfield(L, -2, "list"); - - lua_pushcfunction(L, lua_fs_mkdir); - lua_setfield(L, -2, "mkdir"); - - lua_pushcfunction(L, lua_fs_stat); - lua_setfield(L, -2, "stat"); - - // Set as global (bypassing proxy) - SetGlobalInRealG(L, "fs"); -} - -} // namespace mosis diff --git a/src/main/cpp/sandbox/virtual_fs.h b/src/main/cpp/sandbox/virtual_fs.h deleted file mode 100644 index 21e1044..0000000 --- a/src/main/cpp/sandbox/virtual_fs.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -struct lua_State; - -namespace mosis { - -struct FileStat { - size_t size; - int64_t modified; // Unix timestamp - bool is_dir; -}; - -struct VirtualFSLimits { - size_t max_quota_bytes = 50 * 1024 * 1024; // 50 MB per app - size_t max_file_size = 10 * 1024 * 1024; // 10 MB per file - int max_path_depth = 10; // Max directory depth - size_t max_path_length = 256; // Max path string length -}; - -class VirtualFS { -public: - VirtualFS(const std::string& app_id, - const std::string& app_root, - const VirtualFSLimits& limits = VirtualFSLimits{}); - ~VirtualFS(); - - // Path operations - bool ValidatePath(const std::string& virtual_path, std::string& error); - std::string ResolvePath(const std::string& virtual_path); - - // File operations - std::optional Read(const std::string& path, std::string& error); - bool Write(const std::string& path, const std::string& data, std::string& error); - bool Append(const std::string& path, const std::string& data, std::string& error); - bool Delete(const std::string& path, std::string& error); - bool Exists(const std::string& path); - std::optional> List(const std::string& path, std::string& error); - bool MakeDir(const std::string& path, std::string& error); - std::optional Stat(const std::string& path, std::string& error); - - // Quota management - size_t GetUsedBytes() const { return m_used_bytes; } - size_t GetQuotaBytes() const { return m_limits.max_quota_bytes; } - void RecalculateUsage(); - - // Cleanup - void ClearTemp(); - void ClearAll(); // For testing - - // Permission check callback (set by sandbox) - std::function CheckPermission; - -private: - std::string m_app_id; - std::string m_app_root; - VirtualFSLimits m_limits; - size_t m_used_bytes = 0; - - bool EnsureParentDir(const std::string& path); - void UpdateUsage(int64_t delta); - bool CheckQuota(size_t additional_bytes, std::string& error); - int GetPathDepth(const std::string& path); - bool IsValidPathChar(char c); - void DeleteDirectoryRecursive(const std::string& path); - size_t CalculateDirectorySize(const std::string& path); -}; - -// Register fs.* APIs as globals -void RegisterVirtualFS(lua_State* L, VirtualFS* vfs); - -} // namespace mosis diff --git a/src/main/cpp/sandbox/websocket_manager.h b/src/main/cpp/sandbox/websocket_manager.h index f14249a..531dca9 100644 --- a/src/main/cpp/sandbox/websocket_manager.h +++ b/src/main/cpp/sandbox/websocket_manager.h @@ -6,7 +6,7 @@ #include #include #include -#include "http_validator.h" +#include struct lua_State; diff --git a/src/main/cpp/shader.cpp b/src/main/cpp/shader.cpp index e6ceea1..b8f8f1d 100644 --- a/src/main/cpp/shader.cpp +++ b/src/main/cpp/shader.cpp @@ -2,7 +2,7 @@ #include #include #include "assets_manager.h" -#include "logger.h" +#include void Shader::destroy() { diff --git a/src/main/cpp/vcpkg.json b/src/main/cpp/vcpkg.json index 99019a9..314b974 100644 --- a/src/main/cpp/vcpkg.json +++ b/src/main/cpp/vcpkg.json @@ -6,6 +6,7 @@ "freetype", "nlohmann-json", "minizip", - "sqlite3" + "sqlite3", + "openssl" ] } \ No newline at end of file