Governance: Safe Execution of Untrusted Logic

Adopt AI Agents without exposing your database to the Wild West.


The Problem

You want to run user-submitted code. Or LLM-generated agents. Or third-party integrations. But JavaScript has no sandbox, no resource limits, and no capability controls.

Every eval is a security incident waiting to happen. Every webhook is a potential DoS vector. Every "plugin system" is an attack surface.

The choice has been: accept the risk, or don't run untrusted code at all.


The TJS Win

"The Platform defines the capabilities. The Guest cannot escape them."

TJS is the language you use to build the trusted infrastructure—your servers, your APIs, your capability boundaries. It's designed to never crash, even when guests misbehave.

Monadic Errors: Exceptions are for Amateurs

No unhandled exceptions. Ever.

const result = createUser({ name: 123 })
// Returns: { $error: true, message: 'Invalid input', path: 'createUser.input' }

if (result.$error) {
  // Handle gracefully - log, retry, return to caller
  return { status: 'invalid', details: result }
}

Type failures return error objects, not exceptions. The host survives anything the guest throws at it.

TJS changes the physics of failure. In every other language, a runtime type error is a catastrophe—uncaught exception, stack trace, 500 server error. In TJS, a type error is just data.

Most developers see: TypeError: Cannot read property 'x' of undefined at anonymous:5:12

Translation: "Something broke somewhere. Good luck."

In TJS Safe Mode, the error looks like:

{
  $error: true,
  message: "Expected 'positive number', got -5",
  path: "calculateTax.input.price"
}

Translation: "The function calculateTax received a bad price."

You trace the error to the source (the caller), not the symptom (the crash).

This means your "Universal Endpoint" cannot crash due to bad data. It simply refuses the contract and tells the caller exactly why. Contracts are for pros.

Full Introspection

Every function carries its type metadata at runtime:

console.log(createUser.__tjs)
// {
//   params: { input: { type: { kind: 'object', shape: { name: 'string' } } } },
//   returns: { kind: 'object', shape: { id: 'number' } }
// }

Audit trail of what code does what. No runtime surprises.

Minimal Supply Chain

Less code to audit. Smaller attack surface.


The AJS Win

"Every agent execution is gas-limited, auditable, and sandboxed. No infinite loops. No data exfiltration."

AJS is the language for untrusted code—user scripts, LLM-generated agents, third-party logic. It compiles to JSON and runs in an isolated VM with strict resource controls.

Capability-Based Security

The VM starts with zero capabilities. No network. No storage. No filesystem. Nothing.

You grant exactly what each agent needs:

const capabilities = {
  fetch: createFetchCapability({
    allowedHosts: ['api.example.com'], // Only these domains
  }),
  store: createReadOnlyStore(), // Read but not write
  // No LLM capability - this agent can't call AI
}

await vm.run(agent, args, { capabilities })

If you don't grant it, the agent can't do it.

Fuel Metering

Every operation costs fuel. Loops can't run forever:

const result = await vm.run(agent, args, {
  fuel: 1000, // CPU budget
  timeoutMs: 5000, // Wall-clock limit
})

if (result.fuelExhausted) {
  // Agent tried to run forever - stopped safely
}

Large allocations cost more fuel. Memory bombs exhaust their budget before they explode.

Timeout Enforcement

Fuel protects against CPU abuse. Timeouts protect against I/O abuse:

await vm.run(agent, args, {
  fuel: 1000,
  timeoutMs: 5000, // 5 second hard limit
})

Slow network calls can't hang your servers.


The Universal Endpoint: One Security Model, Front to Back

Most systems mirror their security. TJS keeps one copy of it.

Because the VM is environment-agnostic and every capability is injected, the same agent program runs unchanged in your data center and inside the browser client. Not a client SDK that re-implements the server's rules and drifts out of sync — the same program, under the same capability + fuel + RBAC envelope, enforced identically in both places.

The RBAC rules themselves are TJS — portable, serializable data, not compiled server code — so a request denied in the browser is denied for the same reason, by the same rule, as it would be on the server. You audit one security model, not two that are supposed to agree.

The payoff in production: tools come in matched pairs. A client getRecords answers from data already loaded in the browser — zero round-trips — and falls back to the server-side getRecords only on a genuine miss. Same contract, same authorization, and the fallback was never written into the program; it lives in the atom. Code travels to the data instead of the data travelling to the code.

Two security surfaces to keep in sync is two surfaces to get wrong. One is one.

→ Full architecture: The Universal Endpoint


Threat Model

Threat Defense
Infinite loops Fuel exhaustion - every op costs gas
Memory bombs Proportional charging - large allocs cost more
SSRF URL allowlists in fetch capability
Prototype pollution Blocked property access (__proto__, constructor)
Code injection AST nodes, not string eval
ReDoS Suspicious regex rejection
Data exfiltration Zero capabilities by default
Resource exhaustion Per-request fuel + timeout limits

What the Platform Controls

Resource Mechanism
CPU Fuel budget
Memory Proportional charging
Time Timeout enforcement
Network Capability allowlists
Storage Capability scoping
Recursion Depth protocol

What the Platform Trusts


Compliance

Auditable Execution

Every agent run can produce a trace:

const { result, trace } = await vm.run(agent, args, { trace: true })

// trace contains:
// - Every operation executed
// - Inputs and outputs
// - Fuel consumption
// - Timestamps

Full visibility into what untrusted code actually did.

Per-Request Limits

await vm.run(agent, args, {
  fuel: 1000,           // CPU budget per request
  timeoutMs: 5000,      // Wall-clock limit
  capabilities: {...}   // Scoped access
})

No single request can monopolize resources.

Per-Key Scoping

app.post('/execute', async (req, res) => {
  const capabilities = getCapabilitiesForApiKey(req.apiKey)
  // Different API keys get different permissions
  await vm.run(agent, args, { capabilities })
})

Tenant isolation at the capability level.

Test Coverage


Who This Is For


The Architecture

┌─────────────────────────────────────────────────┐
│                  TJS Platform                    │
│  (Your trusted code - servers, APIs, capabilities)│
├─────────────────────────────────────────────────┤
│                                                  │
│   ┌─────────────────────────────────────────┐   │
│   │              AJS Sandbox                 │   │
│   │  (Untrusted code - agents, user scripts) │   │
│   │                                          │   │
│   │  • Zero capabilities by default          │   │
│   │  • Fuel-limited execution                │   │
│   │  • Timeout enforcement                   │   │
│   │  • No direct I/O                         │   │
│   └─────────────────────────────────────────┘   │
│                      │                           │
│           Capability Boundary                    │
│                      │                           │
│   ┌─────────────────────────────────────────┐   │
│   │         Granted Capabilities             │   │
│   │  fetch: allowlist only                   │   │
│   │  store: read-only or scoped             │   │
│   │  llm: if needed                         │   │
│   └─────────────────────────────────────────┘   │
│                                                  │
└─────────────────────────────────────────────────┘

The guest can only reach resources you explicitly provide. Everything else is blocked.


Learn More