yakmesh 2.9.0 → 3.0.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.
Files changed (225) hide show
  1. package/CHANGELOG.md +637 -0
  2. package/Caddyfile +77 -0
  3. package/README.md +119 -29
  4. package/content/api.js +50 -41
  5. package/content/index.js +1 -2
  6. package/content/store.js +323 -177
  7. package/dashboard/index.html +19 -3
  8. package/database/replication.js +117 -37
  9. package/docs/CRYPTO-AGILITY.md +204 -0
  10. package/docs/MTLS-RESEARCH.md +367 -0
  11. package/docs/NAMCHE-SPEC.md +681 -0
  12. package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
  13. package/docs/PRECISION-DISCLOSURE.md +96 -0
  14. package/docs/README.md +76 -0
  15. package/docs/ROADMAP-2.4.0.md +447 -0
  16. package/docs/ROADMAP-2.5.0.md +244 -0
  17. package/docs/SECURITY-AUDIT-REPORT.md +306 -0
  18. package/docs/SST-INTEGRATION.md +712 -0
  19. package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
  20. package/docs/TERNARY-AUDIT-REPORT.md +247 -0
  21. package/docs/TME-FAQ.md +221 -0
  22. package/docs/WHITEPAPER.md +623 -0
  23. package/docs/adapters.html +1001 -0
  24. package/docs/advanced-systems.html +1045 -0
  25. package/docs/annex.html +1046 -0
  26. package/docs/api.html +970 -0
  27. package/docs/business/response-templates.md +160 -0
  28. package/docs/c2c.html +1225 -0
  29. package/docs/cli.html +1332 -0
  30. package/docs/configuration.html +1248 -0
  31. package/docs/darshan.html +1085 -0
  32. package/docs/dharma.html +966 -0
  33. package/docs/docs-bundle.html +1075 -0
  34. package/docs/docs.css +3120 -0
  35. package/docs/docs.js +556 -0
  36. package/docs/doko.html +969 -0
  37. package/docs/geo-proof.html +858 -0
  38. package/docs/getting-started.html +840 -0
  39. package/docs/gumba-tutorial.html +1144 -0
  40. package/docs/gumba.html +1098 -0
  41. package/docs/index.html +914 -0
  42. package/docs/jhilke.html +1312 -0
  43. package/docs/karma.html +1100 -0
  44. package/docs/katha.html +1037 -0
  45. package/docs/lama.html +978 -0
  46. package/docs/mandala.html +1067 -0
  47. package/docs/mani.html +964 -0
  48. package/docs/mantra.html +967 -0
  49. package/docs/mesh.html +1409 -0
  50. package/docs/nakpak.html +869 -0
  51. package/docs/namche.html +928 -0
  52. package/docs/nav-order.json +53 -0
  53. package/docs/prahari.html +1043 -0
  54. package/docs/prism-bash.min.js +1 -0
  55. package/docs/prism-javascript.min.js +1 -0
  56. package/docs/prism-json.min.js +1 -0
  57. package/docs/prism-tomorrow.min.css +1 -0
  58. package/docs/prism.min.js +1 -0
  59. package/docs/privacy.html +699 -0
  60. package/docs/quick-reference.html +1181 -0
  61. package/docs/sakshi.html +1402 -0
  62. package/docs/sandboxing.md +386 -0
  63. package/docs/seva.html +911 -0
  64. package/docs/sherpa.html +871 -0
  65. package/docs/studio.html +860 -0
  66. package/docs/stupa.html +995 -0
  67. package/docs/tailwind.min.css +2 -0
  68. package/docs/tattva.html +1332 -0
  69. package/docs/terms.html +686 -0
  70. package/docs/time-server-deployment.md +166 -0
  71. package/docs/time-sources.html +1392 -0
  72. package/docs/tivra.html +1127 -0
  73. package/docs/trademark-policy.html +686 -0
  74. package/docs/tribhuj.html +1183 -0
  75. package/docs/trust-security.html +1029 -0
  76. package/docs/tutorials/backup-recovery.html +654 -0
  77. package/docs/tutorials/dashboard.html +604 -0
  78. package/docs/tutorials/domain-setup.html +605 -0
  79. package/docs/tutorials/host-website.html +456 -0
  80. package/docs/tutorials/mesh-network.html +505 -0
  81. package/docs/tutorials/mobile-access.html +445 -0
  82. package/docs/tutorials/privacy.html +467 -0
  83. package/docs/tutorials/raspberry-pi.html +600 -0
  84. package/docs/tutorials/security-basics.html +539 -0
  85. package/docs/tutorials/share-files.html +431 -0
  86. package/docs/tutorials/troubleshooting.html +637 -0
  87. package/docs/tutorials/trust-karma.html +419 -0
  88. package/docs/tutorials/yak-protocol.html +456 -0
  89. package/docs/tutorials.html +1034 -0
  90. package/docs/vani.html +1270 -0
  91. package/docs/webserver.html +809 -0
  92. package/docs/yak-protocol.html +940 -0
  93. package/docs/yak-timeserver-design.md +475 -0
  94. package/docs/yakapp.html +1015 -0
  95. package/docs/ypc27.html +1069 -0
  96. package/docs/yurt.html +1344 -0
  97. package/embedded-docs/bundle.js +274 -114
  98. package/gossip/protocol.js +247 -27
  99. package/identity/key-resolver.js +262 -0
  100. package/identity/machine-seed.js +632 -0
  101. package/identity/node-key.js +669 -368
  102. package/identity/tribhuj-ratchet.js +506 -0
  103. package/knowledge-base.js +37 -8
  104. package/launcher/yakmesh.bat +62 -0
  105. package/launcher/yakmesh.sh +70 -0
  106. package/mesh/annex.js +462 -108
  107. package/mesh/beacon-broadcast.js +4 -1
  108. package/mesh/darshan.js +17 -5
  109. package/mesh/gumba.js +47 -13
  110. package/mesh/jhilke.js +651 -0
  111. package/mesh/katha.js +5 -2
  112. package/mesh/nakpak-routing.js +8 -5
  113. package/mesh/network.js +724 -34
  114. package/mesh/pulse-sync.js +4 -1
  115. package/mesh/seva.js +526 -0
  116. package/mesh/sherpa-discovery.js +89 -8
  117. package/mesh/sybil-defense.js +19 -5
  118. package/mesh/temporal-encoder.js +4 -3
  119. package/mesh/yurt.js +72 -17
  120. package/models/entropy-sentinel.onnx +0 -0
  121. package/models/karma-trust.onnx +0 -0
  122. package/models/manifest.json +43 -0
  123. package/models/sakshi-anomaly.onnx +0 -0
  124. package/oracle/code-proof-protocol.js +7 -6
  125. package/oracle/codebase-lock.js +257 -28
  126. package/oracle/index.js +74 -15
  127. package/oracle/ma902-snmp.js +678 -0
  128. package/oracle/module-sealer.js +5 -3
  129. package/oracle/packet-checksum.js +201 -0
  130. package/oracle/ternary-144t.js +714 -0
  131. package/oracle/ternary-ml.js +481 -0
  132. package/oracle/time-api.js +239 -0
  133. package/oracle/time-source.js +137 -47
  134. package/oracle/validation-oracle-hardened.js +1111 -1071
  135. package/oracle/validation-oracle.js +4 -2
  136. package/oracle/ypc27.js +211 -0
  137. package/package.json +20 -3
  138. package/protocol/yak-handler.js +35 -9
  139. package/protocol/yak-protocol.js +6 -5
  140. package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
  141. package/reference/cpp/yakmesh_ypc27.cpp +179 -0
  142. package/sbom.json +87 -0
  143. package/scripts/security-audit.mjs +264 -0
  144. package/scripts/update-docs-sidebar.cjs +164 -0
  145. package/security/crypto-config.js +4 -3
  146. package/security/dharma-moderation.js +4 -3
  147. package/security/doko-identity.js +193 -143
  148. package/security/domain-consensus.js +86 -85
  149. package/security/fs-hardening.js +620 -0
  150. package/security/hardware-attestation.js +5 -3
  151. package/security/hybrid-trust.js +227 -87
  152. package/security/karma-rate-limiter.js +692 -0
  153. package/security/khata-protocol.js +22 -21
  154. package/security/khata-trust-integration.js +277 -150
  155. package/security/memory-safety.js +635 -0
  156. package/security/mesh-auth.js +11 -10
  157. package/security/mesh-revocation.js +18 -5
  158. package/security/namche-gateway.js +298 -69
  159. package/security/sakshi.js +102 -3
  160. package/security/sangha.js +770 -0
  161. package/security/secure-config.js +473 -0
  162. package/security/silicon-parity.js +13 -10
  163. package/security/steadywatch.js +1142 -0
  164. package/security/strike-system.js +32 -3
  165. package/security/temporal-signing.js +488 -0
  166. package/security/trit-commitment.js +464 -0
  167. package/server/crypto/annex.js +247 -0
  168. package/server/darshan-api.js +343 -0
  169. package/server/index.js +3259 -362
  170. package/server/komm-api.js +668 -0
  171. package/utils/accel.js +2273 -0
  172. package/utils/ternary-id.js +79 -0
  173. package/utils/verify-worker.js +57 -0
  174. package/webserver/index.js +95 -5
  175. package/assets/yakmesh-logo.png +0 -0
  176. package/assets/yakmesh-logo.svg +0 -80
  177. package/assets/yakmesh-logo2.png +0 -0
  178. package/assets/yakmesh-logo2sm.png +0 -0
  179. package/assets/ymsm.png +0 -0
  180. package/scripts/update-docs-nav.cjs +0 -194
  181. package/update-docs-nav.cjs +0 -18
  182. package/update-nav.ps1 +0 -16
  183. package/website/assets/silhouettes/adapters.svg +0 -107
  184. package/website/assets/silhouettes/api-endpoints.svg +0 -115
  185. package/website/assets/silhouettes/atomic-clock.svg +0 -83
  186. package/website/assets/silhouettes/base-camp.svg +0 -81
  187. package/website/assets/silhouettes/bridge.svg +0 -69
  188. package/website/assets/silhouettes/docs-bundle.svg +0 -113
  189. package/website/assets/silhouettes/doko-basket.svg +0 -70
  190. package/website/assets/silhouettes/fortress.svg +0 -93
  191. package/website/assets/silhouettes/gateway.svg +0 -54
  192. package/website/assets/silhouettes/gears.svg +0 -93
  193. package/website/assets/silhouettes/globe-satellite.svg +0 -67
  194. package/website/assets/silhouettes/karma-wheel.svg +0 -137
  195. package/website/assets/silhouettes/lama-council.svg +0 -141
  196. package/website/assets/silhouettes/mandala-network.svg +0 -169
  197. package/website/assets/silhouettes/mani-stones.svg +0 -149
  198. package/website/assets/silhouettes/mantra-wheel.svg +0 -116
  199. package/website/assets/silhouettes/mesh-nodes.svg +0 -113
  200. package/website/assets/silhouettes/nakpak.svg +0 -56
  201. package/website/assets/silhouettes/peak-lightning.svg +0 -73
  202. package/website/assets/silhouettes/sherpa.svg +0 -69
  203. package/website/assets/silhouettes/stupa-tower.svg +0 -119
  204. package/website/assets/silhouettes/tattva-eye.svg +0 -78
  205. package/website/assets/silhouettes/terminal.svg +0 -74
  206. package/website/assets/silhouettes/webserver.svg +0 -145
  207. package/website/assets/silhouettes/yak.svg +0 -78
  208. package/website/assets/yakmesh-logo.png +0 -0
  209. package/website/assets/yakmesh-logo.webp +0 -0
  210. package/website/assets/yakmesh-logo128x140.webp +0 -0
  211. package/website/assets/yakmesh-logo2.png +0 -0
  212. package/website/assets/yakmesh-logo2.svg +0 -51
  213. package/website/assets/yakmesh-logo40x44.webp +0 -0
  214. package/website/assets/yakmesh.gif +0 -0
  215. package/website/assets/yakmesh.ico +0 -0
  216. package/website/assets/yakmesh.jpg +0 -0
  217. package/website/assets/yakmesh.pdf +0 -0
  218. package/website/assets/yakmesh.png +0 -0
  219. package/website/assets/yakmesh.svg +0 -70
  220. package/website/assets/yakmesh128.webp +0 -0
  221. package/website/assets/yakmesh32.png +0 -0
  222. package/website/assets/yakmesh32.svg +0 -65
  223. package/website/assets/yakmesh32o.ico +0 -2
  224. package/website/assets/yakmesh32o.svg +0 -65
  225. package/website/assets/yakmesh32o.svgz +0 -0
