Skip to main content
The LIOP TypeScript SDK enforces a defense-in-depth security model composed of six independent layers. Each layer addresses a distinct threat surface — from static code analysis to post-execution data filtering — ensuring that no single point of failure can compromise the system.
Features marked with [ROADMAP] are architecturally defined but not yet available in the current release.

Security Layers Overview

Security Layers Architecture
LayerPurposeTiming
Guardian ASTBlocks unauthorized WASM imports before executionPre-compile
WASI SandboxIsolates runtime with 25 poisoned globals and CPU fuel limitsExecution
Prototype DefenseFreezes 11 core prototypes in strict mode to prevent pollution attacksExecution
PII ShieldPrevents data leakage with algorithmic validatorsPost-execution
Aggregation PolicyEnforces output cardinality limits on row-level dataPost-execution
ZK-ReceiptProvides HMAC-SHA256 proof of honest computationPost-execution

Layer 1: Guardian AST

The Guardian AST module performs a zero-latency static inspection of WebAssembly modules before compilation. It leverages WebAssembly.Module.imports() to enumerate all host-bound import requests and validate them against a strict allowlist.

WASI Function Allowlist

Only the following 14 wasi_snapshot_preview1 functions are permitted:
fd_write, fd_read, fd_close, fd_seek, fd_prestat_get, fd_prestat_dir_name,
fd_fdstat_get, environ_get, environ_sizes_get, args_get, args_sizes_get,
clock_time_get, proc_exit, random_get
Any import outside this set triggers an immediate SandboxViolation error. A hard cap of 128 total imports per module prevents resource exhaustion via logic-bomb payloads.
The Guardian AST inspects WASM module imports, not JavaScript globals. Blocking of dangerous JS APIs (require, eval, fetch) is handled by Layer 2 through global poisoning.

Layer 2: WASI Sandbox

The SDK provides a dual-path execution engine that routes payloads to the appropriate isolation mechanism:
PathTechnologyUse Case
Native WASMnode:wasi (preview1)Pre-compiled .wasm binaries
V8 Isolatenode:vm + hardened contextDynamic JavaScript payloads

V8 Global Poisoning

Twenty-five attack vectors are neutralized by setting each to undefined and sealing them as non-writable:
const POISONED_GLOBALS = [
  // Core Node.js / Runtime APIs
  "require", "process", "global", "globalThis",
  "Buffer", "setTimeout", "setInterval", "setImmediate",
  "queueMicrotask", "eval", "Function", "SharedArrayBuffer",
  // Timing Side-Channel Defense
  "Date",
  // Heap Bomb / Binary Exploitation Defense
  "ArrayBuffer", "Uint8Array", "Int8Array",
  "Uint16Array", "Int16Array", "Uint32Array", "Int32Array",
  "Float32Array", "Float64Array",
  "BigInt64Array", "BigUint64Array", "DataView"
];
Date poisoning prevents injected logic from performing timing side-channel analysis to infer dataset size or execution patterns. TypedArray poisoning prevents heap bomb DoS attacks that allocate massive binary buffers to crash the worker process.
After assignment, the entire sandbox context is recursively frozen via Object.freeze(), preventing any runtime modification of the execution environment.

Prototype Pollution Defense

Inside the sandbox IIFE, eleven core JavaScript prototypes are frozen before user code executes to satisfy strict PCI-DSS and HIPAA logical isolation requirements:
Object.freeze(Object.prototype);
Object.freeze(Array.prototype);
Object.freeze(String.prototype);
Object.freeze(Number.prototype);
Object.freeze(Boolean.prototype);
Object.freeze(RegExp.prototype);
Object.freeze(Map.prototype);
Object.freeze(Set.prototype);
Object.freeze(Promise.prototype);
Object.freeze(Object.getPrototypeOf(function(){})); // Resolves and freezes Function.prototype
Object.freeze(Error.prototype);
Because Function and other global namespaces are explicitly poisoned (set to undefined) in the global scope to eliminate execution escapes, Function.prototype cannot be frozen directly. The SDK resolves it dynamically via Object.getPrototypeOf(function(){}) to apply the freeze. The entire guest execution is wrapped inside a block containing "use strict";. Consequently, any attempt by the injected code to write or assign properties to these frozen prototypes (e.g., Object.prototype.polluted = "leak") will immediately throw a hard TypeError, halting execution and preventing prototype pollution vulnerabilities.

CPU Fuel Limits

