@nekzus/liop 2.0.0-alpha.1 → 2.0.0-alpha.3

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.
Files changed (124) hide show
  1. package/README.md +30 -20
  2. package/dist/bin/agent.d.ts +0 -1
  3. package/dist/bin/agent.js +5 -306
  4. package/dist/bin/agent.js.map +1 -0
  5. package/dist/{bridge/stream.d.ts → bridge.d.ts} +44 -3
  6. package/dist/bridge.js +2 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/chunk-7MAGL6ON.js +33 -0
  9. package/dist/chunk-7MAGL6ON.js.map +1 -0
  10. package/dist/chunk-ANFXJGMP.js +2 -0
  11. package/dist/chunk-ANFXJGMP.js.map +1 -0
  12. package/dist/chunk-DBXGYHKY.js +2 -0
  13. package/dist/chunk-DBXGYHKY.js.map +1 -0
  14. package/dist/chunk-FW6CICSY.js +29 -0
  15. package/dist/chunk-FW6CICSY.js.map +1 -0
  16. package/dist/chunk-HM77MWB6.js +2 -0
  17. package/dist/chunk-HM77MWB6.js.map +1 -0
  18. package/dist/chunk-HNDVAKEK.js +24 -0
  19. package/dist/chunk-HNDVAKEK.js.map +1 -0
  20. package/dist/chunk-HQZHZM6U.js +2 -0
  21. package/dist/chunk-HQZHZM6U.js.map +1 -0
  22. package/dist/chunk-JBMEAXYU.js +13 -0
  23. package/dist/chunk-JBMEAXYU.js.map +1 -0
  24. package/dist/chunk-LYULZHZO.js +3 -0
  25. package/dist/chunk-LYULZHZO.js.map +1 -0
  26. package/dist/chunk-P52IE4L6.js +2 -0
  27. package/dist/chunk-P52IE4L6.js.map +1 -0
  28. package/dist/chunk-PPCOS2NU.js +2 -0
  29. package/dist/chunk-PPCOS2NU.js.map +1 -0
  30. package/dist/chunk-RWRRBYG4.js +2 -0
  31. package/dist/chunk-RWRRBYG4.js.map +1 -0
  32. package/dist/chunk-S6RJHZV2.js +2 -0
  33. package/dist/chunk-S6RJHZV2.js.map +1 -0
  34. package/dist/chunk-UVTEJYHN.js +2 -0
  35. package/dist/chunk-UVTEJYHN.js.map +1 -0
  36. package/dist/client.d.ts +5 -0
  37. package/dist/client.js +2 -0
  38. package/dist/client.js.map +1 -0
  39. package/dist/{gateway/router.d.ts → gateway.d.ts} +30 -5
  40. package/dist/gateway.js +2 -0
  41. package/dist/gateway.js.map +1 -0
  42. package/dist/{client/index.d.ts → index-CyxNLlz7.d.ts} +24 -5
  43. package/dist/index.d.ts +313 -12
  44. package/dist/index.js +31 -12
  45. package/dist/index.js.map +1 -0
  46. package/dist/kyber-2WDOTUQX.js +2 -0
  47. package/dist/kyber-2WDOTUQX.js.map +1 -0
  48. package/dist/{mesh/node.d.ts → mesh.d.ts} +5 -3
  49. package/dist/mesh.js +2 -0
  50. package/dist/mesh.js.map +1 -0
  51. package/dist/{server/index.d.ts → server.d.ts} +125 -12
  52. package/dist/server.js +2 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/types.d.ts +17 -14
  55. package/dist/types.js +2 -26
  56. package/dist/types.js.map +1 -0
  57. package/dist/{crypto/verifier.d.ts → verifier-DTCD9imJ.d.ts} +3 -1
  58. package/dist/verifier-RQRYXA4C.js +2 -0
  59. package/dist/verifier-RQRYXA4C.js.map +1 -0
  60. package/dist/workers/logic-execution.d.ts +4 -2
  61. package/dist/workers/logic-execution.js +2 -123
  62. package/dist/workers/logic-execution.js.map +1 -0
  63. package/dist/workers/zk-verifier.d.ts +4 -2
  64. package/dist/workers/zk-verifier.js +2 -98
  65. package/dist/workers/zk-verifier.js.map +1 -0
  66. package/package.json +32 -19
  67. package/dist/bridge/index.d.ts +0 -37
  68. package/dist/bridge/index.js +0 -249
  69. package/dist/bridge/stream.js +0 -210
  70. package/dist/client/index.js +0 -275
  71. package/dist/crypto/logic-image-id.d.ts +0 -3
  72. package/dist/crypto/logic-image-id.js +0 -27
  73. package/dist/crypto/verifier.js +0 -97
  74. package/dist/economy/estimator.d.ts +0 -53
  75. package/dist/economy/estimator.js +0 -69
  76. package/dist/economy/index.d.ts +0 -5
  77. package/dist/economy/index.js +0 -3
  78. package/dist/economy/otel.d.ts +0 -38
  79. package/dist/economy/otel.js +0 -100
  80. package/dist/economy/telemetry.d.ts +0 -77
  81. package/dist/economy/telemetry.js +0 -224
  82. package/dist/errors.d.ts +0 -14
  83. package/dist/errors.js +0 -19
  84. package/dist/gateway/hybrid.d.ts +0 -23
  85. package/dist/gateway/hybrid.js +0 -199
  86. package/dist/gateway/router.js +0 -1054
  87. package/dist/mesh/index.d.ts +0 -1
  88. package/dist/mesh/index.js +0 -1
  89. package/dist/mesh/node.js +0 -853
  90. package/dist/prompts/adapters.d.ts +0 -16
  91. package/dist/prompts/adapters.js +0 -55
  92. package/dist/rpc/client.d.ts +0 -22
  93. package/dist/rpc/client.js +0 -40
  94. package/dist/rpc/codec/lpm.d.ts +0 -20
  95. package/dist/rpc/codec/lpm.js +0 -36
  96. package/dist/rpc/crypto/aes.d.ts +0 -22
  97. package/dist/rpc/crypto/aes.js +0 -47
  98. package/dist/rpc/crypto/kyber.d.ts +0 -27
  99. package/dist/rpc/crypto/kyber.js +0 -70
  100. package/dist/rpc/proto.d.ts +0 -2
  101. package/dist/rpc/proto.js +0 -33
  102. package/dist/rpc/server.d.ts +0 -13
  103. package/dist/rpc/server.js +0 -50
  104. package/dist/rpc/tls.d.ts +0 -26
  105. package/dist/rpc/tls.js +0 -54
  106. package/dist/rpc/types.d.ts +0 -28
  107. package/dist/rpc/types.js +0 -5
  108. package/dist/sandbox/guardian.d.ts +0 -18
  109. package/dist/sandbox/guardian.js +0 -58
  110. package/dist/sandbox/wasi.d.ts +0 -36
  111. package/dist/sandbox/wasi.js +0 -233
  112. package/dist/security/guardian.d.ts +0 -22
  113. package/dist/security/guardian.js +0 -52
  114. package/dist/security/zk.d.ts +0 -37
  115. package/dist/security/zk.js +0 -76
  116. package/dist/server/index.js +0 -1047
  117. package/dist/server/ner-scanner.d.ts +0 -29
  118. package/dist/server/ner-scanner.js +0 -141
  119. package/dist/server/pii.d.ts +0 -66
  120. package/dist/server/pii.js +0 -428
  121. package/dist/utils/logger.d.ts +0 -21
  122. package/dist/utils/logger.js +0 -70
  123. package/dist/utils/mcpCompact.d.ts +0 -11
  124. package/dist/utils/mcpCompact.js +0 -29
