@runtime-digital-twin/sdk 1.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 (56) hide show
  1. package/README.md +214 -0
  2. package/dist/constants.d.ts +11 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +13 -0
  5. package/dist/db-wrapper.d.ts +258 -0
  6. package/dist/db-wrapper.d.ts.map +1 -0
  7. package/dist/db-wrapper.js +636 -0
  8. package/dist/event-envelope.d.ts +35 -0
  9. package/dist/event-envelope.d.ts.map +1 -0
  10. package/dist/event-envelope.js +101 -0
  11. package/dist/fastify-plugin.d.ts +29 -0
  12. package/dist/fastify-plugin.d.ts.map +1 -0
  13. package/dist/fastify-plugin.js +243 -0
  14. package/dist/http-sentinels.d.ts +39 -0
  15. package/dist/http-sentinels.d.ts.map +1 -0
  16. package/dist/http-sentinels.js +169 -0
  17. package/dist/http-wrapper.d.ts +25 -0
  18. package/dist/http-wrapper.d.ts.map +1 -0
  19. package/dist/http-wrapper.js +477 -0
  20. package/dist/index.d.ts +19 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +93 -0
  23. package/dist/invariants.d.ts +58 -0
  24. package/dist/invariants.d.ts.map +1 -0
  25. package/dist/invariants.js +192 -0
  26. package/dist/multi-service-edge-builder.d.ts +80 -0
  27. package/dist/multi-service-edge-builder.d.ts.map +1 -0
  28. package/dist/multi-service-edge-builder.js +107 -0
  29. package/dist/outbound-matcher.d.ts +192 -0
  30. package/dist/outbound-matcher.d.ts.map +1 -0
  31. package/dist/outbound-matcher.js +457 -0
  32. package/dist/peer-service-resolver.d.ts +22 -0
  33. package/dist/peer-service-resolver.d.ts.map +1 -0
  34. package/dist/peer-service-resolver.js +85 -0
  35. package/dist/redaction.d.ts +111 -0
  36. package/dist/redaction.d.ts.map +1 -0
  37. package/dist/redaction.js +487 -0
  38. package/dist/replay-logger.d.ts +438 -0
  39. package/dist/replay-logger.d.ts.map +1 -0
  40. package/dist/replay-logger.js +434 -0
  41. package/dist/root-cause-analyzer.d.ts +45 -0
  42. package/dist/root-cause-analyzer.d.ts.map +1 -0
  43. package/dist/root-cause-analyzer.js +606 -0
  44. package/dist/shape-digest-utils.d.ts +45 -0
  45. package/dist/shape-digest-utils.d.ts.map +1 -0
  46. package/dist/shape-digest-utils.js +154 -0
  47. package/dist/trace-bundle-writer.d.ts +52 -0
  48. package/dist/trace-bundle-writer.d.ts.map +1 -0
  49. package/dist/trace-bundle-writer.js +267 -0
  50. package/dist/trace-loader.d.ts +69 -0
  51. package/dist/trace-loader.d.ts.map +1 -0
  52. package/dist/trace-loader.js +146 -0
  53. package/dist/trace-uploader.d.ts +25 -0
  54. package/dist/trace-uploader.d.ts.map +1 -0
  55. package/dist/trace-uploader.js +132 -0
  56. package/package.json +63 -0