ParameterValue
Execution timeout5,000 ms (hard kill)
Fuel limit1,000,000 units
Working directoryEphemeral (deleted on teardown)

Microtask Escape Defense

In V8 context execution, microtasks (like resolved Promises) scheduled during evaluation can sometimes outlive the script execution boundary or bypass simple synchronous limits. To prevent this, the SDK enforces:
const context = vm.createContext(sandboxEnv, {
  name: "LIOP Isolate",
  origin: "liop://sandbox",
  microtaskMode: "afterEvaluate",
});
The microtaskMode: 'afterEvaluate' option instructs Node.js to immediately run all microtasks queued by the script before returning. This guarantees that no async logic survives outside the 5,000 ms sandbox execution limit, neutralizing potential asynchronous logic-bomb bypasses.

Safe Host Environment Variables (allowEnv)

By default, the WASI sandbox completely isolates the guest environment. If your business logic strictly requires environment variables, you can enable safe host environment propagation:
const server = new LiopServer(info, {
  // In WasiSandbox options
  allowEnv: true
});
To prevent shell injection exploits (such as Shellshock) and avoid silent leaks of sensitive host credentials (such as AWS_SECRET_ACCESS_KEY or NPM_TOKEN), the SDK filters environment variables through a strict safe allowlist via getDefaultEnvironment():
  • Windows Host Allowlist: APPDATA, HOMEDRIVE, HOMEPATH, LOCALAPPDATA, PATH, PROCESSOR_ARCHITECTURE, SYSTEMDRIVE, SYSTEMROOT, TEMP, USERNAME, USERPROFILE, PROGRAMFILES.
  • Unix/Linux Host Allowlist: HOME, LOGNAME, PATH, SHELL, TERM, USER.
Any environment variable starting with shell function definitions () is immediately rejected to prevent remote code execution vectors.

Post-Execution Cleanup

The sandbox destroys all temporary artifacts after every execution cycle:
await fs.rm(workingDir, { recursive: true, force: true });

Layer 3: PII Egress Shield

The PII Scanner intercepts all sandbox output before results are returned to the caller. Starting from v3, it operates a four-stage detection pipeline that combines structural analysis, pattern matching, and natural language processing. Each stage is independent — the system blocks data if any stage detects a violation.

Detection Pipeline

StageMechanismPurpose
1. Exact Key MatchSet<string>.has() — O(1)Blocks well-known PII field names (ssn, email, password)
2. Fuzzy Key MatchBoundary regex + substringCatches aliases and variations (patientId, names, fullName)
3. Pattern ValidatorsRegex + algorithmic checksDetects PII values regardless of key naming (emails, credit cards, IBANs)
4. NER Content Scancompromise NLP engineIdentifies person names, places, and organizations in free text

Stage 1–2: Key Analysis

Exact match uses a Set<string> for O(1) constant-time lookup against a configurable list of forbidden keys. Fuzzy match extends protection to aliases and variations using two algorithms:
  • Short tokens (< 4 chars, e.g., id): Boundary-aware regex that detects patientId, record_id, user-id but allows grid, video, android
  • Long tokens (≥ 4 chars, e.g., name, phone): Substring containment that detects firstName, accountName, names
A safelist of common English words and LIOP protocol internal keys prevents false positives on terms like diagnosis, medication, image_id, or timestamp.

Stage 3: Pattern Validators

PatternAlgorithmValidation
EMAILRegexExcludes @example.com, @test.com
CREDIT_CARDLuhn checksumDigit sum mod 10 === 0
IP_ADDRESSRegex + range checkExcludes 127.x.x.x, validates octets 0–255
PHONERegex + repetition check7–15 digits, rejects 1111111 patterns
SSNStructural validationBlocks area=0/666/≥900, group=0, serial=0
IBANISO 7064 Mod-97 via BigIntBigInt(numericString) % 97n === 1n
PASSPORT_MRZRegexTD3 44-character Machine Readable Zone

Stage 4: Named Entity Recognition (NER)

When enableNerScanning is set to true, the scanner leverages the compromise NLP library to detect person names, geographic locations, and organization names embedded in output values — regardless of the key used to store them.
const server = new LiopServer(
  { name: "secure-vault", version: "1.0.0" },
  {
    security: {
      enableNerScanning: true,  // Activates NLP-based entity detection
      forbiddenKeys: ["id", "name", "ssn", "email"],
    },
  },
);
NER scanning is opt-in during the alpha phase. It adds approximately 10ms of latency for typical output sizes (< 10KB). The compromise library operates entirely in-process with no external API calls.