@@ -1,29 +0,0 @@
1
- /** Single named entity detected by the NER scanner. */
2
- export interface NerEntity {
3
- type: "person" | "place" | "organization";
4
- text: string;
5
- }
6
- /** Result of an NER scan operation. */
7
- export interface NerScanResult {
8
- detected: boolean;
9
- entities: NerEntity[];
10
- }
11
- /**
12
- * Scans text content for named entities that may represent PII.
13
- * Uses `compromise/three` for person, place, and organization detection.
14
- *
15
- * Designed for egress filtering — optimized for recall over precision
16
- * to ensure sensitive data does not leak through aliased output keys.
17
- */
18
- export declare class NerScanner {
19
- /**
20
- * Scans a single string value for named entities.
21
- * Returns detected entities if the text contains recognizable PII.
22
- */
23
- scan(text: string): NerScanResult;
24
- /**
25
- * Recursively scans all string values within an object/array.
26
- * Stops at the first detection for performance (fail-fast).
27
- */
28
- scanDeep(input: unknown, seen?: WeakSet<object>): NerScanResult;
29
- }
@@ -1,141 +0,0 @@
1
- /**
2
- * LIOP NER Content Scanner (The Shield V3 — Named Entity Recognition Layer)
3
- *
4
- * Lightweight NER scanner using `compromise` NLP for detecting
5
- * person names, places, and organizations in free-text output values.
6
- *
7
- * This layer operates AFTER the regex-based PII scanner and
8
- * catches entities that lack a deterministic format pattern
9
- * (e.g., "Evelyn Reed" cannot be detected by regex).
10
- *
11
- * Architecture: opt-in per-server via `enableNerScanning: true`.
12
- * Performance: ~10ms for typical SDK output sizes (< 10KB).
13
- *
14
- * @see https://github.com/spencermountain/compromise
15
- */
16
- import nlp from "compromise/three";
17
- /**
18
- * Medical/pharmaceutical vocabulary safelist.
19
- * These terms are tagged as #Medication to prevent the NER
20
- * from misclassifying them as person/organization names.
21
- * Extends progressively — add terms as false positives arise.
22
- */
23
- const MEDICAL_VOCABULARY = {
24
- aspirin: "Medication",
25
- lisinopril: "Medication",
26
- metformin: "Medication",
27
- amlodipine: "Medication",
28
- atorvastatin: "Medication",
29
- omeprazole: "Medication",
30
- losartan: "Medication",
31
- simvastatin: "Medication",
32
- levothyroxine: "Medication",
33
- ibuprofen: "Medication",
34
- acetaminophen: "Medication",
35
- amoxicillin: "Medication",
36
- ciprofloxacin: "Medication",
37
- prednisone: "Medication",
38
- warfarin: "Medication",
39
- insulin: "Medication",
40
- hydrochlorothiazide: "Medication",
41
- gabapentin: "Medication",
42
- albuterol: "Medication",
43
- pantoprazole: "Medication",
44
- // Generic clinical terms
45
- hypertension: "Condition",
46
- diabetes: "Condition",
47
- bronchitis: "Condition",
48
- pneumonia: "Condition",
49
- asthma: "Condition",
50
- };
51
- // Register medical vocabulary BEFORE any scan operations.
52
- // compromise's addWords() overrides the default classification,
53
- // preventing these terms from being tagged as #Person or #Organization.
54
- nlp.addWords(MEDICAL_VOCABULARY);
55
- // Minimum string length to attempt NER analysis.
56
- // Shorter strings are unlikely to contain meaningful named entities.
57
- const MIN_TEXT_LENGTH = 4;
58
- // Pattern to identify strings that are purely numeric/symbolic (skip NER)
59
- const NON_TEXT_PATTERN = /^[\d\s.,:;!?()[\]{}<>@#$%^&*+=|\\/"'`~_-]+$/;
60
- /**
61
- * Scans text content for named entities that may represent PII.
62
- * Uses `compromise/three` for person, place, and organization detection.
63
- *
64
- * Designed for egress filtering — optimized for recall over precision
65
- * to ensure sensitive data does not leak through aliased output keys.
66
- */
67
- export class NerScanner {
68
- /**
69
- * Scans a single string value for named entities.
70
- * Returns detected entities if the text contains recognizable PII.
71
- */
72
- scan(text) {
73
- if (text.length < MIN_TEXT_LENGTH || NON_TEXT_PATTERN.test(text)) {
74
- return { detected: false, entities: [] };
75
- }
76
- const doc = nlp(text);
77
- const entities = [];
78
- const people = doc.people().out("array");
79
- for (const person of people) {
80
- const trimmed = person.trim();
81
- if (trimmed.length >= MIN_TEXT_LENGTH) {
82
- entities.push({ type: "person", text: trimmed });
83
- }
84
- }
85
- const places = doc.places().out("array");
86
- for (const place of places) {
87
- const trimmed = place.trim();
88
- if (trimmed.length >= MIN_TEXT_LENGTH) {
89
- entities.push({ type: "place", text: trimmed });
90
- }
91
- }
92
- const orgs = doc.organizations().out("array");
93
- for (const org of orgs) {
94
- const trimmed = org.trim();
95
- if (trimmed.length >= MIN_TEXT_LENGTH) {
96
- entities.push({ type: "organization", text: trimmed });
97
- }
98
- }
99
- return {
100
- detected: entities.length > 0,
101
- entities,
102
- };
103
- }
104
- /**
105
- * Recursively scans all string values within an object/array.
106
- * Stops at the first detection for performance (fail-fast).
107
- */
108
- scanDeep(input, seen = new WeakSet()) {
109
- if (input === null || input === undefined) {
110
- return { detected: false, entities: [] };
111
- }
112
- if (typeof input === "string") {
113
- return this.scan(input);
114
- }
115
- if (typeof input === "object") {
116
- if (seen.has(input)) {
117
- return { detected: false, entities: [] };
118
- }
119
- seen.add(input);
120
- const values = Array.isArray(input)
121
- ? input
122
- : Object.values(input);
123
- const allEntities = [];
124
- for (const value of values) {
125
- const result = this.scanDeep(value, seen);
126
- if (result.detected) {
127
- allEntities.push(...result.entities);
128
- // Fail-fast: return immediately on first person detection
129
- if (result.entities.some((e) => e.type === "person")) {
130
- return { detected: true, entities: allEntities };
131
- }
132
- }
133
- }
134
- return {
135
- detected: allEntities.length > 0,
136
- entities: allEntities,
137
- };
138
- }
139
- return { detected: false, entities: [] };
140
- }
141
- }
@@ -1,66 +0,0 @@
1
- /**
2
- * LIOP Professional PII Engine (The Shield V2 - Tier-1 Military Edition)
3
- * Implements high-fidelity detection based on NIST and OWASP standards.
4
- * Features Multi-Layer Verification (Regex + Algorithmic Validators).
5
- */
6
- export type PiiRuleDefinition = {
7
- name: string;
8
- pattern: string | RegExp;
9
- validator?: (match: string) => boolean;
10
- };
11
- export type PiiRule = string | RegExp | PiiRuleDefinition;
12
- export declare const PII_PATTERNS: {
13
- EMAIL: PiiRuleDefinition;
14
- CREDIT_CARD: PiiRuleDefinition;
15
- IP_ADDRESS: PiiRuleDefinition;
16
- PHONE: PiiRuleDefinition;
17
- SSN: PiiRuleDefinition;
18
- IBAN: PiiRuleDefinition;
19
- PASSPORT_MRZ: PiiRuleDefinition;
20
- };
21
- /**
22
- * Regional and Cultural Security Presets for Out-Of-The-Box compliance.
23
- * Developers can override, merge, or omit these based on local laws.
24
- */
25
- export declare const PII_PRESETS: {
26
- GLOBAL_STRICT: PiiRuleDefinition[];
27
- US_COMPLIANT: PiiRuleDefinition[];
28
- EU_GDPR: PiiRuleDefinition[];
29
- };
30
- export declare class PiiScanner {
31
- private patterns;
32
- private forbiddenKeysSet;
33
- private nerScanner;
34
- /**
35
- * Safelist of keys that contain forbidden substrings but are NOT PII.
36
- * Prevents false positives from fuzzy matching (e.g., "grid" contains "id").
37
- */
38
- private static readonly KEY_SAFELIST;
39
- /**
40
- * Short forbidden tokens (< 4 chars) that require boundary-aware matching.
41
- * Uses regex boundary detection to avoid false positives.
42
- */
43
- private shortTokenBoundaryPatterns;
44
- /**
45
- * Long forbidden tokens (>= 4 chars) that use substring containment.
46
- */
47
- private longForbiddenTokens;
48
- constructor(patterns?: PiiRule[], forbiddenKeys?: string[], nerScanner?: import("./ner-scanner.js").NerScanner | null);
49
- /**
50
- * Scans any input (string, object, array) for PII violations.
51
- * Returns the pattern/rule name that triggered the violation, or null if safe.
52
- *
53
- * Detection pipeline (fail-fast):
54
- * 1. Exact key match (O(1) Set lookup)
55
- * 2. Fuzzy key match (boundary detection for short tokens, substring for long)
56
- * 3. Regex/algorithmic pattern match on string values
57
- * 4. NER content scan on string values (if enabled)
58
- */
59
- scan(input: unknown, seen?: WeakSet<object>): string | null;
60
- /**
61
- * Checks a key against fuzzy matching rules.
62
- * Short tokens use boundary-aware regex; long tokens use substring containment.
63
- */
64
- private checkKeyFuzzy;
65
- private checkString;
66
- }
@@ -1,428 +0,0 @@
1
- /**
2
- * LIOP Professional PII Engine (The Shield V2 - Tier-1 Military Edition)
3
- * Implements high-fidelity detection based on NIST and OWASP standards.
4
- * Features Multi-Layer Verification (Regex + Algorithmic Validators).
5
- */
6
- /**
7
- * Validates a credit card number using the Luhn algorithm.
8
- * Prevents false positives from random 16-digit IDs.
9
- */
10
- function isLuhnValid(cardNumber) {
11
- const digits = cardNumber.replace(/\D/g, "");
12
- if (digits.length < 13 || digits.length > 19)
13
- return false;
14
- let sum = 0;
15
- let isEven = false;
16
- for (let i = digits.length - 1; i >= 0; i--) {
17
- let digit = parseInt(digits.charAt(i), 10);
18
- if (isEven) {
19
- digit *= 2;
20
- if (digit > 9) {
21
- digit -= 9;
22
- }
23
- }
24
- sum += digit;
25
- isEven = !isEven;
26
- }
27
- return sum % 10 === 0;
28
- }
29
- /**
30
- * Validates an International Bank Account Number (IBAN) using ISO 7064 Modulo 97.
31
- * Uses BigInt algebra to avoid JS floating point truncation with 30-digit numbers.
32
- */
33
- function isIbanValid(iban) {
34
- const sanitized = iban.replace(/\s+/g, "").toUpperCase();
35
- if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/.test(sanitized))
36
- return false;
37
- const rearranged = sanitized.substring(4) + sanitized.substring(0, 4);
38
- let numericString = "";
39
- for (let i = 0; i < rearranged.length; i++) {
40
- const charCode = rearranged.charCodeAt(i);
41
- if (charCode >= 65 && charCode <= 90) {
42
- numericString += (charCode - 55).toString();
43
- }
44
- else if (charCode >= 48 && charCode <= 57) {
45
- numericString += rearranged.charAt(i);
46
- }
47
- else {
48
- return false;
49
- }
50
- }
51
- try {
52
- return BigInt(numericString) % 97n === 1n;
53
- }
54
- catch (_e) {
55
- return false;
56
- }
57
- }
58
- export const PII_PATTERNS = {
59
- EMAIL: {
60
- name: "EMAIL",
61
- pattern: /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/gi,
62
- validator: (match) => !match.endsWith("@example.com") && !match.endsWith("@test.com"),
63
- },
64
- CREDIT_CARD: {
65
- name: "CREDIT_CARD",
66
- pattern: /\b(?:\d[ -]*?){13,16}\b/g,
67
- validator: isLuhnValid,
68
- },
69
- IP_ADDRESS: {
70
- name: "IP_ADDRESS",
71
- pattern: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
72
- validator: (match) => {
73
- const safeIps = ["127.0.0.1", "0.0.0.0", "255.255.255.255"];
74
- if (safeIps.includes(match))
75
- return false;
76
- // Validate valid IPv4 ranges
77
- const parts = match.split(".").map(Number);
78
- return parts.every((p) => p >= 0 && p <= 255);
79
- },
80
- },
81
- PHONE: {
82
- name: "PHONE",
83
- // Strict boundary to avoid matching long numeric IDs wrapped in symbols
84
- pattern: /(?:(?:\+?\d{1,3}[-. ]?)?\(?\d{3}\)?[-. ]?\d{3}[-. ]?\d{4})\b/g,
85
- validator: (match) => {
86
- const digits = match.replace(/\D/g, "");
87
- if (digits.length < 7 || digits.length > 15)
88
- return false;
89
- // Reject fake test numbers like 0000000000 or 1234567890
90
- if (/^(\d)\1+$/.test(digits))
91
- return false;
92
- if (digits === "1234567890")
93
- return false;
94
- return true;
95
- },
96
- },
97
- SSN: {
98
- name: "SSN",
99
- pattern: /\b\d{3}[- ]?\d{2}[- ]?\d{4}\b/g,
100
- validator: (match) => {
101
- const digits = match.replace(/\D/g, "");
102
- if (digits.length !== 9)
103
- return false;
104
- const area = parseInt(digits.substring(0, 3), 10);
105
- if (area === 0 || area === 666 || area >= 900)
106
- return false;
107
- const group = parseInt(digits.substring(3, 5), 10);
108
- if (group === 0)
109
- return false;
110
- const serial = parseInt(digits.substring(5, 9), 10);
111
- if (serial === 0)
112
- return false;
113
- if (/^(\d)\1+$/.test(digits) || digits === "123456789")
114
- return false;
115
- return true;
116
- },
117
- },
118
- IBAN: {
119
- name: "IBAN",
120
- pattern: /\b[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}\b/gi,
121
- validator: isIbanValid,
122
- },
123
- PASSPORT_MRZ: {
124
- name: "PASSPORT_MRZ",
125
- // Machina Readable Zone line match for standard international passports
126
- pattern: /\bP[A-Z<][A-Z<]{3}[A-Z0-9<]{39}(?:\b|\s|$)/g,
127
- },
128
- };
129
- /**
130
- * Regional and Cultural Security Presets for Out-Of-The-Box compliance.
131
- * Developers can override, merge, or omit these based on local laws.
132
- */
133
- export const PII_PRESETS = {
134
- GLOBAL_STRICT: [
135
- PII_PATTERNS.EMAIL,
136
- PII_PATTERNS.CREDIT_CARD,
137
- PII_PATTERNS.IP_ADDRESS,
138
- PII_PATTERNS.PHONE,
139
- PII_PATTERNS.PASSPORT_MRZ,
140
- PII_PATTERNS.IBAN,
141
- ],
142
- US_COMPLIANT: [
143
- PII_PATTERNS.EMAIL,
144
- PII_PATTERNS.CREDIT_CARD,
145
- PII_PATTERNS.IP_ADDRESS,
146
- PII_PATTERNS.PHONE,
147
- PII_PATTERNS.SSN,
148
- PII_PATTERNS.PASSPORT_MRZ,
149
- ],
150
- EU_GDPR: [
151
- PII_PATTERNS.EMAIL,
152
- PII_PATTERNS.CREDIT_CARD,
153
- PII_PATTERNS.IP_ADDRESS,
154
- PII_PATTERNS.PHONE,
155
- PII_PATTERNS.IBAN,
156
- PII_PATTERNS.PASSPORT_MRZ,
157
- ],
158
- };
159
- export class PiiScanner {
160
- patterns;
161
- forbiddenKeysSet;
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) {
262
- this.patterns = patterns;
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
- }
282
- }
283
- /**
284
- * Scans any input (string, object, array) for PII violations.
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)
292
- */
293
- scan(input, seen = new WeakSet()) {
294
- if (input === null || input === undefined)
295
- return null;
296
- // 1. String Scan (Direct Regex/String/Definition check)
297
- if (typeof input === "string") {
298
- // SECURITY PATCH: JSON Deep-Parsing Recursion (Fortification V2)
299
- // Defeats Double JSON Encoding bypasses by forcefully parsing stringified JSON back into objects.
300
- const trimmed = input.trim();
301
- if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
302
- (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
303
- try {
304
- const parsed = JSON.parse(trimmed);
305
- // Successfully parsed JSON string. Recursively scan the unescaped object.
306
- const violation = this.scan(parsed, seen);
307
- if (violation)
308
- return violation;
309
- }
310
- catch (_e) {
311
- // Silent fallback: It looked like JSON but wasn't valid. Proceed with raw string check.
312
- }
313
- }
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;
329
- }
330
- // 2. Recursive Objects/Arrays Scan
331
- if (typeof input === "object") {
332
- // Protection against circular references
333
- if (seen.has(input))
334
- return null;
335
- seen.add(input);
336
- if (Array.isArray(input)) {
337
- for (const element of input) {
338
- const violation = this.scan(element, seen);
339
- if (violation)
340
- return violation;
341
- }
342
- }
343
- else {
344
- for (const [key, value] of Object.entries(input)) {
345
- // Layer 1: Exact key match — O(1) constant time
346
- if (this.forbiddenKeysSet.has(key.toLowerCase())) {
347
- return `Forbidden Key: ${key}`;
348
- }
349
- // Layer 2: Fuzzy key match — catches aliases and variations
350
- const fuzzyViolation = this.checkKeyFuzzy(key);
351
- if (fuzzyViolation)
352
- return fuzzyViolation;
353
- // Recurse into values
354
- const violation = this.scan(value, seen);
355
- if (violation)
356
- return violation;
357
- }
358
- }
359
- }
360
- return null;
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
- }
385
- checkString(text) {
386
- for (const rule of this.patterns) {
387
- if (typeof rule === "string") {
388
- if (text.toLowerCase().includes(rule.toLowerCase())) {
389
- return rule;
390
- }
391
- }
392
- else if (rule instanceof RegExp) {
393
- if (rule.global)
394
- rule.lastIndex = 0;
395
- if (rule.test(text)) {
396
- return rule.source;
397
- }
398
- }
399
- else if (typeof rule === "object" && rule !== null) {
400
- // PiiRuleDefinition (Military Grade Multi-layer)
401
- const def = rule;
402
- if (typeof def.pattern === "string") {
403
- if (text.toLowerCase().includes(def.pattern.toLowerCase())) {
404
- if (!def.validator || def.validator(def.pattern)) {
405
- return def.name;
406
- }
407
- }
408
- }
409
- else if (def.pattern instanceof RegExp) {
410
- if (def.pattern.global)
411
- def.pattern.lastIndex = 0;
412
- // Use matchAll or exec to get the specific match for the validator
413
- let match = def.pattern.exec(text);
414
- while (match !== null) {
415
- const matchedText = match[0];
416
- if (!def.validator || def.validator(matchedText)) {
417
- return def.name;
418
- }
419
- if (!def.pattern.global)
420
- break; // Break if not global
421
- match = def.pattern.exec(text);
422
- }
423
- }
424
- }
425
- }
426
- return null;
427
- }
428
- }
@@ -1,21 +0,0 @@
1
- export type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
2
- /**
3
- * LiopLogger - Structured Logging Abstraction
4
- * Configurable via `process.env.LIOP_LOG_LEVEL`.
5
- * Emits strictly to stderr to comply with MCP stdio protocols.
6
- */
7
- export declare class LiopLogger {
8
- private static instance;
9
- private level;
10
- private constructor();
11
- static getInstance(): LiopLogger;
12
- private setLevelFromEnv;
13
- setLevel(level: LogLevel): void;
14
- private shouldLog;
15
- private formatMessage;
16
- error(message: string, ...args: unknown[]): void;
17
- warn(message: string, ...args: unknown[]): void;
18
- info(message: string, ...args: unknown[]): void;
19
- debug(message: string, ...args: unknown[]): void;
20
- }
21
- export declare const log: LiopLogger;