@@ -0,0 +1,487 @@
1
+ "use strict";
2
+ /**
3
+ * Redaction Module
4
+ *
5
+ * Provides privacy guardrails for trace capture by:
6
+ * - Masking sensitive headers (auth, tokens, cookies)
7
+ * - Redacting passwords and secrets from request/response bodies
8
+ * - Supporting custom redaction rules via config file
9
+ * - Using deterministic hashing for redacted values (stable across runs)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SENSITIVE_BODY_FIELDS = exports.SENSITIVE_QUERY_PARAMS = exports.SENSITIVE_HEADERS = void 0;
13
+ exports.computeRedactionHash = computeRedactionHash;
14
+ exports.createRedactedValue = createRedactedValue;
15
+ exports.loadRedactionConfig = loadRedactionConfig;
16
+ exports.setRedactionConfig = setRedactionConfig;
17
+ exports.getRedactionConfig = getRedactionConfig;
18
+ exports.resetRedactionConfig = resetRedactionConfig;
19
+ exports.shouldRedactHeader = shouldRedactHeader;
20
+ exports.shouldRedactQueryParam = shouldRedactQueryParam;
21
+ exports.redactHeaders = redactHeaders;
22
+ exports.redactQueryParams = redactQueryParams;
23
+ exports.redactBodyObject = redactBodyObject;
24
+ exports.redactBodyPatterns = redactBodyPatterns;
25
+ exports.redactBody = redactBody;
26
+ exports.redactUrl = redactUrl;
27
+ exports.containsSensitivePatterns = containsSensitivePatterns;
28
+ const crypto_1 = require("crypto");
29
+ const fs_1 = require("fs");
30
+ const path_1 = require("path");
31
+ /**
32
+ * Default sensitive header names (case-insensitive)
33
+ */
34
+ const DEFAULT_SENSITIVE_HEADERS = [
35
+ "authorization",
36
+ "x-api-key",
37
+ "x-auth-token",
38
+ "x-access-token",
39
+ "x-refresh-token",
40
+ "cookie",
41
+ "set-cookie",
42
+ "x-csrf-token",
43
+ "x-xsrf-token",
44
+ "proxy-authorization",
45
+ "www-authenticate",
46
+ "x-amz-security-token",
47
+ "x-api-secret",
48
+ "x-client-secret",
49
+ ];
50
+ /**
51
+ * Default sensitive query parameter names (case-insensitive)
52
+ */
53
+ const DEFAULT_SENSITIVE_QUERY_PARAMS = [
54
+ "token",
55
+ "access_token",
56
+ "refresh_token",
57
+ "api_key",
58
+ "apikey",
59
+ "secret",
60
+ "password",
61
+ "pwd",
62
+ "auth",
63
+ "key",
64
+ "session",
65
+ "sid",
66
+ ];
67
+ /**
68
+ * Default sensitive body field names (case-insensitive)
69
+ */
70
+ const DEFAULT_SENSITIVE_BODY_FIELDS = [
71
+ "password",
72
+ "passwd",
73
+ "pwd",
74
+ "secret",
75
+ "token",
76
+ "access_token",
77
+ "refresh_token",
78
+ "api_key",
79
+ "apiKey",
80
+ "apikey",
81
+ "private_key",
82
+ "privateKey",
83
+ "secret_key",
84
+ "secretKey",
85
+ "client_secret",
86
+ "clientSecret",
87
+ "ssn",
88
+ "social_security",
89
+ "credit_card",
90
+ "creditCard",
91
+ "card_number",
92
+ "cardNumber",
93
+ "cvv",
94
+ "cvc",
95
+ "pin",
96
+ ];
97
+ /**
98
+ * Default body patterns to redact (regex)
99
+ */
100
+ const DEFAULT_SENSITIVE_BODY_PATTERNS = [
101
+ // Bearer tokens
102
+ /Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*/g,
103
+ // JWT tokens
104
+ /eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*/g,
105
+ // API keys (common formats)
106
+ /(?:api[_-]?key|apikey)[=:]["']?[A-Za-z0-9\-_]{20,}["']?/gi,
107
+ // AWS access keys
108
+ /AKIA[0-9A-Z]{16}/g,
109
+ // Credit card numbers (basic pattern)
110
+ /\b(?:\d{4}[- ]?){3}\d{4}\b/g,
111
+ ];
112
+ /**
113
+ * Build default redaction rules
114
+ */
115
+ function buildDefaultRules() {
116
+ const rules = [];
117
+ // Header rules
118
+ for (const header of DEFAULT_SENSITIVE_HEADERS) {
119
+ rules.push({
120
+ type: "header",
121
+ name: header,
122
+ hash: true,
123
+ });
124
+ }
125
+ // Query param rules
126
+ for (const param of DEFAULT_SENSITIVE_QUERY_PARAMS) {
127
+ rules.push({
128
+ type: "query_param",
129
+ name: param,
130
+ hash: true,
131
+ });
132
+ }
133
+ // Body field rules
134
+ for (const field of DEFAULT_SENSITIVE_BODY_FIELDS) {
135
+ rules.push({
136
+ type: "body_field",
137
+ name: field,
138
+ hash: true,
139
+ });
140
+ }
141
+ // Body pattern rules
142
+ for (const pattern of DEFAULT_SENSITIVE_BODY_PATTERNS) {
143
+ rules.push({
144
+ type: "body_pattern",
145
+ pattern: pattern.source,
146
+ hash: true,
147
+ });
148
+ }
149
+ return rules;
150
+ }
151
+ /**
152
+ * Default redaction configuration
153
+ */
154
+ const DEFAULT_CONFIG = {
155
+ enabled: true,
156
+ rules: buildDefaultRules(),
157
+ hashSalt: "rdt-redaction-salt-v1",
158
+ };
159
+ /**
160
+ * Global redaction config (can be overridden)
161
+ */
162
+ let globalConfig = { ...DEFAULT_CONFIG };
163
+ /**
164
+ * Compute a deterministic hash for a redacted value
165
+ * The hash is stable across runs given the same input and salt
166
+ */
167
+ function computeRedactionHash(value, salt = globalConfig.hashSalt || "") {
168
+ const hash = (0, crypto_1.createHash)("sha256")
169
+ .update(salt + value)
170
+ .digest("hex")
171
+ .substring(0, 8);
172
+ return hash;
173
+ }
174
+ /**
175
+ * Create a redacted placeholder
176
+ */
177
+ function createRedactedValue(originalValue, includeHash = true, salt) {
178
+ if (!includeHash) {
179
+ return "[REDACTED]";
180
+ }
181
+ const hash = computeRedactionHash(originalValue, salt);
182
+ return `[REDACTED:${hash}]`;
183
+ }
184
+ /**
185
+ * Load redaction config from file
186
+ */
187
+ function loadRedactionConfig(configPath) {
188
+ // Try to find config file
189
+ const searchPaths = configPath
190
+ ? [configPath]
191
+ : [
192
+ (0, path_1.join)(process.cwd(), ".rdt.json"),
193
+ (0, path_1.join)(process.cwd(), ".rdt/config.json"),
194
+ (0, path_1.join)(process.cwd(), "rdt.config.json"),
195
+ ];
196
+ for (const path of searchPaths) {
197
+ if ((0, fs_1.existsSync)(path)) {
198
+ try {
199
+ const content = (0, fs_1.readFileSync)(path, "utf8");
200
+ const config = JSON.parse(content);
201
+ if (config.redaction) {
202
+ const userConfig = config.redaction;
203
+ // Merge with defaults
204
+ const mergedConfig = {
205
+ enabled: userConfig.enabled ?? true,
206
+ rules: userConfig.overrideDefaults
207
+ ? userConfig.rules || []
208
+ : [...buildDefaultRules(), ...(userConfig.rules || [])],
209
+ overrideDefaults: userConfig.overrideDefaults,
210
+ hashSalt: userConfig.hashSalt || DEFAULT_CONFIG.hashSalt,
211
+ };
212
+ return mergedConfig;
213
+ }
214
+ }
215
+ catch (error) {
216
+ console.warn(`[Redaction] Failed to load config from ${path}: ${error}`);
217
+ }
218
+ }
219
+ }
220
+ return { ...DEFAULT_CONFIG };
221
+ }
222
+ /**
223
+ * Set global redaction config
224
+ */
225
+ function setRedactionConfig(config) {
226
+ globalConfig = {
227
+ ...DEFAULT_CONFIG,
228
+ ...config,
229
+ rules: config.overrideDefaults
230
+ ? config.rules || []
231
+ : [...buildDefaultRules(), ...(config.rules || [])],
232
+ };
233
+ }
234
+ /**
235
+ * Get current redaction config
236
+ */
237
+ function getRedactionConfig() {
238
+ return { ...globalConfig };
239
+ }
240
+ /**
241
+ * Reset redaction config to defaults
242
+ */
243
+ function resetRedactionConfig() {
244
+ globalConfig = { ...DEFAULT_CONFIG, rules: buildDefaultRules() };
245
+ }
246
+ /**
247
+ * Check if a header name should be redacted
248
+ */
249
+ function shouldRedactHeader(headerName) {
250
+ if (!globalConfig.enabled)
251
+ return false;
252
+ const lowerName = headerName.toLowerCase();
253
+ for (const rule of globalConfig.rules) {
254
+ if (rule.type !== "header")
255
+ continue;
256
+ if (rule.regex && rule.name) {
257
+ const regex = new RegExp(rule.name, "i");
258
+ if (regex.test(lowerName))
259
+ return true;
260
+ }
261
+ else if (rule.name) {
262
+ if (rule.name.toLowerCase() === lowerName)
263
+ return true;
264
+ }
265
+ }
266
+ return false;
267
+ }
268
+ /**
269
+ * Check if a query param should be redacted
270
+ */
271
+ function shouldRedactQueryParam(paramName) {
272
+ if (!globalConfig.enabled)
273
+ return false;
274
+ const lowerName = paramName.toLowerCase();
275
+ for (const rule of globalConfig.rules) {
276
+ if (rule.type !== "query_param")
277
+ continue;
278
+ if (rule.regex && rule.name) {
279
+ const regex = new RegExp(rule.name, "i");
280
+ if (regex.test(lowerName))
281
+ return true;
282
+ }
283
+ else if (rule.name) {
284
+ if (rule.name.toLowerCase() === lowerName)
285
+ return true;
286
+ }
287
+ }
288
+ return false;
289
+ }
290
+ /**
291
+ * Redact headers in a headers object
292
+ */
293
+ function redactHeaders(headers) {
294
+ if (!globalConfig.enabled)
295
+ return headers;
296
+ const redacted = {};
297
+ for (const [key, value] of Object.entries(headers)) {
298
+ if (value === undefined) {
299
+ redacted[key] = value;
300
+ continue;
301
+ }
302
+ if (shouldRedactHeader(key)) {
303
+ const originalValue = Array.isArray(value) ? value.join(", ") : value;
304
+ redacted[key] = createRedactedValue(originalValue, true, globalConfig.hashSalt);
305
+ }
306
+ else {
307
+ redacted[key] = value;
308
+ }
309
+ }
310
+ return redacted;
311
+ }
312
+ /**
313
+ * Redact query parameters
314
+ */
315
+ function redactQueryParams(query) {
316
+ if (!globalConfig.enabled)
317
+ return query;
318
+ const redacted = {};
319
+ for (const [key, value] of Object.entries(query)) {
320
+ if (shouldRedactQueryParam(key)) {
321
+ const originalValue = typeof value === "string" ? value : JSON.stringify(value);
322
+ redacted[key] = createRedactedValue(originalValue, true, globalConfig.hashSalt);
323
+ }
324
+ else if (typeof value === "object" && value !== null) {
325
+ redacted[key] = redactQueryParams(value);
326
+ }
327
+ else {
328
+ redacted[key] = value;
329
+ }
330
+ }
331
+ return redacted;
332
+ }
333
+ /**
334
+ * Check if a body field name should be redacted
335
+ */
336
+ function shouldRedactBodyField(fieldName) {
337
+ const lowerName = fieldName.toLowerCase();
338
+ for (const rule of globalConfig.rules) {
339
+ if (rule.type !== "body_field")
340
+ continue;
341
+ if (rule.regex && rule.name) {
342
+ const regex = new RegExp(rule.name, "i");
343
+ if (regex.test(lowerName))
344
+ return true;
345
+ }
346
+ else if (rule.name) {
347
+ if (rule.name.toLowerCase() === lowerName)
348
+ return true;
349
+ }
350
+ }
351
+ return false;
352
+ }
353
+ /**
354
+ * Redact sensitive fields from a body object (recursive)
355
+ */
356
+ function redactBodyObject(body) {
357
+ if (!globalConfig.enabled)
358
+ return body;
359
+ if (body === null || body === undefined) {
360
+ return body;
361
+ }
362
+ if (typeof body !== "object") {
363
+ return body;
364
+ }
365
+ if (Array.isArray(body)) {
366
+ return body.map((item) => redactBodyObject(item));
367
+ }
368
+ const redacted = {};
369
+ for (const [key, value] of Object.entries(body)) {
370
+ if (shouldRedactBodyField(key)) {
371
+ const originalValue = typeof value === "string" ? value : JSON.stringify(value);
372
+ redacted[key] = createRedactedValue(originalValue, true, globalConfig.hashSalt);
373
+ }
374
+ else if (typeof value === "object" && value !== null) {
375
+ redacted[key] = redactBodyObject(value);
376
+ }
377
+ else {
378
+ redacted[key] = value;
379
+ }
380
+ }
381
+ return redacted;
382
+ }
383
+ /**
384
+ * Redact patterns from a body string
385
+ */
386
+ function redactBodyPatterns(body) {
387
+ if (!globalConfig.enabled)
388
+ return body;
389
+ let result = body;
390
+ for (const rule of globalConfig.rules) {
391
+ if (rule.type !== "body_pattern" || !rule.pattern)
392
+ continue;
393
+ try {
394
+ const regex = new RegExp(rule.pattern, "g");
395
+ result = result.replace(regex, (match) => {
396
+ return createRedactedValue(match, rule.hash ?? true, globalConfig.hashSalt);
397
+ });
398
+ }
399
+ catch {
400
+ // Invalid regex, skip
401
+ console.warn(`[Redaction] Invalid pattern: ${rule.pattern}`);
402
+ }
403
+ }
404
+ return result;
405
+ }
406
+ /**
407
+ * Redact a body (handles both string and object)
408
+ */
409
+ function redactBody(body) {
410
+ if (!globalConfig.enabled)
411
+ return body;
412
+ if (body === null || body === undefined) {
413
+ return body;
414
+ }
415
+ if (typeof body === "string") {
416
+ // Try to parse as JSON
417
+ try {
418
+ const parsed = JSON.parse(body);
419
+ const redacted = redactBodyObject(parsed);
420
+ let result = JSON.stringify(redacted);
421
+ // Also apply pattern matching
422
+ result = redactBodyPatterns(result);
423
+ return result;
424
+ }
425
+ catch {
426
+ // Not JSON, apply pattern matching only
427
+ return redactBodyPatterns(body);
428
+ }
429
+ }
430
+ if (typeof body === "object") {
431
+ const redacted = redactBodyObject(body);
432
+ // Also check for patterns in string values
433
+ return JSON.parse(redactBodyPatterns(JSON.stringify(redacted)));
434
+ }
435
+ return body;
436
+ }
437
+ /**
438
+ * Redact a URL (query params)
439
+ */
440
+ function redactUrl(url) {
441
+ if (!globalConfig.enabled)
442
+ return url;
443
+ try {
444
+ const urlObj = new URL(url, "http://placeholder");
445
+ const redactedParams = new URLSearchParams();
446
+ for (const [key, value] of urlObj.searchParams) {
447
+ if (shouldRedactQueryParam(key)) {
448
+ redactedParams.set(key, createRedactedValue(value, true, globalConfig.hashSalt));
449
+ }
450
+ else {
451
+ redactedParams.set(key, value);
452
+ }
453
+ }
454
+ urlObj.search = redactedParams.toString();
455
+ // Return the full URL if it was absolute, or just path+query if relative
456
+ if (url.startsWith("http://") || url.startsWith("https://")) {
457
+ return urlObj.toString();
458
+ }
459
+ return urlObj.pathname + (urlObj.search ? urlObj.search : "");
460
+ }
461
+ catch {
462
+ // Invalid URL, return as-is
463
+ return url;
464
+ }
465
+ }
466
+ /**
467
+ * Check if a value looks like it contains sensitive data
468
+ * (heuristic-based, for additional protection)
469
+ */
470
+ function containsSensitivePatterns(value) {
471
+ // Check for common sensitive patterns
472
+ const sensitivePatterns = [
473
+ /Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/,
474
+ /eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/,
475
+ /password[=:]/i,
476
+ /secret[=:]/i,
477
+ /token[=:]/i,
478
+ /api[_-]?key[=:]/i,
479
+ ];
480
+ return sensitivePatterns.some((pattern) => pattern.test(value));
481
+ }
482
+ /**
483
+ * Export default sensitive lists for testing
484
+ */
485
+ exports.SENSITIVE_HEADERS = DEFAULT_SENSITIVE_HEADERS;
486
+ exports.SENSITIVE_QUERY_PARAMS = DEFAULT_SENSITIVE_QUERY_PARAMS;
487
+ exports.SENSITIVE_BODY_FIELDS = DEFAULT_SENSITIVE_BODY_FIELDS;