Regional Presets

The SDK ships with three preconfigured pattern sets tailored to common regulatory frameworks:
PII_PRESETS.GLOBAL_STRICT  // 6 patterns (Excludes SSN)
PII_PRESETS.US_COMPLIANT   // 6 patterns (Includes SSN, excludes IBAN)
PII_PRESETS.EU_GDPR        // 6 patterns (Includes IBAN, excludes SSN)
Presets can be merged with custom regex rules for organization-specific compliance requirements. See the LiopServer configuration guide for usage examples.

Anti-Bypass Protections

  • Nested JSON Defense: Recursive parsing of JSON-encoded strings defeats obfuscation through JSON.stringify() wrapping
  • Circular Reference Guard: WeakSet tracking prevents infinite recursion on self-referencing objects
  • Aggregation-First Policy: Blocks raw row-level data export — only aggregated results (counts, averages, summaries) pass through
  • Output Schema Enforcement: When a Zod output schema is defined, .strict() mode is automatically applied, rejecting any keys not explicitly allowed in the schema
  • Cryptographic & Envelope Unwrapping: Automatically isolates and extracts the raw business data payload from MCP/LIOP envelopes and gRPC proxied responses (via unwrapForAggregationPolicyScan) before scanning. This prevents false positives from binary metadata and cryptographic seals (like HMAC-SHA256 ZK-Receipt signatures).
  • Recursive In-Memory Numerical Sanitization: Before scanning, all numeric values inside the returned payload are processed recursively: positive floats are clamped to a maximum of 4 decimal places, and negative values are safely clamped to 0 (via sanitizeOutput()). This is executed fully in-memory to prevent float-representation side-channels and avoids redundant string conversions.
  • K-Anonymity on Small Datasets: For micro-datasets or synthetic demos (dataset size < 10), the Egress Shield applies a strict K-Anonymity filter. Any output returned by the sandbox is rejected if it contains more than 3 scalar keys (flat properties) or if it has any arrays or nested objects. This blocks attempts to rebuild datasets key-by-key or through nested structure side-channels.

Differential Privacy & 3-Tier Query Budget (NIST SP 800-226)

To prevent reconstruction and statistical differentiation attacks (where an agent performs multiple overlapping queries to isolate a single record’s values), the SDK implements a tiered query budget system coupled with Laplace differential privacy.

3-Tier Query Budget Session Limits

During a secure PQC-negotiated session, the SDK tracks query access to data fields. In compliance with NIST SP 800-226 guidelines, fields are classified into three sensitivity tiers, each with its own strict query limit per session:
  1. Forbidden Tier (Max 3 queries/session): Applies to fields declared in forbiddenKeys (e.g., ssn, password, email). Any attempt to query these fields beyond the limit triggers an immediate egress block.
  2. Sensitive Tier (Max 8 queries/session): Applies to fields declared in global or tool-level sensitiveKeys (e.g., balance, diagnosis, ticker).
  3. Public Tier (Max 25 queries/session): Applies to any public fields not registered as sensitive or forbidden.
Limits are enforced dynamically based on taint analysis tracking. If any tier’s budget is exhausted, further sandbox executions attempting to access fields in that tier are blocked.

Persistent Query Budgets & Concurrency Controls

To maintain zero-trust security invariants across server restarts, server crashes, or in multi-instance clusters (such as multiple parallel local bridge processes), the SDK supports filesystem-backed persistence of active query budgets:
  • Configuring Persistence: Operators can provide budgetStorePath either globally in the LiopServer constructor options or locally inside a tool-level LogicExecutionPolicy.
  • Client Identity Binding (Anti-Bypass): The budget limits are strictly bound to the client’s cryptographic identity (agentDid derived from their persistent Ed25519 PeerID, or clientId from JWT tokens authorized by the Nexus OAuth 2.1 server). By auditing the actual client identity rather than ephemeral transport tokens (such as gRPC session_token), the SDK prevents adversaries from resetting their query budgets by spawning new handshakes or reconnecting.
  • Atomic File Locking: Under the hood, the SDK enforces cross-process synchronization and atomic updates to the budget JSON store using file-system locking (.lock files). This prevents race conditions where parallel LLM requests execute concurrently on separate worker threads/instances to exceed budget caps.
  • Fail-Safe In-Memory Fallback: If filesystem write permissions fail, if the storage directory is read-only, or if a locking deadlock occurs, the SDK automatically reports the error to system logs and falls back to session-isolated in-memory budget tracking, ensuring continued node serviceability without dropping security enforcement.