@@ -0,0 +1,692 @@
1
+ /**
2
+ * Yakmesh KARMA-Adaptive Rate Limiting + Input Validation
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * PHILOSOPHY: TRUST ENABLES THROUGHPUT
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Traditional rate limiting: Fixed thresholds (e.g., 100 req/min for everyone).
9
+ * KARMA-adaptive: Throughput scales with earned trust.
10
+ *
11
+ * - Unknown peers: Strict limits (10 req/min)
12
+ * - Low KARMA (0-30): Cautious (25 req/min)
13
+ * - Medium KARMA (31-60): Standard (50 req/min)
14
+ * - High KARMA (61-85): Elevated (100 req/min)
15
+ * - Excellent KARMA (86-100): Trusted (200 req/min)
16
+ *
17
+ * This creates economic incentive: good behavior → higher throughput capacity.
18
+ *
19
+ * ═══════════════════════════════════════════════════════════════════════════════
20
+ * INTEGRATION WITH SANGHA
21
+ * ═══════════════════════════════════════════════════════════════════════════════
22
+ *
23
+ * The rate limiter participates in SANGHA collective attestation:
24
+ * - Reports current load and block counts during circulation
25
+ * - Can trigger collective response to coordinated flood attacks
26
+ * - Receives warnings from other components about suspicious peers
27
+ *
28
+ * @module security/karma-rate-limiter
29
+ * @version 1.0.0
30
+ */
31
+
32
+ import { createLogger } from '../utils/logger.js';
33
+ import { EventEmitter } from 'events';
34
+
35
+ const log = createLogger('security:karma-rate-limiter');
36
+
37
+ // =============================================================================
38
+ // CONSTANTS
39
+ // =============================================================================
40
+
41
+ /** Rate limit tiers based on KARMA score */
42
+ export const KARMA_TIERS = {
43
+ UNKNOWN: { min: -1, max: -1, limit: 10, window: 60000, label: 'Unknown' },
44
+ HOSTILE: { min: 0, max: 10, limit: 2, window: 60000, label: 'Hostile' },
45
+ LOW: { min: 11, max: 30, limit: 25, window: 60000, label: 'Low' },
46
+ MEDIUM: { min: 31, max: 60, limit: 50, window: 60000, label: 'Medium' },
47
+ HIGH: { min: 61, max: 85, limit: 100, window: 60000, label: 'High' },
48
+ EXCELLENT: { min: 86, max: 100, limit: 200, window: 60000, label: 'Excellent' },
49
+ };
50
+
51
+ /** Request size limits by content type */
52
+ export const SIZE_LIMITS = {
53
+ json: 256 * 1024, // 256 KB for JSON payloads
54
+ binary: 16 * 1024 * 1024, // 16 MB for binary content
55
+ websocket: 64 * 1024, // 64 KB per WebSocket message
56
+ gossip: 4 * 1024, // 4 KB for gossip messages
57
+ };
58
+
59
+ /** Burst multiplier (allows short bursts above rate limit) */
60
+ const BURST_MULTIPLIER = 2;
61
+
62
+ /** Block duration after exceeding limits (ms) */
63
+ const BLOCK_DURATION = 60000;
64
+
65
+ /** Escalation: each violation increases block duration */
66
+ const BLOCK_ESCALATION = 2;
67
+
68
+ // =============================================================================
69
+ // RATE BUCKET
70
+ // =============================================================================
71
+
72
+ /**
73
+ * RateBucket — Token bucket for a single peer
74
+ */
75
+ class RateBucket {
76
+ peerId;
77
+ karma;
78
+ tier;
79
+ tokens;
80
+ lastRefill;
81
+ violations;
82
+ blockedUntil;
83
+
84
+ constructor(peerId, karma = -1) {
85
+ this.peerId = peerId;
86
+ this.karma = karma;
87
+ this.tier = this.#getTier(karma);
88
+ this.tokens = this.tier.limit * BURST_MULTIPLIER;
89
+ this.lastRefill = Date.now();
90
+ this.violations = 0;
91
+ this.blockedUntil = 0;
92
+
93
+ Object.seal(this);
94
+ }
95
+
96
+ #getTier(karma) {
97
+ if (karma < 0) return KARMA_TIERS.UNKNOWN;
98
+ if (karma <= 10) return KARMA_TIERS.HOSTILE;
99
+ if (karma <= 30) return KARMA_TIERS.LOW;
100
+ if (karma <= 60) return KARMA_TIERS.MEDIUM;
101
+ if (karma <= 85) return KARMA_TIERS.HIGH;
102
+ return KARMA_TIERS.EXCELLENT;
103
+ }
104
+
105
+ /**
106
+ * Update KARMA score (may change tier)
107
+ */
108
+ updateKarma(newKarma) {
109
+ const oldTier = this.tier;
110
+ this.karma = newKarma;
111
+ this.tier = this.#getTier(newKarma);
112
+
113
+ if (oldTier !== this.tier) {
114
+ log.debug('Peer tier changed', {
115
+ peerId: this.peerId.slice(0, 16),
116
+ oldTier: oldTier.label,
117
+ newTier: this.tier.label,
118
+ newLimit: this.tier.limit,
119
+ });
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Refill tokens based on elapsed time
125
+ */
126
+ #refill() {
127
+ const now = Date.now();
128
+ const elapsed = now - this.lastRefill;
129
+ const tokensPerMs = this.tier.limit / this.tier.window;
130
+ const newTokens = Math.floor(elapsed * tokensPerMs);
131
+
132
+ if (newTokens > 0) {
133
+ this.tokens = Math.min(
134
+ this.tokens + newTokens,
135
+ this.tier.limit * BURST_MULTIPLIER
136
+ );
137
+ this.lastRefill = now;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Try to consume tokens
143
+ * @param {number} cost - Tokens to consume (default 1)
144
+ * @returns {{ allowed: boolean, remaining: number, reason?: string }}
145
+ */
146
+ consume(cost = 1) {
147
+ const now = Date.now();
148
+
149
+ // Check if blocked
150
+ if (this.blockedUntil > now) {
151
+ return {
152
+ allowed: false,
153
+ remaining: 0,
154
+ reason: `Blocked for ${Math.ceil((this.blockedUntil - now) / 1000)}s`,
155
+ retryAfter: this.blockedUntil - now,
156
+ };
157
+ }
158
+
159
+ // Refill tokens
160
+ this.#refill();
161
+
162
+ // Check if enough tokens
163
+ if (this.tokens < cost) {
164
+ this.violations++;
165
+ const blockDuration = BLOCK_DURATION * Math.pow(BLOCK_ESCALATION, Math.min(this.violations - 1, 5));
166
+ this.blockedUntil = now + blockDuration;
167
+
168
+ log.warn('Rate limit exceeded — peer blocked', {
169
+ peerId: this.peerId.slice(0, 16),
170
+ tier: this.tier.label,
171
+ violations: this.violations,
172
+ blockDuration: Math.round(blockDuration / 1000) + 's',
173
+ });
174
+
175
+ return {
176
+ allowed: false,
177
+ remaining: 0,
178
+ reason: `Rate limit exceeded (${this.tier.label} tier: ${this.tier.limit}/min)`,
179
+ retryAfter: blockDuration,
180
+ };
181
+ }
182
+
183
+ this.tokens -= cost;
184
+ return {
185
+ allowed: true,
186
+ remaining: this.tokens,
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Get current status
192
+ */
193
+ getStatus() {
194
+ this.#refill();
195
+ return {
196
+ peerId: this.peerId,
197
+ karma: this.karma,
198
+ tier: this.tier.label,
199
+ limit: this.tier.limit,
200
+ remaining: Math.floor(this.tokens),
201
+ violations: this.violations,
202
+ blocked: this.blockedUntil > Date.now(),
203
+ };
204
+ }
205
+ }
206
+
207
+ // =============================================================================
208
+ // INPUT VALIDATOR
209
+ // =============================================================================
210
+
211
+ /**
212
+ * InputValidator — Validates and sanitizes incoming data
213
+ */
214
+ export class InputValidator {
215
+ #schemas;
216
+
217
+ constructor() {
218
+ this.#schemas = new Map();
219
+ this.#registerBuiltinSchemas();
220
+ }
221
+
222
+ /**
223
+ * Register built-in validation schemas
224
+ */
225
+ #registerBuiltinSchemas() {
226
+ // Node ID schema
227
+ this.#schemas.set('nodeId', {
228
+ type: 'string',
229
+ pattern: /^node-[a-z0-9-]+-pq-[A-Za-z0-9]{4}$/,
230
+ minLength: 20,
231
+ maxLength: 100,
232
+ });
233
+
234
+ // Public key (hex)
235
+ this.#schemas.set('publicKey', {
236
+ type: 'string',
237
+ pattern: /^[a-f0-9]+$/i,
238
+ minLength: 64,
239
+ maxLength: 4096,
240
+ });
241
+
242
+ // Signature (hex)
243
+ this.#schemas.set('signature', {
244
+ type: 'string',
245
+ pattern: /^[a-f0-9]+$/i,
246
+ minLength: 128,
247
+ maxLength: 8192,
248
+ });
249
+
250
+ // Hash (SHA3-256 hex)
251
+ this.#schemas.set('hash', {
252
+ type: 'string',
253
+ pattern: /^[a-f0-9]{64}$/i,
254
+ });
255
+
256
+ // Timestamp (Unix ms)
257
+ this.#schemas.set('timestamp', {
258
+ type: 'number',
259
+ min: 0,
260
+ max: Date.now() + 86400000, // Max 1 day in future
261
+ });
262
+
263
+ // Gossip message
264
+ this.#schemas.set('gossipMessage', {
265
+ type: 'object',
266
+ required: ['type', 'from', 'timestamp'],
267
+ properties: {
268
+ type: { type: 'string', maxLength: 50 },
269
+ from: { $ref: 'nodeId' },
270
+ timestamp: { $ref: 'timestamp' },
271
+ payload: { type: 'object', maxSize: SIZE_LIMITS.gossip },
272
+ },
273
+ });
274
+
275
+ // DOKO document
276
+ this.#schemas.set('dokoDocument', {
277
+ type: 'object',
278
+ required: ['version', 'type', 'nodeId', 'publicKey', 'signature'],
279
+ properties: {
280
+ version: { type: 'number', min: 1, max: 10 },
281
+ type: { type: 'string', enum: ['node', 'entity', 'content', 'code', 'system'] },
282
+ nodeId: { $ref: 'nodeId' },
283
+ publicKey: { $ref: 'publicKey' },
284
+ signature: { $ref: 'signature' },
285
+ },
286
+ });
287
+ }
288
+
289
+ /**
290
+ * Register a custom schema
291
+ */
292
+ registerSchema(name, schema) {
293
+ this.#schemas.set(name, schema);
294
+ }
295
+
296
+ /**
297
+ * Validate data against a schema
298
+ * @param {any} data - Data to validate
299
+ * @param {string|object} schemaOrName - Schema name or schema object
300
+ * @returns {{ valid: boolean, errors: string[] }}
301
+ */
302
+ validate(data, schemaOrName) {
303
+ const schema = typeof schemaOrName === 'string'
304
+ ? this.#schemas.get(schemaOrName)
305
+ : schemaOrName;
306
+
307
+ if (!schema) {
308
+ return { valid: false, errors: [`Unknown schema: ${schemaOrName}`] };
309
+ }
310
+
311
+ const errors = [];
312
+ this.#validateValue(data, schema, '', errors);
313
+
314
+ return {
315
+ valid: errors.length === 0,
316
+ errors,
317
+ };
318
+ }
319
+
320
+ #validateValue(value, schema, path, errors) {
321
+ // Handle schema references
322
+ if (schema.$ref) {
323
+ const refSchema = this.#schemas.get(schema.$ref);
324
+ if (!refSchema) {
325
+ errors.push(`${path}: Unknown schema reference: ${schema.$ref}`);
326
+ return;
327
+ }
328
+ this.#validateValue(value, refSchema, path, errors);
329
+ return;
330
+ }
331
+
332
+ // Type checking
333
+ if (schema.type) {
334
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
335
+ if (actualType !== schema.type) {
336
+ errors.push(`${path}: Expected ${schema.type}, got ${actualType}`);
337
+ return;
338
+ }
339
+ }
340
+
341
+ // String validations
342
+ if (schema.type === 'string' && typeof value === 'string') {
343
+ if (schema.minLength && value.length < schema.minLength) {
344
+ errors.push(`${path}: String too short (min: ${schema.minLength})`);
345
+ }
346
+ if (schema.maxLength && value.length > schema.maxLength) {
347
+ errors.push(`${path}: String too long (max: ${schema.maxLength})`);
348
+ }
349
+ if (schema.pattern && !schema.pattern.test(value)) {
350
+ errors.push(`${path}: String doesn't match pattern`);
351
+ }
352
+ if (schema.enum && !schema.enum.includes(value)) {
353
+ errors.push(`${path}: Value not in allowed list: ${schema.enum.join(', ')}`);
354
+ }
355
+ }
356
+
357
+ // Number validations
358
+ if (schema.type === 'number' && typeof value === 'number') {
359
+ if (schema.min !== undefined && value < schema.min) {
360
+ errors.push(`${path}: Number too small (min: ${schema.min})`);
361
+ }
362
+ if (schema.max !== undefined && value > schema.max) {
363
+ errors.push(`${path}: Number too large (max: ${schema.max})`);
364
+ }
365
+ }
366
+
367
+ // Object validations
368
+ if (schema.type === 'object' && typeof value === 'object' && value !== null) {
369
+ // Check required properties
370
+ if (schema.required) {
371
+ for (const prop of schema.required) {
372
+ if (!(prop in value)) {
373
+ errors.push(`${path}: Missing required property: ${prop}`);
374
+ }
375
+ }
376
+ }
377
+
378
+ // Validate properties
379
+ if (schema.properties) {
380
+ for (const [prop, propSchema] of Object.entries(schema.properties)) {
381
+ if (prop in value) {
382
+ this.#validateValue(value[prop], propSchema, `${path}.${prop}`, errors);
383
+ }
384
+ }
385
+ }
386
+
387
+ // Check max size
388
+ if (schema.maxSize) {
389
+ const size = JSON.stringify(value).length;
390
+ if (size > schema.maxSize) {
391
+ errors.push(`${path}: Object too large (max: ${schema.maxSize} bytes)`);
392
+ }
393
+ }
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Sanitize a string for safe use
399
+ */
400
+ sanitizeString(input, maxLength = 1000) {
401
+ if (typeof input !== 'string') return '';
402
+
403
+ // Remove null bytes and control characters
404
+ let sanitized = input.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
405
+
406
+ // Truncate
407
+ if (sanitized.length > maxLength) {
408
+ sanitized = sanitized.slice(0, maxLength);
409
+ }
410
+
411
+ return sanitized;
412
+ }
413
+
414
+ /**
415
+ * Validate request size
416
+ */
417
+ validateSize(data, type = 'json') {
418
+ const maxSize = SIZE_LIMITS[type] || SIZE_LIMITS.json;
419
+ const size = typeof data === 'string'
420
+ ? data.length
421
+ : JSON.stringify(data).length;
422
+
423
+ return {
424
+ valid: size <= maxSize,
425
+ size,
426
+ maxSize,
427
+ error: size > maxSize ? `Payload too large: ${size} > ${maxSize} bytes` : null,
428
+ };
429
+ }
430
+ }
431
+
432
+ // =============================================================================
433
+ // KARMA RATE LIMITER
434
+ // =============================================================================
435
+
436
+ /**
437
+ * KarmaRateLimiter — KARMA-adaptive rate limiting with SANGHA integration
438
+ */
439
+ export class KarmaRateLimiter extends EventEmitter {
440
+ #buckets;
441
+ #karmaTrust;
442
+ #sangha;
443
+ #validator;
444
+ #stats;
445
+
446
+ constructor() {
447
+ super();
448
+ this.#buckets = new Map();
449
+ this.#karmaTrust = null;
450
+ this.#sangha = null;
451
+ this.#validator = new InputValidator();
452
+ this.#stats = {
453
+ allowed: 0,
454
+ blocked: 0,
455
+ validated: 0,
456
+ rejected: 0,
457
+ };
458
+
459
+ Object.seal(this);
460
+ }
461
+
462
+ /**
463
+ * Bind KARMA trust model for reputation lookups
464
+ */
465
+ bindKarmaTrust(karmaTrust) {
466
+ this.#karmaTrust = karmaTrust;
467
+ log.info('Rate limiter bound to KARMA trust model');
468
+ }
469
+
470
+ /**
471
+ * Bind SANGHA for collective response
472
+ */
473
+ bindSangha(sangha) {
474
+ this.#sangha = sangha;
475
+ log.info('Rate limiter bound to SANGHA collective');
476
+ }
477
+
478
+ /**
479
+ * Get or create rate bucket for a peer
480
+ */
481
+ #getBucket(peerId) {
482
+ let bucket = this.#buckets.get(peerId);
483
+
484
+ if (!bucket) {
485
+ // Get KARMA score if available
486
+ const karma = this.#karmaTrust?.getTrustScore?.(peerId) ?? -1;
487
+ bucket = new RateBucket(peerId, karma);
488
+ this.#buckets.set(peerId, bucket);
489
+
490
+ log.debug('Created rate bucket', {
491
+ peerId: peerId.slice(0, 16),
492
+ karma,
493
+ tier: bucket.tier.label,
494
+ });
495
+ }
496
+
497
+ return bucket;
498
+ }
499
+
500
+ /**
501
+ * Check if request is allowed
502
+ * @param {string} peerId - Peer making the request
503
+ * @param {number} cost - Request cost (default 1)
504
+ * @returns {{ allowed: boolean, remaining: number, tier: string, reason?: string }}
505
+ */
506
+ checkLimit(peerId, cost = 1) {
507
+ const bucket = this.#getBucket(peerId);
508
+
509
+ // Update KARMA in case it changed
510
+ if (this.#karmaTrust) {
511
+ const currentKarma = this.#karmaTrust.getTrustScore?.(peerId) ?? -1;
512
+ bucket.updateKarma(currentKarma);
513
+ }
514
+
515
+ const result = bucket.consume(cost);
516
+
517
+ if (result.allowed) {
518
+ this.#stats.allowed++;
519
+ } else {
520
+ this.#stats.blocked++;
521
+
522
+ // Emit event for monitoring
523
+ this.emit('blocked', { peerId, reason: result.reason });
524
+
525
+ // Alert SANGHA if many peers are being blocked (possible attack)
526
+ if (this.#stats.blocked % 100 === 0) {
527
+ log.warn('High block rate detected — possible flood attack', {
528
+ blocked: this.#stats.blocked,
529
+ allowed: this.#stats.allowed,
530
+ });
531
+ }
532
+ }
533
+
534
+ return {
535
+ allowed: result.allowed,
536
+ remaining: result.remaining,
537
+ tier: bucket.tier.label,
538
+ reason: result.reason,
539
+ retryAfter: result.retryAfter,
540
+ };
541
+ }
542
+
543
+ /**
544
+ * Validate and rate-limit a request
545
+ * @param {string} peerId - Peer making the request
546
+ * @param {any} data - Request data
547
+ * @param {string} schemaName - Schema to validate against
548
+ * @param {number} cost - Request cost
549
+ * @returns {{ allowed: boolean, valid: boolean, errors: string[], tier: string }}
550
+ */
551
+ validateAndLimit(peerId, data, schemaName, cost = 1) {
552
+ // First check rate limit
553
+ const limitResult = this.checkLimit(peerId, cost);
554
+ if (!limitResult.allowed) {
555
+ return {
556
+ allowed: false,
557
+ valid: false,
558
+ errors: [limitResult.reason],
559
+ tier: limitResult.tier,
560
+ retryAfter: limitResult.retryAfter,
561
+ };
562
+ }
563
+
564
+ // Then validate
565
+ const validation = this.#validator.validate(data, schemaName);
566
+
567
+ if (validation.valid) {
568
+ this.#stats.validated++;
569
+ } else {
570
+ this.#stats.rejected++;
571
+
572
+ log.debug('Validation failed', {
573
+ peerId: peerId.slice(0, 16),
574
+ schema: schemaName,
575
+ errors: validation.errors.slice(0, 3),
576
+ });
577
+ }
578
+
579
+ return {
580
+ allowed: true,
581
+ valid: validation.valid,
582
+ errors: validation.errors,
583
+ tier: limitResult.tier,
584
+ remaining: limitResult.remaining,
585
+ };
586
+ }
587
+
588
+ /**
589
+ * Check payload size
590
+ * @param {string} peerId
591
+ * @param {any} data
592
+ * @param {string} type
593
+ * @returns {{ allowed: boolean, valid: boolean, error?: string }}
594
+ */
595
+ checkSize(peerId, data, type = 'json') {
596
+ const limitResult = this.checkLimit(peerId, 0); // Don't consume tokens, just check block
597
+ if (!limitResult.allowed) {
598
+ return { allowed: false, valid: false, error: limitResult.reason };
599
+ }
600
+
601
+ const sizeCheck = this.#validator.validateSize(data, type);
602
+ return {
603
+ allowed: true,
604
+ valid: sizeCheck.valid,
605
+ error: sizeCheck.error,
606
+ size: sizeCheck.size,
607
+ maxSize: sizeCheck.maxSize,
608
+ };
609
+ }
610
+
611
+ /**
612
+ * Get the input validator
613
+ */
614
+ getValidator() {
615
+ return this.#validator;
616
+ }
617
+
618
+ /**
619
+ * Get state for SANGHA attestation
620
+ */
621
+ getState() {
622
+ return {
623
+ component: 'rate-limiter',
624
+ buckets: this.#buckets.size,
625
+ stats: { ...this.#stats },
626
+ tiers: Object.fromEntries(
627
+ Object.entries(KARMA_TIERS).map(([k, v]) => [k, v.limit])
628
+ ),
629
+ };
630
+ }
631
+
632
+ /**
633
+ * Get status for API
634
+ */
635
+ getStatus() {
636
+ // Count peers by tier
637
+ const byTier = {};
638
+ for (const tier of Object.values(KARMA_TIERS)) {
639
+ byTier[tier.label] = 0;
640
+ }
641
+ for (const bucket of this.#buckets.values()) {
642
+ byTier[bucket.tier.label]++;
643
+ }
644
+
645
+ return {
646
+ trackedPeers: this.#buckets.size,
647
+ byTier,
648
+ stats: { ...this.#stats },
649
+ karmaBound: !!this.#karmaTrust,
650
+ sanghaBound: !!this.#sangha,
651
+ };
652
+ }
653
+
654
+ /**
655
+ * Cleanup old buckets (call periodically)
656
+ */
657
+ cleanup(maxAge = 3600000) {
658
+ const now = Date.now();
659
+ let removed = 0;
660
+
661
+ for (const [peerId, bucket] of this.#buckets) {
662
+ // Remove buckets that haven't been used recently and aren't blocked
663
+ if (bucket.lastRefill < now - maxAge && bucket.blockedUntil < now) {
664
+ this.#buckets.delete(peerId);
665
+ removed++;
666
+ }
667
+ }
668
+
669
+ if (removed > 0) {
670
+ log.debug('Cleaned up old rate buckets', { removed, remaining: this.#buckets.size });
671
+ }
672
+ }
673
+ }
674
+
675
+ // =============================================================================
676
+ // SINGLETON & EXPORTS
677
+ // =============================================================================
678
+
679
+ let _instance = null;
680
+
681
+ /**
682
+ * Get the KarmaRateLimiter singleton
683
+ * @returns {KarmaRateLimiter}
684
+ */
685
+ export function getKarmaRateLimiter() {
686
+ if (!_instance) {
687
+ _instance = new KarmaRateLimiter();
688
+ }
689
+ return _instance;
690
+ }
691
+
692
+ export default KarmaRateLimiter;