move docs to docs/ folder, merge architecture files, update references
This commit is contained in:
411
docs/SANDBOX_MILESTONE_6.md
Normal file
411
docs/SANDBOX_MILESTONE_6.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# Milestone 6: JSON & Crypto APIs
|
||||
|
||||
**Status**: ✅ Complete
|
||||
**Goal**: Safe data parsing and cryptographic primitives.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This milestone implements secure JSON encoding/decoding and cryptographic primitives for Lua apps:
|
||||
- JSON API with depth/size limits to prevent DoS attacks
|
||||
- Crypto API with secure random, hashing, and HMAC
|
||||
- Replace insecure `math.random` with per-app CSPRNG
|
||||
|
||||
### Key Deliverables
|
||||
|
||||
1. **JSON API** - Safe encode/decode with limits
|
||||
2. **Crypto API** - Hash, HMAC, random bytes
|
||||
3. **Secure math.random replacement**
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/main/cpp/sandbox/
|
||||
├── json_api.h # NEW - JSON API header
|
||||
├── json_api.cpp # NEW - JSON implementation
|
||||
├── crypto_api.h # NEW - Crypto API header
|
||||
└── crypto_api.cpp # NEW - Crypto implementation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. JSON API
|
||||
|
||||
```cpp
|
||||
// json_api.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace mosis {
|
||||
|
||||
// Configuration limits
|
||||
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
|
||||
```
|
||||
|
||||
#### json.decode(str) -> table
|
||||
|
||||
- Parse JSON string to Lua table
|
||||
- Enforce depth limit (32 levels)
|
||||
- Enforce string length limit (1 MB)
|
||||
- Enforce array/object size limits
|
||||
- Return `nil, error_message` on failure
|
||||
|
||||
#### json.encode(table) -> string
|
||||
|
||||
- Convert Lua table to JSON string
|
||||
- Detect cycles (error on circular references)
|
||||
- Enforce output size limit (10 MB)
|
||||
- Handle Lua types: nil, boolean, number, string, table
|
||||
- Error on unsupported types (functions, userdata, threads)
|
||||
|
||||
### 2. Crypto API
|
||||
|
||||
```cpp
|
||||
// crypto_api.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
namespace mosis {
|
||||
|
||||
// Per-app cryptographically secure RNG
|
||||
class SecureRandom {
|
||||
public:
|
||||
SecureRandom();
|
||||
|
||||
// Get random bytes
|
||||
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;
|
||||
};
|
||||
|
||||
// Register crypto.* APIs as globals
|
||||
void RegisterCryptoAPI(lua_State* L);
|
||||
|
||||
// Register secure math.random replacement
|
||||
void RegisterSecureMathRandom(lua_State* L, SecureRandom* rng);
|
||||
|
||||
} // namespace mosis
|
||||
```
|
||||
|
||||
#### crypto.randomBytes(n) -> string
|
||||
|
||||
- Generate `n` cryptographically secure random bytes
|
||||
- Limit: max 1024 bytes per call
|
||||
- Use system CSPRNG (std::random_device or platform-specific)
|
||||
|
||||
#### crypto.hash(algorithm, data) -> string
|
||||
|
||||
- Supported algorithms: "sha256", "sha512", "sha1", "md5"
|
||||
- Returns hex-encoded hash
|
||||
- Input size limit: 10 MB
|
||||
|
||||
#### crypto.hmac(algorithm, key, data) -> string
|
||||
|
||||
- HMAC using specified algorithm
|
||||
- Returns hex-encoded result
|
||||
- Key and data limits same as hash
|
||||
|
||||
### 3. Secure math.random
|
||||
|
||||
Replace Lua's `math.random` and remove `math.randomseed`:
|
||||
|
||||
```lua
|
||||
-- After registration:
|
||||
math.random() -- Returns double in [0.0, 1.0) using CSPRNG
|
||||
math.random(n) -- Returns integer in [1, n]
|
||||
math.random(m, n) -- Returns integer in [m, n]
|
||||
math.randomseed -- Removed (nil)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Cases
|
||||
|
||||
### Test 1: JSON Decode Valid
|
||||
|
||||
```cpp
|
||||
bool Test_JsonDecodeValid(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
mosis::RegisterJsonAPI(sandbox.GetState());
|
||||
|
||||
std::string script = R"(
|
||||
local obj = json.decode('{"name":"test","value":42,"arr":[1,2,3]}')
|
||||
assert(obj.name == "test")
|
||||
assert(obj.value == 42)
|
||||
assert(#obj.arr == 3)
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "decode_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 2: JSON Decode Rejects Deep Nesting
|
||||
|
||||
```cpp
|
||||
bool Test_JsonDecodeRejectsDeep(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
mosis::JsonLimits limits;
|
||||
limits.max_depth = 5;
|
||||
mosis::RegisterJsonAPI(sandbox.GetState(), limits);
|
||||
|
||||
// Create deeply nested JSON (10 levels)
|
||||
std::string deep_json = "[[[[[[[[[[1]]]]]]]]]]";
|
||||
|
||||
std::string script =
|
||||
"local result, err = json.decode('" + deep_json + "')\n"
|
||||
"assert(result == nil, 'should fail')\n"
|
||||
"assert(err:find('depth'), 'should mention depth')";
|
||||
|
||||
if (!sandbox.LoadString(script, "deep_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 3: JSON Encode Detects Cycles
|
||||
|
||||
```cpp
|
||||
bool Test_JsonEncodeDetectsCycles(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
mosis::RegisterJsonAPI(sandbox.GetState());
|
||||
|
||||
std::string script = R"(
|
||||
local t = {a = 1}
|
||||
t.self = t -- Create cycle
|
||||
local result, err = json.encode(t)
|
||||
assert(result == nil, 'should fail on cycle')
|
||||
assert(err:find('cycle') or err:find('circular'), 'should mention cycle')
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "cycle_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 4: JSON Encode Valid
|
||||
|
||||
```cpp
|
||||
bool Test_JsonEncodeValid(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
mosis::RegisterJsonAPI(sandbox.GetState());
|
||||
|
||||
std::string script = R"(
|
||||
local str = json.encode({name = "test", value = 42, arr = {1, 2, 3}})
|
||||
assert(type(str) == "string")
|
||||
-- Decode back to verify
|
||||
local obj = json.decode(str)
|
||||
assert(obj.name == "test")
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "encode_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 5: Crypto RandomBytes
|
||||
|
||||
```cpp
|
||||
bool Test_CryptoRandomBytes(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
mosis::RegisterCryptoAPI(sandbox.GetState());
|
||||
|
||||
std::string script = R"(
|
||||
local bytes = crypto.randomBytes(16)
|
||||
assert(#bytes == 16, 'should be 16 bytes')
|
||||
|
||||
-- Should be different each time
|
||||
local bytes2 = crypto.randomBytes(16)
|
||||
assert(bytes ~= bytes2, 'should be random')
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "random_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 6: Crypto Hash SHA256
|
||||
|
||||
```cpp
|
||||
bool Test_CryptoHashSHA256(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
mosis::RegisterCryptoAPI(sandbox.GetState());
|
||||
|
||||
std::string script = R"(
|
||||
local hash = crypto.hash("sha256", "hello")
|
||||
-- Known SHA256 of "hello"
|
||||
assert(hash == "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "hash_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 7: Crypto HMAC
|
||||
|
||||
```cpp
|
||||
bool Test_CryptoHMAC(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
mosis::RegisterCryptoAPI(sandbox.GetState());
|
||||
|
||||
std::string script = R"(
|
||||
local hmac = crypto.hmac("sha256", "key", "message")
|
||||
-- Known HMAC-SHA256 of "message" with key "key"
|
||||
assert(hmac == "6e9ef29b75fffc5b7abae527d58fdadb2fe42e7219011976917343065f58ed4a")
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "hmac_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 8: Secure Math.Random
|
||||
|
||||
```cpp
|
||||
bool Test_SecureMathRandom(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
mosis::SecureRandom rng;
|
||||
mosis::RegisterSecureMathRandom(sandbox.GetState(), &rng);
|
||||
|
||||
std::string script = R"(
|
||||
-- math.randomseed should be removed
|
||||
assert(math.randomseed == nil, 'randomseed should be removed')
|
||||
|
||||
-- math.random should work
|
||||
local r1 = math.random()
|
||||
assert(r1 >= 0 and r1 < 1, 'random() should return [0,1)')
|
||||
|
||||
local r2 = math.random(10)
|
||||
assert(r2 >= 1 and r2 <= 10, 'random(n) should return [1,n]')
|
||||
|
||||
local r3 = math.random(5, 15)
|
||||
assert(r3 >= 5 and r3 <= 15, 'random(m,n) should return [m,n]')
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "math_random_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Test 9: JSON Rejects Too Large
|
||||
|
||||
```cpp
|
||||
bool Test_JsonRejectsTooLarge(std::string& error_msg) {
|
||||
SandboxContext ctx = TestContext();
|
||||
LuaSandbox sandbox(ctx);
|
||||
|
||||
mosis::JsonLimits limits;
|
||||
limits.max_array_size = 10;
|
||||
mosis::RegisterJsonAPI(sandbox.GetState(), limits);
|
||||
|
||||
std::string script = R"(
|
||||
-- Try to decode array with 20 elements
|
||||
local result, err = json.decode('[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]')
|
||||
assert(result == nil, 'should fail')
|
||||
assert(err:find('size') or err:find('limit'), 'should mention size limit')
|
||||
)";
|
||||
|
||||
if (!sandbox.LoadString(script, "size_test")) {
|
||||
error_msg = sandbox.GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
All tests pass:
|
||||
|
||||
- [x] `Test_JsonDecodeValid` - Decodes valid JSON to Lua table
|
||||
- [x] `Test_JsonDecodeRejectsDeep` - Rejects deeply nested JSON
|
||||
- [x] `Test_JsonEncodeValid` - Encodes Lua table to JSON string
|
||||
- [x] `Test_JsonEncodeDetectsCycles` - Detects circular references
|
||||
- [x] `Test_JsonRejectsTooLarge` - Enforces size limits
|
||||
- [x] `Test_CryptoRandomBytes` - Generates secure random bytes
|
||||
- [x] `Test_CryptoHashSHA256` - Computes correct SHA256 hash
|
||||
- [x] `Test_CryptoHMAC` - Computes correct HMAC
|
||||
- [x] `Test_SecureMathRandom` - Replaces math.random securely
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- nlohmann-json (already in vcpkg for sandbox-test)
|
||||
- OpenSSL or platform crypto APIs for SHA256/HMAC (or header-only implementation)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After Milestone 6 passes:
|
||||
1. Milestone 7: Virtual Filesystem
|
||||
2. Milestone 8: SQLite Database
|
||||
Reference in New Issue
Block a user