Laplace Mechanism & Field Sensitivity

When the dataset size falls below dpSmallDatasetThreshold (default: 50), the DP Engine applies calibrated Laplace noise (NoiseLaplace(0,Δsϵ)Noise \sim \text{Laplace}(0, \frac{\Delta s}{\epsilon})) to all numeric outputs. The engine automatically calibrates the sensitivity (Δs\Delta s) based on field naming conventions:
  • Count fields (count, length, size, num): Sensitivity is locked to 1.01.0.
  • Average fields (avg, mean): Sensitivity is dynamically calculated as dpSensitivityrecordCount\frac{dpSensitivity}{recordCount}.
  • Sum & general fields: Sensitivity is set to the tool’s configured dpSensitivity.

Layer 4: ZK-Receipts

Every sandbox execution produces a cryptographic receipt that binds the output to the exact logic that generated it, providing tamper-evident proof of honest computation.

Receipt Structure (Binary v1)

FieldSizeTypeDescription
Version1 byte0x01Protocol version identifier
Journal Length2 bytesBig-Endian UInt16Length of the JSON payload
JournalVariableJSON stringContains image_id, dataset_hash, output_hash, fuel, and ts
Seal32 bytesHMAC-SHA256Cryptographic commitment over the journal

Cryptographic Pipeline

  1. ImageID Generation: A SHA-256 hash of the original logic payload fingerprints the exact code that was executed
  2. Dataset Hash: A SHA-256 hash of the serialized dataset anchors the data state at execution time (SOX audit trail compliance)
  3. Differential Privacy: Laplace noise is applied to numeric outputs before commitment, ensuring the ZK-Receipt matches the noisy data the client receives. Supports DDP mode (seeded PRNG via dataset_hash + image_id) for audit reproducibility.
  4. Journal Assembly: A JSON object containing image_id, dataset_hash, output_hash (SHA-256 of the post-DP result), fuel consumed, and ts (timestamp)
  5. HMAC Seal: crypto.createHmac("sha256", sessionSecret).update(journal).digest() — the session secret is derived from the ML-KEM-768 (Kyber) key exchange
  6. Verification & Replay Mitigation: The verifier validates the HMAC seal in constant-time (crypto.timingSafeEqual). In addition, to prevent Man-in-the-Middle (MITM) reply manipulation and replay attacks, the verifier computes a local SHA-256 hash of the received result (expectedOutput) and strictly asserts that it is identical to Journal.output_hash (via verifyZkReceipt).
  7. Balanced-Brace Proxy Extractor: If the tool call was delegated via proxy (__liop_proxy_tool), the verifier utilizes an in-process balanced-brace state machine to isolate and extract raw proxy arguments from the response before hashing, avoiding validation false positives when host layers append metadata.
The sessionSecret used for HMAC signing is the shared key negotiated via Post-Quantum Cryptography (ML-KEM-768). This ensures that ZK-Receipt integrity remains quantum-resistant.

Transport Security

Post-Quantum Key Encapsulation (ML-KEM-768)

The SDK uses the mlkem package (FIPS 203 compliant) for key encapsulation:
ParameterValue
AlgorithmML-KEM-768 (Kyber)
Public Key Size1,184 bytes
Ciphertext Size1,088 bytes
Shared Secret32 bytes
Symmetric CipherAES-256-GCM
Nonce12 bytes (unique per input)

Nonce Isolation

Each encrypted payload uses a fresh 12-byte random nonce (crypto.randomBytes(12)) prepended to the ciphertext. This prevents AES-GCM nonce reuse when multiple payloads are encrypted under the same session key.

Production TLS Hardening

While LIOP’s PQC layer encrypts all application-level payloads end-to-end, transport-level encryption (TLS/mTLS) is critical for preventing metadata eavesdropping and ensuring node identity. To prevent silent failures in production, the SDK implements a strict fail-safe check:
  • In development/testing environments, missing or misconfigured certificate files trigger a warning and gracefully fall back to insecure gRPC channels.
  • In production (process.env.NODE_ENV === 'production'), any failure to resolve or load configured TLS certificates (rootCert, certChain, or privateKey) throws a fatal error, forcing the process to crash immediately instead of silently degrading to an unencrypted channel.

Zero-Trust Bridge Authentication

