@nekzus/liop 1.2.0 → 2.0.0-alpha.1
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 +88 -61
- package/dist/bridge/stream.js +14 -6
- package/dist/client/index.js +7 -7
- package/dist/crypto/verifier.d.ts +1 -1
- package/dist/crypto/verifier.js +2 -1
- package/dist/gateway/router.d.ts +7 -0
- package/dist/gateway/router.js +21 -3
- package/dist/sandbox/guardian.js +27 -4
- package/dist/sandbox/wasi.js +25 -0
- package/dist/security/zk.d.ts +1 -1
- package/dist/security/zk.js +11 -1
- package/dist/server/index.d.ts +23 -1
- package/dist/server/index.js +140 -30
- package/dist/server/ner-scanner.d.ts +29 -0
- package/dist/server/ner-scanner.js +141 -0
- package/dist/server/pii.d.ts +27 -1
- package/dist/server/pii.js +167 -5
- package/dist/workers/logic-execution.js +4 -2
- package/dist/workers/zk-verifier.d.ts +2 -0
- package/dist/workers/zk-verifier.js +15 -1
- package/package.json +4 -3
package/dist/server/pii.js
CHANGED
|
@@ -159,14 +159,136 @@ export const PII_PRESETS = {
|
|
|
159
159
|
export class PiiScanner {
|
|
160
160
|
patterns;
|
|
161
161
|
forbiddenKeysSet;
|
|
162
|
-
|
|
162
|
+
nerScanner;
|
|
163
|
+
/**
|
|
164
|
+
* Safelist of keys that contain forbidden substrings but are NOT PII.
|
|
165
|
+
* Prevents false positives from fuzzy matching (e.g., "grid" contains "id").
|
|
166
|
+
*/
|
|
167
|
+
static KEY_SAFELIST = new Set([
|
|
168
|
+
// Common words containing "id" substring
|
|
169
|
+
"grid",
|
|
170
|
+
"video",
|
|
171
|
+
"android",
|
|
172
|
+
"identity",
|
|
173
|
+
"provide",
|
|
174
|
+
"override",
|
|
175
|
+
"validate",
|
|
176
|
+
"hidden",
|
|
177
|
+
"widget",
|
|
178
|
+
"guidelines",
|
|
179
|
+
"beside",
|
|
180
|
+
"guideline",
|
|
181
|
+
"outside",
|
|
182
|
+
"inside",
|
|
183
|
+
"collide",
|
|
184
|
+
"decide",
|
|
185
|
+
"divide",
|
|
186
|
+
"aside",
|
|
187
|
+
"ride",
|
|
188
|
+
"side",
|
|
189
|
+
"wide",
|
|
190
|
+
"hide",
|
|
191
|
+
"tide",
|
|
192
|
+
"pride",
|
|
193
|
+
"bride",
|
|
194
|
+
"slide",
|
|
195
|
+
"guide",
|
|
196
|
+
"stride",
|
|
197
|
+
"oxide",
|
|
198
|
+
"dioxide",
|
|
199
|
+
"suicide",
|
|
200
|
+
"homicide",
|
|
201
|
+
"pesticide",
|
|
202
|
+
"valid",
|
|
203
|
+
"invalid",
|
|
204
|
+
"void",
|
|
205
|
+
"avoid",
|
|
206
|
+
// Common words containing "name" substring
|
|
207
|
+
"diagnosis",
|
|
208
|
+
"medication",
|
|
209
|
+
"namespace",
|
|
210
|
+
"namesake",
|
|
211
|
+
"rename",
|
|
212
|
+
"filename",
|
|
213
|
+
"hostname",
|
|
214
|
+
"typename",
|
|
215
|
+
"unnamed",
|
|
216
|
+
"renamed",
|
|
217
|
+
// Common words containing "phone" substring
|
|
218
|
+
"phonetic",
|
|
219
|
+
"phoneme",
|
|
220
|
+
"microphone",
|
|
221
|
+
"headphone",
|
|
222
|
+
"telephone",
|
|
223
|
+
"saxophone",
|
|
224
|
+
"smartphone",
|
|
225
|
+
// Common words containing "address" substring
|
|
226
|
+
"streetview",
|
|
227
|
+
"addressable",
|
|
228
|
+
"addressing",
|
|
229
|
+
// Common words containing "city" substring
|
|
230
|
+
"cityscape",
|
|
231
|
+
"electricity",
|
|
232
|
+
"capacity",
|
|
233
|
+
"velocity",
|
|
234
|
+
"opacity",
|
|
235
|
+
// Common technical terms
|
|
236
|
+
"timestamp",
|
|
237
|
+
"timezone",
|
|
238
|
+
// LIOP Protocol Internal Keys (must never be blocked)
|
|
239
|
+
"image_id",
|
|
240
|
+
"computation_result",
|
|
241
|
+
"zk_receipt",
|
|
242
|
+
"testid",
|
|
243
|
+
"toolid",
|
|
244
|
+
"sessionid",
|
|
245
|
+
"peerid",
|
|
246
|
+
"nodeid",
|
|
247
|
+
"requestid",
|
|
248
|
+
"correlationid",
|
|
249
|
+
"traceid",
|
|
250
|
+
"spanid",
|
|
251
|
+
]);
|
|
252
|
+
/**
|
|
253
|
+
* Short forbidden tokens (< 4 chars) that require boundary-aware matching.
|
|
254
|
+
* Uses regex boundary detection to avoid false positives.
|
|
255
|
+
*/
|
|
256
|
+
shortTokenBoundaryPatterns;
|
|
257
|
+
/**
|
|
258
|
+
* Long forbidden tokens (>= 4 chars) that use substring containment.
|
|
259
|
+
*/
|
|
260
|
+
longForbiddenTokens;
|
|
261
|
+
constructor(patterns = [], forbiddenKeys = [], nerScanner) {
|
|
163
262
|
this.patterns = patterns;
|
|
164
|
-
// Optimizes large recursive evaluations using O(1) continuous key lookup
|
|
165
263
|
this.forbiddenKeysSet = new Set(forbiddenKeys.map((k) => k.toLowerCase()));
|
|
264
|
+
this.nerScanner = nerScanner ?? null;
|
|
265
|
+
// Pre-compute fuzzy matching structures for performance
|
|
266
|
+
this.shortTokenBoundaryPatterns = new Map();
|
|
267
|
+
this.longForbiddenTokens = [];
|
|
268
|
+
for (const token of this.forbiddenKeysSet) {
|
|
269
|
+
if (token.length < 4) {
|
|
270
|
+
// Short tokens: require word boundary (camelCase, snake_case, kebab-case, or exact)
|
|
271
|
+
// "id" matches: "patientId", "record_id", "user-id", "id"
|
|
272
|
+
// "id" does NOT match: "grid", "video", "android"
|
|
273
|
+
this.shortTokenBoundaryPatterns.set(token, new RegExp(`(?:^|[_-])${token}(?:$|[_-])|` + // snake/kebab boundary
|
|
274
|
+
`(?:^|[a-z])${token.charAt(0).toUpperCase()}${token.slice(1)}|` + // camelCase boundary (e.g., patientId)
|
|
275
|
+
`^${token}$`, // exact match
|
|
276
|
+
"i"));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.longForbiddenTokens.push(token);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
166
282
|
}
|
|
167
283
|
/**
|
|
168
284
|
* Scans any input (string, object, array) for PII violations.
|
|
169
285
|
* Returns the pattern/rule name that triggered the violation, or null if safe.
|
|
286
|
+
*
|
|
287
|
+
* Detection pipeline (fail-fast):
|
|
288
|
+
* 1. Exact key match (O(1) Set lookup)
|
|
289
|
+
* 2. Fuzzy key match (boundary detection for short tokens, substring for long)
|
|
290
|
+
* 3. Regex/algorithmic pattern match on string values
|
|
291
|
+
* 4. NER content scan on string values (if enabled)
|
|
170
292
|
*/
|
|
171
293
|
scan(input, seen = new WeakSet()) {
|
|
172
294
|
if (input === null || input === undefined)
|
|
@@ -189,8 +311,21 @@ export class PiiScanner {
|
|
|
189
311
|
// Silent fallback: It looked like JSON but wasn't valid. Proceed with raw string check.
|
|
190
312
|
}
|
|
191
313
|
}
|
|
192
|
-
//
|
|
193
|
-
|
|
314
|
+
// Check string value against regex patterns
|
|
315
|
+
const patternViolation = this.checkString(input);
|
|
316
|
+
if (patternViolation)
|
|
317
|
+
return patternViolation;
|
|
318
|
+
// Layer 3: NER Content Scan — detect person names in free-text values
|
|
319
|
+
if (this.nerScanner) {
|
|
320
|
+
const nerResult = this.nerScanner.scan(input);
|
|
321
|
+
if (nerResult.detected) {
|
|
322
|
+
const personEntity = nerResult.entities.find((e) => e.type === "person");
|
|
323
|
+
if (personEntity) {
|
|
324
|
+
return `PII Entity Detected: person name "${personEntity.text}"`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
194
329
|
}
|
|
195
330
|
// 2. Recursive Objects/Arrays Scan
|
|
196
331
|
if (typeof input === "object") {
|
|
@@ -207,10 +342,14 @@ export class PiiScanner {
|
|
|
207
342
|
}
|
|
208
343
|
else {
|
|
209
344
|
for (const [key, value] of Object.entries(input)) {
|
|
210
|
-
//
|
|
345
|
+
// Layer 1: Exact key match — O(1) constant time
|
|
211
346
|
if (this.forbiddenKeysSet.has(key.toLowerCase())) {
|
|
212
347
|
return `Forbidden Key: ${key}`;
|
|
213
348
|
}
|
|
349
|
+
// Layer 2: Fuzzy key match — catches aliases and variations
|
|
350
|
+
const fuzzyViolation = this.checkKeyFuzzy(key);
|
|
351
|
+
if (fuzzyViolation)
|
|
352
|
+
return fuzzyViolation;
|
|
214
353
|
// Recurse into values
|
|
215
354
|
const violation = this.scan(value, seen);
|
|
216
355
|
if (violation)
|
|
@@ -220,6 +359,29 @@ export class PiiScanner {
|
|
|
220
359
|
}
|
|
221
360
|
return null;
|
|
222
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Checks a key against fuzzy matching rules.
|
|
364
|
+
* Short tokens use boundary-aware regex; long tokens use substring containment.
|
|
365
|
+
*/
|
|
366
|
+
checkKeyFuzzy(key) {
|
|
367
|
+
const normalized = key.toLowerCase();
|
|
368
|
+
// Skip safelisted keys entirely
|
|
369
|
+
if (PiiScanner.KEY_SAFELIST.has(normalized))
|
|
370
|
+
return null;
|
|
371
|
+
// Short token boundary matching (e.g., "id" in "patientId" but not "grid")
|
|
372
|
+
for (const [token, pattern] of this.shortTokenBoundaryPatterns) {
|
|
373
|
+
if (pattern.test(key)) {
|
|
374
|
+
return `Forbidden Key (fuzzy): ${key} matches boundary pattern "${token}"`;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Long token substring matching (e.g., "name" in "firstName", "names")
|
|
378
|
+
for (const token of this.longForbiddenTokens) {
|
|
379
|
+
if (normalized.includes(token)) {
|
|
380
|
+
return `Forbidden Key (fuzzy): ${key} contains restricted token "${token}"`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
223
385
|
checkString(text) {
|
|
224
386
|
for (const rule of this.patterns) {
|
|
225
387
|
if (typeof rule === "string") {
|
|
@@ -30,9 +30,11 @@ export default async function processLogicExecution(data) {
|
|
|
30
30
|
// 3. Decrypt Inputs
|
|
31
31
|
for (const [key, encValue] of Object.entries(inputs || {})) {
|
|
32
32
|
const valBuffer = Buffer.from(encValue);
|
|
33
|
+
// Extract 12-byte prepended nonce, ciphertext, and 16-byte AuthTag
|
|
34
|
+
const inputNonce = valBuffer.subarray(0, 12);
|
|
33
35
|
const valTag = valBuffer.subarray(-16);
|
|
34
|
-
const valData = valBuffer.subarray(
|
|
35
|
-
const valDecipher = crypto.createDecipheriv("aes-256-gcm", aesKey,
|
|
36
|
+
const valData = valBuffer.subarray(12, -16);
|
|
37
|
+
const valDecipher = crypto.createDecipheriv("aes-256-gcm", aesKey, inputNonce);
|
|
36
38
|
valDecipher.setAuthTag(valTag);
|
|
37
39
|
let valDecrypted = valDecipher.update(valData);
|
|
38
40
|
valDecrypted = Buffer.concat([valDecrypted, valDecipher.final()]);
|
|
@@ -10,6 +10,8 @@ export interface ZkVerificationPayload {
|
|
|
10
10
|
remoteImageIdHex: string;
|
|
11
11
|
/** Cbor-encoded or raw buffer containing the execution Receipt (Journal + Seal) */
|
|
12
12
|
zkReceipt: Uint8Array;
|
|
13
|
+
/** Kyber-derived session secret to verify HMAC signature */
|
|
14
|
+
sessionSecret?: Uint8Array;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Main worker entry point for Piscina.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { parentPort } from "node:worker_threads";
|
|
2
3
|
import { deriveLogicImageDigest } from "../crypto/logic-image-id.js";
|
|
3
4
|
// Ensure this worker is used via Piscina pool
|
|
@@ -12,7 +13,7 @@ function deriveImageId(logicPayload) {
|
|
|
12
13
|
* In a real environment, this delegates to @risc0/verifier or SP1 FFI bindings.
|
|
13
14
|
*/
|
|
14
15
|
async function verifyZkReceipt(payload) {
|
|
15
|
-
const { logicPayload, remoteImageIdHex, zkReceipt } = payload;
|
|
16
|
+
const { logicPayload, remoteImageIdHex, zkReceipt, sessionSecret } = payload;
|
|
16
17
|
// 1. Calculate local ImageID (Integrity Check)
|
|
17
18
|
const localImageId = deriveImageId(logicPayload);
|
|
18
19
|
const localImageIdHex = localImageId.toString("hex");
|
|
@@ -60,6 +61,19 @@ async function verifyZkReceipt(payload) {
|
|
|
60
61
|
catch (_e) {
|
|
61
62
|
return { verified: false, message: "Failed to parse journal data." };
|
|
62
63
|
}
|
|
64
|
+
// 4. Mathematical Verification (HMAC-SHA256)
|
|
65
|
+
if (sessionSecret && sessionSecret.length > 0) {
|
|
66
|
+
const expectedSeal = crypto
|
|
67
|
+
.createHmac("sha256", sessionSecret)
|
|
68
|
+
.update(journal)
|
|
69
|
+
.digest();
|
|
70
|
+
if (!crypto.timingSafeEqual(seal, expectedSeal)) {
|
|
71
|
+
return {
|
|
72
|
+
verified: false,
|
|
73
|
+
message: "Invalid seal: HMAC verification failed.",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
63
77
|
return {
|
|
64
78
|
verified: true,
|
|
65
79
|
message: "HMAC Commitment Verified: Integrity intact.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nekzus/liop",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha.1",
|
|
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",
|
|
@@ -88,10 +88,10 @@
|
|
|
88
88
|
"type": "module",
|
|
89
89
|
"repository": {
|
|
90
90
|
"type": "git",
|
|
91
|
-
"url": "git+https://github.com/Nekzus/
|
|
91
|
+
"url": "git+https://github.com/Nekzus/LIOP.git"
|
|
92
92
|
},
|
|
93
93
|
"bugs": {
|
|
94
|
-
"url": "https://github.com/Nekzus/
|
|
94
|
+
"url": "https://github.com/Nekzus/LIOP/issues"
|
|
95
95
|
},
|
|
96
96
|
"homepage": "https://nekzus-32.mintlify.app/",
|
|
97
97
|
"publishConfig": {
|
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
132
132
|
"@multiformats/multiaddr": "^13.0.1",
|
|
133
133
|
"@opentelemetry/api": "^1.9.1",
|
|
134
|
+
"compromise": "14.15.0",
|
|
134
135
|
"gpt-tokenizer": "^3.4.0",
|
|
135
136
|
"hono": "^4.12.5",
|
|
136
137
|
"it-pipe": "^3.0.1",
|