Skip to content

Security Model

Nox is built on a single axiom:

The code running inside the sandbox is assumed to be potentially malicious or incorrect at all times.

Whether the code was written by a third-party plugin developer, submitted by an end user, or generated by an AI model, we need verification at every layer. Nox does exactly that.

Nox does not compile .nox scripts to JVM bytecode.

By compiling to a custom bytecode executed by a custom VM, we guarantee that the script can only perform actions explicitly coded into the VM’s instruction handlers.

ThreatMitigation
JVM ReflectionImpossible. The script has no concept of Kotlin/JVM classes, methods, or the Class object. There is no opcode for reflection.
Arbitrary Native CallsImpossible. The only way to call Kotlin code is through the registered FFI system, which is type-checked and permission-gated.
Memory CorruptionImpossible. The VM accesses pre-allocated arrays (pMem, rMem) with bounds-checked indices. There are no raw pointers.
Class LoadingImpossible. The VM has no class loader mechanism. It cannot instantiate arbitrary Kotlin/JVM objects.

Note: All this works on the assumption that the plugins are well-intentioned and free from flaws. If a plugin is malicious, it can try to exploit the VM itself. However, the VM is written in Kotlin and is subject to the same security measures as any other JVM application. However flimsy those may be.

The guarantee: If an operation is not explicitly implemented as a VM opcode or a registered library function, it simply does not exist within the sandbox.

No code inside the sandbox has any implicit permissions. Every interaction with the outside world must go through an explicit capability request.

┌──────────────────────────────────────────────────┐
│ SANDBOX BOUNDARY │
│ │
│ .nox script │
│ ┌──────────────────────┐ │
│ │ File.read("/data") │ │
│ └──────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Permission Bridge │── File.Read("/data") │
│ │ (inside VM) │ │
│ └──────────┬───────────┘ │ │
└──────────────┼─────────────────────┼─────────────┘
│ │
▼ ▼
┌───────────────────────────────────────┐
│ SUPERVISOR │
│ ┌─────────────────────────────┐ │
│ │ Policy Engine / User GUI │ │
│ │ │ │
│ │ ✓ Granted / ✗ Denied │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────────┘

When the VM encounters a library call tagged as requiring permission (e.g., File.read, Http.get):

  1. The VM pauses execution of the current instruction
  2. Constructs a typed PermissionRequest. For example, PermissionRequest.File.Read("/data/file.txt")
  3. Calls RuntimeContext.requestPermission(request) and the Sandbox’s coroutine suspends
  4. The Host pattern-matches on the request type and evaluates its policy. For example:
    • Auto-grant policies for pre-approved operations
    • User prompts via GUI for sensitive requests
    • Blanket deny for prohibited categories
  5. Returns a typed PermissionResponse and the response flows back to the Sandbox
  6. The Sandbox inspects the response:
    • Granted: The operation proceeds (with constraints if provided)
    • Denied: A SecurityException is thrown inside the sandbox, carrying the denial reason
  • Granularity: Permissions are checked per-operation, not per-program. A program can read one file but be denied another.
  • Context-Aware: The Host receives a typed request object with all details (path, URL, etc.) and can make informed decisions.
  • Constraint-Capable: The Host can attach typed constraints to a grant (e.g., FileGrant(maxBytes = 1_048_576)).
  • Non-Bypassable: The permission check lives inside the VM instruction handler. There is no way to call File.read without going through the bridge.

Even without accessing external resources, malicious code can attack the host through resource exhaustion. Nox employs multiple “Watchdogs” to prevent this.

WatchdogThreat MitigatedMechanism
Instruction CounterInfinite loops (while(true){})The VM increments a counter every iteration of the execution loop. Exceeding the limit (e.g., 500,000 ops) triggers QuotaExceededException.
Execution TimeoutHung or stalled scriptsThe Host tracks wall-clock time per Sandbox. Exceeding the maximum duration (e.g., 60s) forces termination.
Memory CapMemory bombs (allocating huge strings/arrays)The VM monitors the size of objects entering the system (e.g., File.read results, JSON parses). Exceeding the threshold (e.g., 100MB) triggers MemoryLimitException.
Recursion LimitStack overflow attacksThe VM’s internal call stack is a fixed-size array (~1024 frames). Exceeding it prevents a StackOverflowError on the host JVM.
Register File SizeVariable explosionThe pre-allocated pMem/rMem arrays have a fixed size (~65k slots). Scripts cannot allocate beyond this.

Security in Nox is layered. A failure in one layer is caught by the next:

Layer 1: Static Analysis (Compile Time)
│ Type mismatches, undeclared variables, schema violations
│ caught before any code runs.
Layer 2: VM Sandbox (Runtime)
│ Only explicitly implemented operations exist.
│ No reflection, no raw memory, no class loading.
Layer 3: Permission Bridge (Runtime)
│ Every external operation requires explicit approval.
│ Per-operation, context-aware, non-bypassable.
Layer 4: Resource Watchdogs (Runtime)
│ Hard limits on CPU, memory, recursion, and time.
│ Enforced by the VM loop itself.
Layer 5: Sandbox Isolation (Architecture)
Sandboxes are single-use coroutines.
No state persists between invocations.
Failure in one Sandbox cannot affect others.

Program authors can declare expected permissions in the file header. These are informational hints and they do not grant permissions but help the Host understand what a program will request.

@tool:name "data_fetcher"
@tool:description "Fetches and processes remote data."
@tool:permission "http.get"
@tool:permission "file.write"
Attack VectorStatusDefense
Arbitrary code execution on host✗ BlockedCustom bytecode VM, no JVM bytecode generation
File system access without permission✗ BlockedPermission Bridge on all File.* calls
Network access without permission✗ BlockedPermission Bridge on all Http.* calls
Secret/env variable leakage✗ BlockedPermission Bridge on all Env.* calls
System fingerprinting via OS properties✗ BlockedEnv.SystemInfo gated behind Permission Bridge
Infinite loop / CPU exhaustion✗ BlockedInstruction counter watchdog
Memory bomb / OOM✗ BlockedMemory cap watchdog
Stack overflow✗ BlockedFixed-size call stack
Cross-sandbox data leakage✗ BlockedSingle-use Sandboxes, no shared state
JVM reflection / class loading✗ BlockedNo opcodes for reflection; concept doesn’t exist in NSL
Plugin escape (FFI)✗ BlockedAll FFI calls wrapped in exception containment