The LiopStreamBridge enforces mandatory Bearer token authentication on all HTTP endpoints:
  • If ZERO_TRUST_TOKEN is not set, a secure ephemeral token is auto-generated via randomUUID()
  • Every request to /mcp requires a valid Authorization: Bearer <token> header
  • Per-IP rate limiting on session creation (default: 10 concurrent sessions)
  • Automatic eviction of idle sessions (TTL: 30 minutes)

P2P Network Security

ComponentTechnology
Transport EncryptionNoise Protocol (Ed25519)
Stream MultiplexingYamux
IdentityEd25519 persistent key pairs
DiscoveryKademlia DHT with public bootstrap nodes
TransportsTCP + WebSocket

Worker Pool Isolation

Computationally intensive cryptographic operations are dispatched to OS-level threads via Piscina worker pools, preventing blocking of the main V8 event loop:
WorkerPurpose
logic-executionSandbox instantiation, AES decryption, AST inspection
zk-verifierReceipt deserialization, HMAC verification
Default configuration: 2–8 threads (production), 0–1 threads (test), FixedQueue scheduling, 5s idle timeout.

Heap Bomb Defense

Each worker thread is constrained via V8’s resourceLimits.maxOldGenerationSizeMb (default: 64 MB, configurable via workerPool.maxHeapMb or the LIOP_WORKER_MAX_HEAP_MB environment variable). If injected logic attempts to allocate memory beyond this limit, the worker is terminated immediately with a WorkerPoolError, preventing denial-of-service attacks that target Node.js heap exhaustion.

Worker Pool Async Warmup

To avoid initial CPU latency spikes and mitigate V8/WASI cold-starts (~820k fuel units), the SDK features an asynchronous thread-pool warmup strategy. On server initialization (or verifier creation), background “warmup” tasks (isWarmup: true or action: "warmup") are dispatched to pre-warm the Piscina worker instances. This ensures worker threads are initialized, V8 isolate contexts are allocated, and WASI handles are pre-cached before processing real client payloads.

Aggregation-First Policy

The SDK enforces an Aggregation-First heuristic that blocks raw row-level data from leaving the sandbox. This is the last computational defense before the ZK-Receipt layer.

How It Works

After execution, the output is recursively scanned for arrays containing objects. If the number of object elements exceeds the configured threshold, the response is blocked:
ParameterDefaultDescription
maxOutputRows10Maximum number of object-type array elements allowed
allowPrimitiveArraystruePermits arrays containing only primitive values (numbers, strings)
server.tool(
  "analyze_data",
  "Aggregated analysis",
  { query: z.string() },
  async ({ query }) => { /* ... */ },
  {
    enforceAggregationFirst: {
      maxOutputRows: 5,
      allowPrimitiveArrays: true
    }
  }
);

Conditional Error Normalization

The SDK implements environment-aware error reporting for policy violations:
EnvironmentBehavior
development / testExposes detailed Zod validation errors, schema mismatch metadata, and HINT messages to enable LLM self-correction
productionReturns a generic [LIOP] Egress Security Violation message with zero internal details leaked
This is controlled automatically via process.env.NODE_ENV. In production deployments, always ensure NODE_ENV=production is set to activate full error opacity.
Detailed error messages in development mode may include field names, schema structures, and rejected value excerpts. Never expose development-mode error output to untrusted clients.

Protocol-Native Directive Channels

To ensure that LLM clients generate compliant JavaScript code that respects sandbox limits without trial-and-error latency, the SDK broadcasts structured, protocol-native instructions at three levels:
  1. JSON Schema Metadata ($comment): The data dictionary automatically injects a $comment field containing sandbox directives directly into the active JSON schema representation.
  2. Execution Guidelines Resource: A dynamic resource (liop://schema/guidelines) details the exact workarounds (e.g., date filtering via ISO 8601 strings) and constraints (K-Anonymity rules, Laplace suffixes) required by the node.
  3. Cross-AI System Prompts: The prompt adapter system normalizes constraints across models (Claude, OpenAI, Gemini) to prevent hallucinated API calls.

Roadmap

The following capabilities are architecturally defined and planned for future releases:
FeatureStatusDescription
TEE Attestation🔴 PlannedHardware enclave support (AWS Nitro / Intel SGX)
Native ZK-VM🔴 PlannedRISC Zero or SP1 bindings for true zero-knowledge proofs
Rust Core ZK🔴 PlannedNative cryptographic proof generation in the Rust core
The current SDK uses HMAC-SHA256 commitments as ZK-Receipt seals. While cryptographically sound for integrity verification, these are not true zero-knowledge proofs. Migration to a native ZK-VM is planned for a future release.