Isolated-Mode Developer Guide
A reference for developers writing miniApps JavaScript snippets that run on the isolated execution path (isolated-vm). It describes the runtime your code sees every global and library injected into it and the exact subset of each that is supported.
This guide covers the isolated path only (the default for all scripts). It is the runtime your code should target. The legacy in-process path is being retired and is not documented here.
The execution model
Each request runs your script in a fresh V8 context inside a shared isolated-vm isolate. Key consequences:
-
Bare V8, not Node. The context has standard ECMAScript built-ins (
JSON,Math,Date,String,Array,Object,RegExp,Promise,Map,Set, etc.) but none of Node's runtime. There is noprocess, nofs, nohttp/net, nosetTimeout/setInterval, nofetch, and no nativeBufferorcrypto. The capabilities below are explicitly injected. -
Isolation per request. Globals you create do not leak across requests, the context is created fresh and released after every call. You cannot share state between invocations.
-
Memory limit. The isolate is capped (default 128 MB,
ISOLATE_MEMORY_LIMIT). Exceeding it terminates execution. -
Time limit. Your script must finish within
TIMEOUT_MS − 100 ms(default 2900 ms, floor 100 ms). Long loops are killed. -
No module system.
require(...)is a curated shim, not Node's loader. Only four module names resolve; everything else throws.
Returning a result
There are two ways to produce the result returned to the caller:
-
ocpReturn(value)
Call the injectedocpReturnfunction with the value you want returned.
ocpReturn("Hello, " + params.name);
-
Completion value (no
ocpReturn)
If your script never callsocpReturn, the value of the last evaluated expression is returned.
const greeting = "Hi " + params.name;
greeting; // <-- this becomes the result
Injected globals
These are available directly, without require:
|
Global |
Type |
Purpose |
|---|---|---|
|
|
object |
Request input - the |
|
|
object |
Request input - the |
|
|
function |
|
|
|
function |
Appends to the response |
|
|
object |
|
|
|
constructor |
|
|
|
function |
latin1 string → base64. Equivalent to: Throws on char codes > 255. |
|
|
function |
base64 → latin1 string. Equivalent to: |
|
|
object |
Host-backed crypto facade (also via |
|
|
function |
|
|
|
function |
|
|
|
object |
The Handlebars library (also via |
|
|
object |
The Eta library (also via |
Handlebars / eta are loaded on demand.
To keep per-request cost down, the runtime injects these two libraries only when it statically detects that your script uses them by a plain Handlebars / eta reference or by require('handlebars'|'eta').
Reaching them indirectly leaves the library undefined: globalThis['Handlebars'], global.Handlebars, this.eta, or a dynamic require(variableName) are not detected. Always reference them by their plain name or via a string-literal require.
-
paramsandocpvars
Read-only inputs copied in from the request. Treat them as data; mutating them has no effect outside your script. -
print(...)andconsole.*
Everything passed toprint(and to anyconsolemethod) is collected and returned in the responselogsfield. Use it for debugging and tracing. It does not affect the result.
print("debug:", params.id);
console.warn("careful", { value: 42 });
require(...) - the module shim
require resolves exactly four names. Any other argument throws Cannot find module '<name>'.
|
Names |
Returns |
Notes |
|---|---|---|
|
|
Handlebars 4.7.9 |
Full library. Also the |
|
|
Eta 3.4.0 |
Full library. Also the |
|
|
crypto facade |
Host-backed subset. |
|
|
|
Only |
const Handlebars = require('handlebars');
const tpl = Handlebars.compile("Hello {{name}}");
ocpReturn(tpl({ name: params.name }));
Buffer (shim)
A minimal pure-JS Buffer. It supports the common encode/decode and crypto plumbing patterns, not the full Node Buffer API.
Supported constructors / statics
|
API |
Notes |
|---|---|
|
|
|
|
|
From an array of byte values (0–255). |
|
|
From a |
|
|
Copy of another shim Buffer. |
|
|
Zero-filled (or |
|
|
Concatenate buffers/byte arrays. |
|
|
True for shim buffers. |
Supported instance members
|
API |
Notes |
|---|---|
|
|
|
|
|
|
|
|
Byte length. |
Supported encodings
'utf8'/'utf-8' (default), 'hex', 'base64', 'base64url', 'latin1'/'binary'/'ascii'. Any other encoding throws.
'base64url' is the URL-safe, unpadded base64 variant (RFC 4648). +// become -/_ and trailing = padding is dropped. It works both for encoding (buf.toString('base64url')) and decoding (Buffer.from(str, 'base64url')), and is also accepted as a digest() output encoding by crypto. Useful for OAuth flows (e.g. PKCE code_challenge, DPoP ath).
Not available
buf.slice, buf.write, buf.readUInt*/writeUInt*, buf.indexOf, indexed access (buf[0]), streaming, etc. If you need byte-level manipulation, work with Buffer.from(...).toString('hex') and back.
const b64 = Buffer.from("Grüße", "utf-8").toString("base64");
const back = Buffer.from(b64, "base64").toString("utf-8"); // "Grüße"
A Buffer returned as the result arrives at the caller as { "type": "Buffer", "data": [ ...bytes ] } - identical to Node's Buffer#toJSON()
parseXML(xmlString, options?)
Parses XML to a JavaScript object using fast-xml-parser. XXE is disabled (processEntities: false) and cannot be re-enabled. External/entity expansion is blocked. options are passed through to the parser otherwise.
const obj = parseXML("<root><id>42</id></root>");
ocpReturn(obj.root.id);
JWT signing
Two equivalent entry points, both backed by jsonwebtoken.sign:
// Global
const token = signJwt({ sub: params.userId }, secret, { expiresIn: "1h" });
// Via require - ONLY sign is available
const { sign } = require('jsonwebtoken');
const token2 = sign({ sub: params.userId }, secret, { algorithm: "HS256" });
verify and decode are not provided. Signing keys/secrets must be passed in (e.g. via params/ocpvars); do not hardcode secrets in scripts.
crypto (host-backed facade)
require('crypto') returns a facade that bridges to the host's real Node crypto. Inputs/outputs cross the boundary as hex internally; you use the normal API. The supported surface is driven by real production usage. It is a subset, listed exhaustively below.
crypto is also available as a bare global. crypto.randomUUID() works without require('crypto'), matching Node's global. It is the same facade object either way, so the supported subset below applies identically. Note this global mirrors node:crypto (this facade), not the browser WebCrypto API, so crypto.subtle is not present.
Supported
Hashing
const crypto = require('crypto');
const h = crypto.createHash('sha256').update('data').digest('hex');
-
createHash(algorithm)→.update(data, inputEncoding?)→.digest(outputEncoding?) -
.update(...)is chainable and may be called multiple times. -
digest()with no encoding returns aBuffer;'hex'/'base64'/'base64url'etc. return a string.
HMAC
const mac = crypto.createHmac('sha256', key).update(msg).digest('hex');
-
createHmac(algorithm, key)-keymay be a string orBuffer.
Random
const bytes = crypto.randomBytes(16); // returns a Buffer
const id = crypto.randomUUID(); // returns a string
Symmetric ciphers (block modes)
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let out = cipher.update(plaintext, 'utf8', 'hex');
out += cipher.final('hex');
-
createCipheriv(algorithm, key, iv)/createDecipheriv(algorithm, key, iv) -
Tested block modes:
aes-128-ecb,aes-256-cbc(and similar block modes). -
ivmay benull/empty for modes without an IV (e.g. ECB). -
setAutoPadding(false)is supported.
Buffering note
Input is buffered and the whole operation runs at final(). update() returns an empty chunk ('' or empty Buffer), so the idiomatic out = update(...); out += final(...) produces the correct, byte-identical result. Do not rely on update() returning partial ciphertext.
Signing (one-shot)
const sig = crypto.sign('sha256', Buffer.from(data), {
key: privatePem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 32,
});
-
crypto.sign(algorithm, data, key)-keymay be a PEM string or an object
{ key, padding, saltLength }. -
crypto.constantsis available (e.g.RSA_PKCS1_PSS_PADDING).
Not supported
These have no implementation and will fail, design around them:
-
AEAD / GCM modes and authentication tags (
getAuthTag/setAuthTag). -
Streaming
createSign(...)/createVerify(...)and signature
verification of any kind. -
Key derivation:
pbkdf2,pbkdf2Sync,scrypt,hkdf. -
Key generation:
generateKeyPair*,createPrivateKey, etc. -
getRandomValues(browser API),getCiphers,getHashes, and other
introspection helpers.
If you need an unsupported primitive, raise it with the platform team rather
than working around it. The facade is intentionally minimal.
Quick reference
|
Need |
Use |
|---|---|
|
Read request input |
|
|
Return a value |
|
|
Log / debug |
|
|
Hash / HMAC / random / cipher / sign |
|
|
Encode/decode bytes & base64/base64url/hex |
|
|
Sign a JWT |
|
|
Parse XML (XXE-safe) |
|
|
Render a template |
|
Common pitfalls
-
Don't expect Node globals (
process,fs,setTimeout,fetch), they
don't exist here. -
Don't
requireanything outside the four allowed names. -
Don't return functions or non-serializable objects.
-
Don't rely on cipher
update()returning partial output. -
Don't expect
jsonwebtoken.verify/decodeor any crypto verification. -
Keep work within the time and memory limits.