@nekzus/liop 1.2.0-alpha.10 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/crypto/logic-image-id.js +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/sandbox/wasi.js +42 -13
- package/dist/server/index.d.ts +7 -2
- package/dist/server/index.js +32 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -301,7 +301,7 @@ const server = new LiopServer(info, {
|
|
|
301
301
|
The following shows a complete Logic-Injection-on-Origin execution cycle (handled internally by the SDK):
|
|
302
302
|
|
|
303
303
|
```
|
|
304
|
-
1. LLM generates JavaScript analysis code wrapped in
|
|
304
|
+
1. LLM generates JavaScript analysis code wrapped in @LIOP / @END boundaries
|
|
305
305
|
2. LiopServer receives the payload via tools/call (JSON-RPC or direct)
|
|
306
306
|
3. Guardian AST inspects for sandbox escapes (zero-time heuristic analysis)
|
|
307
307
|
4. Code executes inside a V8 isolate with CPU fuel limits (no Node.js globals)
|
|
@@ -4,7 +4,7 @@ import crypto from "node:crypto";
|
|
|
4
4
|
* proxy logic embeds a full envelope inside JSON strings; `^` per line would
|
|
5
5
|
* incorrectly treat that as the document root and desync ImageID vs the worker.
|
|
6
6
|
*/
|
|
7
|
-
const TOP_LEVEL_ENVELOPE = /^\s
|
|
7
|
+
const TOP_LEVEL_ENVELOPE = /^\s*@LIOP\{[^}]+\}\n?([\s\S]*?)\n?@END\s*$/;
|
|
8
8
|
export function normalizeLogicSource(logicUtf8) {
|
|
9
9
|
const match = logicUtf8.match(TOP_LEVEL_ENVELOPE);
|
|
10
10
|
if (match?.[1] !== undefined) {
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare enum ErrorCode {
|
|
2
|
+
CapabilityViolation = "CapabilityViolation",
|
|
3
|
+
SandboxEscape = "SandboxEscape",
|
|
4
|
+
PiiLeak = "PiiLeak",
|
|
5
|
+
InvalidIntent = "InvalidIntent",
|
|
6
|
+
Throttled = "Throttled",
|
|
7
|
+
ZkVerificationFailed = "ZkVerificationFailed",
|
|
8
|
+
MeshUnavailable = "MeshUnavailable",
|
|
9
|
+
ConnectionFailed = "ConnectionFailed"
|
|
10
|
+
}
|
|
11
|
+
export declare class LiopError extends Error {
|
|
12
|
+
readonly code: ErrorCode;
|
|
13
|
+
constructor(code: ErrorCode, message: string);
|
|
14
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export var ErrorCode;
|
|
2
|
+
(function (ErrorCode) {
|
|
3
|
+
ErrorCode["CapabilityViolation"] = "CapabilityViolation";
|
|
4
|
+
ErrorCode["SandboxEscape"] = "SandboxEscape";
|
|
5
|
+
ErrorCode["PiiLeak"] = "PiiLeak";
|
|
6
|
+
ErrorCode["InvalidIntent"] = "InvalidIntent";
|
|
7
|
+
ErrorCode["Throttled"] = "Throttled";
|
|
8
|
+
ErrorCode["ZkVerificationFailed"] = "ZkVerificationFailed";
|
|
9
|
+
ErrorCode["MeshUnavailable"] = "MeshUnavailable";
|
|
10
|
+
ErrorCode["ConnectionFailed"] = "ConnectionFailed";
|
|
11
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
12
|
+
export class LiopError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "LiopError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/sandbox/wasi.js
CHANGED
|
@@ -101,36 +101,65 @@ export class WasiSandbox {
|
|
|
101
101
|
else {
|
|
102
102
|
// Path B: Hardened V8 Isolate Fallback
|
|
103
103
|
// Uses node:vm with zero-prototype objects to prevent prototype pollution escapes.
|
|
104
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for Sandbox global poisoning
|
|
104
105
|
const sandboxEnv = Object.create(null); // Isolated global object
|
|
105
106
|
const env = { records, ...inputs };
|
|
107
|
+
// Explicitly poison Node.js escape vectors in the context
|
|
108
|
+
sandboxEnv.require = undefined;
|
|
109
|
+
sandboxEnv.process = undefined;
|
|
110
|
+
sandboxEnv.global = undefined;
|
|
111
|
+
sandboxEnv.globalThis = undefined;
|
|
112
|
+
sandboxEnv.Buffer = undefined;
|
|
113
|
+
sandboxEnv.setTimeout = undefined;
|
|
114
|
+
sandboxEnv.setInterval = undefined;
|
|
115
|
+
sandboxEnv.setImmediate = undefined;
|
|
116
|
+
sandboxEnv.queueMicrotask = undefined;
|
|
117
|
+
sandboxEnv.eval = undefined;
|
|
118
|
+
sandboxEnv.Function = undefined;
|
|
106
119
|
// Inject strictly monitored globals
|
|
107
120
|
sandboxEnv.records = JSON.parse(JSON.stringify(records)); // Deep copy safety
|
|
108
121
|
sandboxEnv.env = JSON.parse(JSON.stringify(env));
|
|
109
122
|
for (const [key, value] of Object.entries(inputs)) {
|
|
110
123
|
sandboxEnv[key] = JSON.parse(JSON.stringify(value));
|
|
111
124
|
}
|
|
125
|
+
// Freeze the sandbox context to prevent mutation (SEC-GAP-1)
|
|
126
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for recursive deep freeze of unknown data
|
|
127
|
+
const deepFreeze = (obj) => {
|
|
128
|
+
if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
|
|
129
|
+
Object.freeze(obj);
|
|
130
|
+
for (const key of Object.keys(obj)) {
|
|
131
|
+
deepFreeze(obj[key]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return obj;
|
|
135
|
+
};
|
|
136
|
+
deepFreeze(sandboxEnv.records);
|
|
137
|
+
deepFreeze(sandboxEnv.env);
|
|
138
|
+
// Prevent property addition/modification on global scope
|
|
139
|
+
for (const key of Object.keys(sandboxEnv)) {
|
|
140
|
+
Object.defineProperty(sandboxEnv, key, {
|
|
141
|
+
writable: false,
|
|
142
|
+
configurable: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
112
145
|
// LIOP Execution Wrapper
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
146
|
+
// Host-side logic transformation to avoid 'new Function' in sandbox
|
|
147
|
+
let processedLogic = String(compiledLogic);
|
|
148
|
+
if (/^\s*return\s/m.test(processedLogic) ||
|
|
149
|
+
!processedLogic.includes("function liop_main")) {
|
|
150
|
+
if (!processedLogic.includes("function liop_main")) {
|
|
151
|
+
processedLogic = `function liop_main(env) {\n${processedLogic}\n}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
116
154
|
const scriptCode = `
|
|
117
155
|
(function() {
|
|
118
156
|
try {
|
|
119
|
-
${
|
|
157
|
+
${processedLogic}
|
|
120
158
|
if (typeof liop_main === 'function') {
|
|
121
159
|
return liop_main(env);
|
|
122
160
|
}
|
|
123
161
|
return "ERR_NO_ENTRY_POINT";
|
|
124
162
|
} catch(e) {
|
|
125
|
-
if (e instanceof SyntaxError && /Illegal return statement/i.test(e.message)) {
|
|
126
|
-
// Bare-return pattern: wrap the logic as a function body
|
|
127
|
-
try {
|
|
128
|
-
const __liop_fn = new Function('env', 'records', ${JSON.stringify(String(compiledLogic))});
|
|
129
|
-
return __liop_fn(env, env.records);
|
|
130
|
-
} catch(e2) {
|
|
131
|
-
return "LogicError: " + e2.message;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
163
|
return "LogicError: " + e.message;
|
|
135
164
|
}
|
|
136
165
|
})();
|
package/dist/server/index.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface LiopServerOptions {
|
|
|
24
24
|
executionTypes?: string[];
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
export interface AggregationPolicy {
|
|
28
|
+
/** Maximum number of object-type array elements allowed (default: 10) */
|
|
29
|
+
maxOutputRows?: number;
|
|
30
|
+
/** Allow arrays containing only primitive values (default: true) */
|
|
31
|
+
allowPrimitiveArrays?: boolean;
|
|
32
|
+
}
|
|
27
33
|
export interface LogicExecutionPolicy {
|
|
28
34
|
/**
|
|
29
35
|
* Validate the business payload returned by sandbox logic (post-execution).
|
|
@@ -33,7 +39,7 @@ export interface LogicExecutionPolicy {
|
|
|
33
39
|
/**
|
|
34
40
|
* Enforce aggregation-first heuristics (preflight + post-check).
|
|
35
41
|
*/
|
|
36
|
-
enforceAggregationFirst?: boolean;
|
|
42
|
+
enforceAggregationFirst?: boolean | AggregationPolicy;
|
|
37
43
|
/**
|
|
38
44
|
* Optional additional deny patterns checked against extracted logic source.
|
|
39
45
|
*/
|
|
@@ -58,7 +64,6 @@ export declare class LiopServer {
|
|
|
58
64
|
private rpcServer;
|
|
59
65
|
private boundPort;
|
|
60
66
|
private sessions;
|
|
61
|
-
private static readonly LIOP_LOGIC_REGEX;
|
|
62
67
|
private static readonly LIOP_COMPACT_REGEX;
|
|
63
68
|
private extractLogic;
|
|
64
69
|
private parseUnknownJson;
|
package/dist/server/index.js
CHANGED
|
@@ -40,14 +40,9 @@ export class LiopServer {
|
|
|
40
40
|
rpcServer = null;
|
|
41
41
|
boundPort = null;
|
|
42
42
|
sessions = new Map();
|
|
43
|
-
static LIOP_LOGIC_REGEX = /\s*LIOP_MAGIC:0x00FF\s*\n?\s*MANIFEST:(?<manifest>\{[\s\S]*?\})\s*\n?\s*---BEGIN_LOGIC---\n?(?<logic>[\s\S]*?)\n?---END_LOGIC---/m;
|
|
44
43
|
// Compact envelope: @LIOP{target,name}\n<code>\n@END
|
|
45
44
|
static LIOP_COMPACT_REGEX = /@LIOP\{(?<target>[^,}]+)(?:,(?<name>[^}]*))?\}\n(?<logic>[\s\S]*?)\n@END/m;
|
|
46
45
|
extractLogic(payload) {
|
|
47
|
-
const match = payload.match(LiopServer.LIOP_LOGIC_REGEX);
|
|
48
|
-
if (match?.groups?.logic)
|
|
49
|
-
return match.groups.logic.trim();
|
|
50
|
-
// Compact envelope fallback — same execution, fewer tokens
|
|
51
46
|
const compact = payload.match(LiopServer.LIOP_COMPACT_REGEX);
|
|
52
47
|
return compact?.groups?.logic ? compact.groups.logic.trim() : null;
|
|
53
48
|
}
|
|
@@ -101,7 +96,7 @@ export class LiopServer {
|
|
|
101
96
|
}
|
|
102
97
|
}
|
|
103
98
|
if (policy.enforceAggregationFirst &&
|
|
104
|
-
this.violatesAggregationFirstPolicy(this.unwrapForAggregationPolicyScan(parsed))) {
|
|
99
|
+
this.violatesAggregationFirstPolicy(this.unwrapForAggregationPolicyScan(parsed), policy.enforceAggregationFirst)) {
|
|
105
100
|
return "Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
106
101
|
}
|
|
107
102
|
return null;
|
|
@@ -147,13 +142,13 @@ export class LiopServer {
|
|
|
147
142
|
const joined = texts.length === 1 ? texts[0] : texts.join("\n");
|
|
148
143
|
return this.unwrapForAggregationPolicyScan(joined);
|
|
149
144
|
}
|
|
150
|
-
violatesAggregationFirstPolicy(input) {
|
|
145
|
+
violatesAggregationFirstPolicy(input, policyObj) {
|
|
151
146
|
if (typeof input === "string") {
|
|
152
147
|
const trimmed = input.trim();
|
|
153
148
|
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
154
149
|
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
155
150
|
try {
|
|
156
|
-
return this.violatesAggregationFirstPolicy(JSON.parse(trimmed));
|
|
151
|
+
return this.violatesAggregationFirstPolicy(JSON.parse(trimmed), policyObj);
|
|
157
152
|
}
|
|
158
153
|
catch {
|
|
159
154
|
return false;
|
|
@@ -162,14 +157,32 @@ export class LiopServer {
|
|
|
162
157
|
return false;
|
|
163
158
|
}
|
|
164
159
|
if (Array.isArray(input)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
const maxRows = typeof policyObj === "object" &&
|
|
161
|
+
typeof policyObj.maxOutputRows === "number"
|
|
162
|
+
? policyObj.maxOutputRows
|
|
163
|
+
: 10;
|
|
164
|
+
const allowPrimitives = typeof policyObj === "object" &&
|
|
165
|
+
typeof policyObj.allowPrimitiveArrays === "boolean"
|
|
166
|
+
? policyObj.allowPrimitiveArrays
|
|
167
|
+
: true;
|
|
168
|
+
if (input.length > 0 &&
|
|
169
|
+
input.every((item) => typeof item === "object" && item !== null)) {
|
|
170
|
+
// Treat tabular row export as non-aggregated leakage risk if above threshold.
|
|
171
|
+
if (input.length > maxRows) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
return input.some((item) => this.violatesAggregationFirstPolicy(item, policyObj));
|
|
175
|
+
}
|
|
176
|
+
if (input.length > 0 &&
|
|
177
|
+
input.every((item) => typeof item !== "object" || item === null)) {
|
|
178
|
+
if (!allowPrimitives)
|
|
179
|
+
return true;
|
|
180
|
+
return false;
|
|
168
181
|
}
|
|
169
|
-
return input.some((item) => this.violatesAggregationFirstPolicy(item));
|
|
182
|
+
return input.some((item) => this.violatesAggregationFirstPolicy(item, policyObj));
|
|
170
183
|
}
|
|
171
184
|
if (input && typeof input === "object") {
|
|
172
|
-
return Object.values(input).some((value) => this.violatesAggregationFirstPolicy(value));
|
|
185
|
+
return Object.values(input).some((value) => this.violatesAggregationFirstPolicy(value, policyObj));
|
|
173
186
|
}
|
|
174
187
|
return false;
|
|
175
188
|
}
|
|
@@ -242,20 +255,13 @@ export class LiopServer {
|
|
|
242
255
|
"LIOP v1 Envelope Specification",
|
|
243
256
|
"================================",
|
|
244
257
|
"",
|
|
245
|
-
"
|
|
258
|
+
"FORMAT:",
|
|
246
259
|
"",
|
|
247
|
-
"Compact:",
|
|
260
|
+
"Compact Envelope:",
|
|
248
261
|
" @LIOP{wasi_v1,TaskName}",
|
|
249
262
|
" <JavaScript code>",
|
|
250
263
|
" @END",
|
|
251
264
|
"",
|
|
252
|
-
"Standard:",
|
|
253
|
-
" LIOP_MAGIC:0x00FF",
|
|
254
|
-
' MANIFEST:{"target":"wasi_v1","name":"TaskName","integrity_checks":true}',
|
|
255
|
-
" ---BEGIN_LOGIC---",
|
|
256
|
-
" <JavaScript code>",
|
|
257
|
-
" ---END_LOGIC---",
|
|
258
|
-
"",
|
|
259
265
|
"RUNTIME ENVIRONMENT:",
|
|
260
266
|
"- env.records: Array of data objects from the origin",
|
|
261
267
|
"- Must use 'return' to output results",
|
|
@@ -406,7 +412,7 @@ export class LiopServer {
|
|
|
406
412
|
content: [
|
|
407
413
|
{
|
|
408
414
|
type: "text",
|
|
409
|
-
text:
|
|
415
|
+
text: "Error: Malformed payload. Missing @LIOP boundary.\\nYou MUST wrap your logic exactly like this:\\n\\n@LIOP{wasi_v1,DynamicAudit}\\n// Your JS code here\\n@END",
|
|
410
416
|
},
|
|
411
417
|
],
|
|
412
418
|
isError: true,
|
|
@@ -509,13 +515,11 @@ Your objective is to perform secure Logic-on-Origin injections. You must process
|
|
|
509
515
|
INDUSTRIAL CONSTRAINTS & PROTOCOL RULES:
|
|
510
516
|
1. DATA PRIVACY: NEVER attempt to export Personally Identifiable Information (PII). The LIOP Egress Shield will block any response containing raw IDs, names, or addresses.
|
|
511
517
|
2. AGGREGATION FIRST: Always prefer returning counts, averages, or anonymized summaries.
|
|
512
|
-
3. PAYLOAD ENCAPSULATION: Your JavaScript payloads MUST strictly adhere to the
|
|
518
|
+
3. PAYLOAD ENCAPSULATION: Your JavaScript payloads MUST strictly adhere to the Compact Envelope. DO NOT include markdown backticks or leading text inside the 'payload' argument.
|
|
513
519
|
Structure:
|
|
514
|
-
|
|
515
|
-
MANIFEST:{"target":"wasi_v1","name":"AnalysisTask","integrity_checks":true}
|
|
516
|
-
---BEGIN_LOGIC---
|
|
520
|
+
@LIOP{wasi_v1,AnalysisTask}
|
|
517
521
|
// Your JS Code Here
|
|
518
|
-
|
|
522
|
+
@END
|
|
519
523
|
4. RUNTIME SCOPE: The execution environment provides a global 'env' object. Use 'env.records' to access the target dataset.
|
|
520
524
|
5. LOCALIZATION: Format all JSON response keys in the language used by the user in their query (e.g., use Spanish keys if the query is in Spanish).
|
|
521
525
|
6. SCHEMA RIGIDITY: Only use fields defined in the 'Data Dictionary'. Usage of non-existent fields will trigger a sandbox runtime exception.${this.activeSchema
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nekzus/liop",
|
|
3
|
-
"version": "1.2.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Official SDK for Logic-Injection-on-Origin Protocol (LIOP). Deploy Logic-on-Origin with WebAssembly at gRPC speed and bidirectional MCP compatibility.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|