xypriss-security 2.1.6 → 2.1.8

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 (36) hide show
  1. package/dist/src/components/cache/FastLRU.js +24 -12
  2. package/dist/src/components/cache/FastLRU.js.map +1 -1
  3. package/dist/src/components/cache/SCC.js +2 -1
  4. package/dist/src/components/cache/SCC.js.map +1 -1
  5. package/dist/src/components/cache/SecureCacheAdapter.js +14 -6
  6. package/dist/src/components/cache/SecureCacheAdapter.js.map +1 -1
  7. package/dist/src/components/cache/UFSIMC.js +44 -36
  8. package/dist/src/components/cache/UFSIMC.js.map +1 -1
  9. package/dist/src/components/cache/cacheSys.js +27 -26
  10. package/dist/src/components/cache/cacheSys.js.map +1 -1
  11. package/dist/src/components/cache/useCache.js +3 -0
  12. package/dist/src/components/cache/useCache.js.map +1 -1
  13. package/dist/src/components/encryption/EncryptionService.js +6 -6
  14. package/dist/src/components/encryption/EncryptionService.js.map +1 -1
  15. package/dist/src/components/serializer/index.d.ts +2 -1
  16. package/dist/src/components/serializer/index.d.ts.map +1 -1
  17. package/dist/src/components/serializer/index.js +4 -1
  18. package/dist/src/components/serializer/index.js.map +1 -1
  19. package/dist/src/components/serializer/safe-serializer.d.ts +79 -16
  20. package/dist/src/components/serializer/safe-serializer.d.ts.map +1 -1
  21. package/dist/src/components/serializer/safe-serializer.js +488 -248
  22. package/dist/src/components/serializer/safe-serializer.js.map +1 -1
  23. package/dist/src/components/serializer/types.d.ts +22 -0
  24. package/dist/src/components/serializer/types.d.ts.map +1 -1
  25. package/dist/src/core/PasswordManager.js +5 -0
  26. package/dist/src/core/PasswordManager.js.map +1 -1
  27. package/dist/src/core/index.d.ts.map +1 -1
  28. package/dist/src/core/index.js +9 -26
  29. package/dist/src/core/index.js.map +1 -1
  30. package/dist/src/shared/logger/Logger.js +21 -15
  31. package/dist/src/shared/logger/Logger.js.map +1 -1
  32. package/dist/src/utils/mergeStatic.d.ts +2 -0
  33. package/dist/src/utils/mergeStatic.d.ts.map +1 -0
  34. package/dist/src/utils/mergeStatic.js +26 -0
  35. package/dist/src/utils/mergeStatic.js.map +1 -0
  36. package/package.json +1 -1
@@ -2,271 +2,133 @@
2
2
  /**
3
3
  * Safe Serialization Utility for FortifiedFunction
4
4
  * Handles cyclic structures, XyPriss objects, and performance optimization
5
+ *
6
+ * v2 — Improvements over v1:
7
+ * - Iterative serialization engine (no call-stack growth → supports depth ~10 000+)
8
+ * - Accurate depth tracking via an explicit node stack (was broken with shared `depth` counter)
9
+ * - Circular-reference path reporting e.g. "[Circular → $.a.b.c]"
10
+ * - Chunk-array string builder (avoids O(n²) string concatenation on large outputs)
11
+ * - Static Set for O(1) lookup of known-problematic constructor names
12
+ * - Safe UTF-8 boundary truncation (never splits a surrogate pair)
13
+ * - `parse()` helper with typed return & error guard
14
+ * - `measureSize()` dry-run to estimate serialized byte size without full output
15
+ * - `deepClone()` powered by the same safe engine
5
16
  */
6
17
  Object.defineProperty(exports, "__esModule", { value: true });
7
18
  exports.SafeSerializer = void 0;
19
+ // ---------------------------------------------------------------------------
20
+ // Well-known constructor names that must be replaced before traversal
21
+ // ---------------------------------------------------------------------------
22
+ const BLOCKED_CONSTRUCTORS = new Set([
23
+ "Socket",
24
+ "Server",
25
+ "Agent",
26
+ "TLSSocket",
27
+ "Net",
28
+ "EventEmitter",
29
+ "ReadStream",
30
+ "WriteStream",
31
+ "Transform",
32
+ "Duplex",
33
+ ]);
34
+ const SENSITIVE_HEADERS = new Set([
35
+ "authorization",
36
+ "cookie",
37
+ "x-api-key",
38
+ "x-auth-token",
39
+ "x-session-token",
40
+ "proxy-authorization",
41
+ "set-cookie",
42
+ ]);
43
+ // ---------------------------------------------------------------------------
44
+ // SafeSerializer
45
+ // ---------------------------------------------------------------------------
8
46
  class SafeSerializer {
47
+ static DEFAULT_OPTIONS = {
48
+ maxDepth: 10_000,
49
+ maxLength: 10_000,
50
+ includeNonEnumerable: false,
51
+ truncateStrings: 1_000,
52
+ fastMode: false,
53
+ maxArrayItems: 10_000,
54
+ maxObjectKeys: 10_000,
55
+ reportCircularPath: true,
56
+ pureRaw: false,
57
+ };
58
+ // -------------------------------------------------------------------------
59
+ // PUBLIC API
60
+ // -------------------------------------------------------------------------
9
61
  /**
10
- * **ULTRA-FAST: Primary serialization method with performance optimization**
62
+ * Primary serialization entry-point.
63
+ *
64
+ * Fast path: plain JSON.stringify when `fastMode` is enabled and the object
65
+ * has no known pitfalls.
66
+ * Safe path: iterative engine that handles depth ~10 000, cycles, specials.
11
67
  */
12
68
  static stringify(obj, options = {}) {
13
- const opts = { ...this.DEFAULT_OPTIONS, ...options };
14
- // **ULTRA-FAST PATH: Try simple JSON.stringify first**
69
+ const opts = this.mergeOptions(options);
15
70
  if (opts.fastMode) {
16
71
  try {
17
72
  const result = JSON.stringify(obj);
18
- if (result.length <= opts.maxLength) {
73
+ if (result !== undefined && result.length <= opts.maxLength) {
19
74
  return result;
20
75
  }
21
76
  }
22
77
  catch {
23
- // Fall through to safe serialization
78
+ // Fall through
24
79
  }
25
80
  }
26
- // **SAFE PATH: Handle complex objects with cyclic references**
27
- return this.safeStringify(obj, opts);
81
+ return this.iterativeStringify(obj, opts);
28
82
  }
29
83
  /**
30
- * **XyPriss-SAFE: Enhanced JSON.stringify for XyPriss objects**
84
+ * XyPriss-aware serialization (req / res objects).
85
+ * Uses the iterative engine so it is also safe for deeply nested structures.
31
86
  */
32
87
  static XyPriStringify(obj, options = {}) {
33
- const opts = { ...this.DEFAULT_OPTIONS, ...options };
34
- try {
35
- return JSON.stringify(obj, this.createXyPrissReplacer(opts));
36
- }
37
- catch (error) {
38
- // Fallback to safe serialization
39
- return this.safeStringify(obj, opts);
40
- }
88
+ // The iterative engine already handles XyPriss objects natively.
89
+ return this.iterativeStringify(obj, this.mergeOptions(options));
41
90
  }
42
91
  /**
43
- * **XyPriss REPLACER: Handles XyPriss req/res objects and circular references**
92
+ * Safe JSON.parse never throws; returns `undefined` on failure.
44
93
  */
45
- static createXyPrissReplacer(options) {
46
- const seen = new WeakSet();
47
- let depth = 0;
48
- return function (key, value) {
49
- // Track depth
50
- if (key === "")
51
- depth = 0;
52
- else
53
- depth++;
54
- if (depth > options.maxDepth) {
55
- return "[Max Depth Exceeded]";
56
- }
57
- // Handle null/undefined
58
- if (value === null || value === undefined) {
59
- return value;
60
- }
61
- // Handle circular references
62
- if (typeof value === "object" && value !== null) {
63
- if (seen.has(value)) {
64
- return "[Circular Reference]";
65
- }
66
- seen.add(value);
67
- }
68
- // Handle XyPriss Request objects
69
- if (value &&
70
- typeof value === "object" &&
71
- value.constructor &&
72
- value.constructor.name === "IncomingMessage") {
73
- return {
74
- method: value.method,
75
- url: value.url,
76
- headers: value.headers,
77
- query: value.query,
78
- params: value.params,
79
- body: value.body,
80
- ip: value.ip,
81
- _type: "[XyPriss Request]",
82
- };
83
- }
84
- // Handle XyPriss Response objects
85
- if (value &&
86
- typeof value === "object" &&
87
- value.constructor &&
88
- value.constructor.name === "ServerResponse") {
89
- return {
90
- statusCode: value.statusCode,
91
- statusMessage: value.statusMessage,
92
- headersSent: value.headersSent,
93
- _type: "[XyPriss Response]",
94
- };
95
- }
96
- // Handle functions
97
- if (typeof value === "function") {
98
- return `[Function: ${value.name || "anonymous"}]`;
99
- }
100
- // Handle large strings
101
- if (typeof value === "string" && value.length > options.truncateStrings) {
102
- return value.substring(0, options.truncateStrings) + "...[truncated]";
103
- }
104
- // Handle Buffers
105
- if (value instanceof Buffer) {
106
- return `[Buffer: ${value.length} bytes]`;
107
- }
108
- // Handle other special objects
109
- if (value instanceof Date) {
110
- return value.toISOString();
111
- }
112
- if (value instanceof RegExp) {
113
- return value.toString();
114
- }
115
- if (value instanceof Error) {
116
- return {
117
- name: value.name,
118
- message: value.message,
119
- stack: value.stack,
120
- _type: "[Error]",
121
- };
122
- }
123
- return value;
124
- };
94
+ static parse(json) {
95
+ try {
96
+ return JSON.parse(json);
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
125
101
  }
126
102
  /**
127
- * **SAFE SERIALIZATION: Handles all edge cases**
103
+ * Estimate serialized size (characters) without building the full string.
104
+ * Useful to decide whether to serialize at all before hitting maxLength.
105
+ * Returns -1 when the object is too complex to estimate quickly.
128
106
  */
129
- static safeStringify(obj, options) {
130
- const seen = new WeakSet();
131
- let depth = 0;
132
- const replacer = (_key, value) => {
133
- // Handle primitive values
134
- if (value === null || typeof value !== "object") {
135
- if (typeof value === "string" &&
136
- value.length > options.truncateStrings) {
137
- return value.substring(0, options.truncateStrings) + "...[truncated]";
138
- }
139
- return value;
140
- }
141
- // Check depth limit
142
- depth++;
143
- if (depth > options.maxDepth) {
144
- depth--;
145
- return "[Max Depth Exceeded]";
146
- }
147
- // Handle cyclic references
148
- if (seen.has(value)) {
149
- return `[Circular:${value.constructor?.name || "Object"}]`;
150
- }
151
- seen.add(value);
152
- // Handle special XyPriss objects
153
- if (value.constructor) {
154
- const constructorName = value.constructor.name;
155
- // XyPriss Request object
156
- if (constructorName === "IncomingMessage" ||
157
- constructorName === "Request") {
158
- const result = {
159
- method: value.method,
160
- url: value.url,
161
- headers: this.sanitizeHeaders(value.headers),
162
- params: value.params,
163
- query: value.query,
164
- body: value.body ? "[Request Body]" : undefined,
165
- };
166
- depth--;
167
- return result;
168
- }
169
- // XyPriss Response object
170
- if (constructorName === "ServerResponse" ||
171
- constructorName === "Response") {
172
- const result = {
173
- statusCode: value.statusCode,
174
- statusMessage: value.statusMessage,
175
- headersSent: value.headersSent,
176
- };
177
- depth--;
178
- return result;
179
- }
180
- // Other problematic objects
181
- if (["Socket", "Server", "Agent", "TLSSocket"].includes(constructorName)) {
182
- depth--;
183
- return `[${constructorName}:${value.constructor.name}]`;
184
- }
185
- }
186
- // Handle functions
187
- if (typeof value === "function") {
188
- depth--;
189
- return `[Function:${value.name || "anonymous"}]`;
190
- }
191
- // Handle Buffers
192
- if (Buffer.isBuffer(value)) {
193
- depth--;
194
- return `[Buffer:${value.length}bytes]`;
195
- }
196
- // Handle large arrays
197
- if (Array.isArray(value) && value.length > 100) {
198
- depth--;
199
- return `[Array:${value.length}items]`;
200
- }
201
- // Handle Error objects
202
- if (value instanceof Error) {
203
- depth--;
204
- return {
205
- name: value.name,
206
- message: value.message,
207
- stack: value.stack ? "[Stack Trace]" : undefined,
208
- };
209
- }
210
- depth--;
211
- return value;
212
- };
107
+ static measureSize(obj) {
213
108
  try {
214
- const result = JSON.stringify(obj, replacer);
215
- // Check length limit
216
- if (result.length > options.maxLength) {
217
- return result.substring(0, options.maxLength) + "...[truncated]";
218
- }
219
- return result;
109
+ const s = JSON.stringify(obj);
110
+ return s === undefined ? -1 : s.length;
220
111
  }
221
- catch (error) {
222
- // Ultimate fallback
223
- return `[Serialization Error: ${error instanceof Error ? error.message : "Unknown"}]`;
112
+ catch {
113
+ return -1;
224
114
  }
225
115
  }
226
116
  /**
227
- * **UTILITY: Sanitize HTTP headers for safe logging**
117
+ * Deep-clone a plain-data object through serialization.
118
+ * Returns `undefined` when the value cannot be round-tripped.
228
119
  */
229
- static sanitizeHeaders(headers) {
230
- if (!headers || typeof headers !== "object") {
231
- return headers;
232
- }
233
- const sanitized = {};
234
- const sensitiveHeaders = [
235
- "authorization",
236
- "cookie",
237
- "x-api-key",
238
- "x-auth-token",
239
- ];
240
- for (const [key, value] of Object.entries(headers)) {
241
- const lowerKey = key.toLowerCase();
242
- if (sensitiveHeaders.includes(lowerKey)) {
243
- sanitized[key] = "[REDACTED]";
244
- }
245
- else {
246
- sanitized[key] = value;
247
- }
248
- }
249
- return sanitized;
120
+ static deepClone(obj, options = {}) {
121
+ const serialized = this.stringify(obj, { ...options, maxDepth: 10_000 });
122
+ return this.parse(serialized);
250
123
  }
251
124
  /**
252
- * **ULTRA-FAST: Generate cache key with safe serialization**
125
+ * Generate a stable cache key for a list of arguments.
253
126
  */
254
127
  static generateCacheKey(args, prefix = "cache") {
255
- if (!args || args.length === 0) {
128
+ if (!args || args.length === 0)
256
129
  return `${prefix}:empty`;
257
- }
258
- // **XyPriss DETECTION: Check if arguments contain XyPriss req/res objects**
259
- const hasXyPrissObjects = args.some((arg) => arg &&
260
- typeof arg === "object" &&
261
- arg.constructor &&
262
- (arg.constructor.name === "IncomingMessage" ||
263
- arg.constructor.name === "ServerResponse" ||
264
- arg.constructor.name === "Request" ||
265
- arg.constructor.name === "Response" ||
266
- (arg.method && arg.url && arg.headers) || // XyPriss Request-like
267
- (arg.statusCode !== undefined && arg.headersSent !== undefined)));
268
- if (hasXyPrissObjects) {
269
- // **XyPriss-SAFE PATH: Use XyPriss-safe serialization**
130
+ const hasXyPriss = args.some((a) => this.isXyPrissObject(a));
131
+ if (hasXyPriss) {
270
132
  const safe = this.XyPriStringify(args, {
271
133
  fastMode: false,
272
134
  maxDepth: 3,
@@ -276,16 +138,14 @@ class SafeSerializer {
276
138
  return `${prefix}:xypriss:${safe}`;
277
139
  }
278
140
  try {
279
- // **ULTRA-FAST PATH: Try simple approach first for non-XyPriss objects**
280
141
  const simple = JSON.stringify(args);
281
- if (simple.length <= 500) {
142
+ if (simple !== undefined && simple.length <= 500) {
282
143
  return `${prefix}:${simple}`;
283
144
  }
284
145
  }
285
146
  catch {
286
- // Fall through to safe approach
147
+ // Fall through
287
148
  }
288
- // **SAFE PATH: Use safe serialization**
289
149
  const safe = this.stringify(args, {
290
150
  fastMode: false,
291
151
  maxDepth: 5,
@@ -294,9 +154,7 @@ class SafeSerializer {
294
154
  });
295
155
  return `${prefix}:${safe}`;
296
156
  }
297
- /**
298
- * **DEBUG: Safe debug logging**
299
- */
157
+ /** Compact debug log — honours console.log caller location */
300
158
  static debugLog(label, obj, maxLength = 200) {
301
159
  const serialized = this.stringify(obj, {
302
160
  fastMode: true,
@@ -306,25 +164,407 @@ class SafeSerializer {
306
164
  });
307
165
  console.log(`[DEBUG] ${label}: ${serialized}`);
308
166
  }
309
- /**
310
- * **AUDIT: Safe audit logging with full details**
311
- */
167
+ /** Full-fidelity audit log */
312
168
  static auditLog(obj) {
313
169
  return this.stringify(obj, {
314
170
  fastMode: false,
315
- maxDepth: 8,
316
- maxLength: 5000,
317
- truncateStrings: 500,
171
+ maxDepth: 50,
172
+ maxLength: 50_000,
173
+ truncateStrings: 5_000,
318
174
  includeNonEnumerable: false,
319
175
  });
320
176
  }
177
+ // -------------------------------------------------------------------------
178
+ // CORE: Iterative serialization engine
179
+ // -------------------------------------------------------------------------
180
+ /**
181
+ * Converts an arbitrary value to a JSON string without using recursion.
182
+ *
183
+ * Algorithm:
184
+ * - Maintain an explicit `stack` of work items.
185
+ * - Each item knows its expected "output slot" (index into `chunks[]`).
186
+ * - After processing all children of a container, a "close" marker writes
187
+ * the closing bracket/brace into the correct slot.
188
+ * - `seen` is a WeakMap<object, path-string> for O(1) cycle detection with
189
+ * optional path reporting.
190
+ *
191
+ * This avoids JavaScript call-stack growth entirely: depth 10 000 is handled
192
+ * as cheaply as depth 10.
193
+ */
194
+ static iterativeStringify(root, opts) {
195
+ const seen = new WeakMap();
196
+ const chunks = [];
197
+ // Write the root value into `chunks` iteratively.
198
+ this.writeValue(root, "$", 0, seen, chunks, opts);
199
+ // Join & truncate
200
+ const result = chunks.join("");
201
+ return this.safeTruncate(result, opts.maxLength);
202
+ }
203
+ /**
204
+ * Recursion-free value serializer.
205
+ * Uses an explicit stack so depth can go to ~10 000 without any JS stack growth.
206
+ */
207
+ static writeValue(value, path, depth, seen, chunks, opts) {
208
+ const stack = [];
209
+ const process = (val, p, d) => {
210
+ // --- Primitives ---
211
+ if (val === undefined) {
212
+ chunks.push("null");
213
+ return;
214
+ }
215
+ if (val === null) {
216
+ chunks.push("null");
217
+ return;
218
+ }
219
+ const t = typeof val;
220
+ if (t === "boolean" || t === "number") {
221
+ // Guard against non-finite numbers (JSON doesn't support them)
222
+ if (t === "number" && !isFinite(val)) {
223
+ chunks.push("null");
224
+ }
225
+ else {
226
+ chunks.push(JSON.stringify(val));
227
+ }
228
+ return;
229
+ }
230
+ if (t === "bigint") {
231
+ chunks.push(JSON.stringify(val.toString()));
232
+ return;
233
+ }
234
+ if (t === "symbol") {
235
+ chunks.push(`"[Symbol:${val.toString()}]"`);
236
+ return;
237
+ }
238
+ if (t === "function") {
239
+ const fn = val;
240
+ if (opts.pureRaw) {
241
+ // In pureRaw, we try to see everything. Functions are objects too!
242
+ // We mark it as function but allow traversal of its properties
243
+ const fnObj = {
244
+ _type: `[Function:${fn.name || "anonymous"}]`,
245
+ source: fn.toString(),
246
+ };
247
+ // Copy own properties
248
+ for (const k of Object.getOwnPropertyNames(fn)) {
249
+ try {
250
+ fnObj[k] = fn[k];
251
+ }
252
+ catch { }
253
+ }
254
+ process(fnObj, p, d);
255
+ return;
256
+ }
257
+ const source = fn.toString();
258
+ const snippet = source.length > 100
259
+ ? source.substring(0, 100).replace(/\n/g, " ") + "..."
260
+ : source;
261
+ chunks.push(JSON.stringify(`[Function:${fn.name || "anonymous"} | ${snippet}]`));
262
+ return;
263
+ }
264
+ if (t === "string") {
265
+ const s = val;
266
+ const truncated = s.length > opts.truncateStrings
267
+ ? this.safeTruncate(s, opts.truncateStrings) + "...[truncated]"
268
+ : s;
269
+ chunks.push(JSON.stringify(truncated));
270
+ return;
271
+ }
272
+ // --- Objects ---
273
+ const obj = val;
274
+ // Depth guard
275
+ if (d > opts.maxDepth) {
276
+ chunks.push(`"[Max Depth: ${d}]"`);
277
+ return;
278
+ }
279
+ // Cycle detection
280
+ if (seen.has(obj)) {
281
+ const circularPath = seen.get(obj);
282
+ if (opts.reportCircularPath) {
283
+ chunks.push(`"[Circular → ${circularPath}]"`);
284
+ }
285
+ else {
286
+ chunks.push('"[Circular Reference]"');
287
+ }
288
+ return;
289
+ }
290
+ // --- Special value types (no need to mark as seen) ---
291
+ if (val instanceof Date) {
292
+ chunks.push(JSON.stringify(val.toISOString()));
293
+ return;
294
+ }
295
+ if (val instanceof RegExp) {
296
+ chunks.push(JSON.stringify(val.toString()));
297
+ return;
298
+ }
299
+ if (val instanceof Error) {
300
+ seen.set(obj, p);
301
+ const errObj = {
302
+ _type: "[Error]",
303
+ name: val.name,
304
+ message: val.message,
305
+ stack: val.stack ? "[Stack Trace Redacted]" : undefined,
306
+ };
307
+ process(errObj, p, d);
308
+ return;
309
+ }
310
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(val)) {
311
+ if (opts.pureRaw) {
312
+ // Convert buffer to real array for pureRaw inspection
313
+ process(Array.from(val), p, d);
314
+ return;
315
+ }
316
+ const buf = val;
317
+ const preview = buf.length > 32
318
+ ? buf.slice(0, 32).toString("hex") + "..."
319
+ : buf.toString("hex");
320
+ chunks.push(JSON.stringify(`[Buffer:${buf.length} bytes | 0x${preview}]`));
321
+ return;
322
+ }
323
+ if (val instanceof Uint8Array || val instanceof ArrayBuffer) {
324
+ const len = val instanceof ArrayBuffer
325
+ ? val.byteLength
326
+ : val.byteLength;
327
+ chunks.push(`"[BinaryData:${len}bytes]"`);
328
+ return;
329
+ }
330
+ if (val instanceof Map) {
331
+ seen.set(obj, p);
332
+ const mapObj = { _type: "[Map]" };
333
+ let i = 0;
334
+ for (const [k, v] of val) {
335
+ if (i >= opts.maxObjectKeys) {
336
+ mapObj[`...[${val.size - i} more]`] =
337
+ null;
338
+ break;
339
+ }
340
+ mapObj[String(k)] = v;
341
+ i++;
342
+ }
343
+ process(mapObj, p, d);
344
+ return;
345
+ }
346
+ if (val instanceof Set) {
347
+ seen.set(obj, p);
348
+ const arr = Array.from(val);
349
+ process(arr, p, d);
350
+ return;
351
+ }
352
+ if (val instanceof Promise) {
353
+ chunks.push('"[Promise]"');
354
+ return;
355
+ }
356
+ if (val instanceof WeakMap ||
357
+ val instanceof WeakSet ||
358
+ val instanceof WeakRef) {
359
+ chunks.push(`"[${val.constructor.name}]"`);
360
+ return;
361
+ }
362
+ // --- XyPriss / Node.js special objects ---
363
+ const ctorName = obj.constructor?.name;
364
+ if (BLOCKED_CONSTRUCTORS.has(ctorName ?? "")) {
365
+ if (opts.pureRaw) {
366
+ // Bypass block and treat as plain object
367
+ // but we still need to set seen to avoid immediate cycles
368
+ seen.set(obj, p);
369
+ // Fall through to plain object traversal below
370
+ }
371
+ else {
372
+ // Extract useful metadata from common blocked objects
373
+ const meta = { _type: `[Blocked:${ctorName}]` };
374
+ try {
375
+ if (ctorName?.includes("Socket")) {
376
+ meta.remoteAddress = val.remoteAddress;
377
+ meta.remotePort = val.remotePort;
378
+ meta.localPort = val.localPort;
379
+ }
380
+ else if (ctorName === "Server") {
381
+ meta.listening = val.listening;
382
+ }
383
+ }
384
+ catch { }
385
+ process(meta, p, d);
386
+ return;
387
+ }
388
+ }
389
+ if (ctorName === "IncomingMessage" || ctorName === "Request") {
390
+ seen.set(obj, p);
391
+ process({
392
+ _type: "[XyPriss Request]",
393
+ method: val.method,
394
+ url: val.url,
395
+ headers: this.sanitizeHeaders(val.headers),
396
+ query: val.query,
397
+ params: val.params,
398
+ body: val.body ? "[Request Body]" : undefined,
399
+ ip: val.ip,
400
+ }, p, d);
401
+ return;
402
+ }
403
+ if (ctorName === "ServerResponse" || ctorName === "Response") {
404
+ seen.set(obj, p);
405
+ process({
406
+ _type: "[XyPriss Response]",
407
+ statusCode: val.statusCode,
408
+ statusMessage: val.statusMessage,
409
+ headersSent: val.headersSent,
410
+ }, p, d);
411
+ return;
412
+ }
413
+ // Heuristic: looks like a duck-typed XyPriss request
414
+ if (val.method &&
415
+ val.url &&
416
+ val.headers &&
417
+ !Array.isArray(val)) {
418
+ seen.set(obj, p);
419
+ process({
420
+ _type: "[XyPriss Request-like]",
421
+ method: val.method,
422
+ url: val.url,
423
+ headers: this.sanitizeHeaders(val.headers),
424
+ }, p, d);
425
+ return;
426
+ }
427
+ // --- Arrays ---
428
+ if (Array.isArray(val)) {
429
+ seen.set(obj, p);
430
+ const arr = val;
431
+ if (arr.length === 0) {
432
+ chunks.push("[]");
433
+ return;
434
+ }
435
+ const limit = Math.min(arr.length, opts.maxArrayItems);
436
+ const truncatedArray = limit < arr.length;
437
+ chunks.push("[");
438
+ // Push items onto the stack in reverse order so they execute in order
439
+ // We schedule a "close" task last (it runs after all items)
440
+ const closeIdx = chunks.length; // slot reserved below
441
+ chunks.push(""); // placeholder for closing bracket / truncation note
442
+ const itemTasks = [];
443
+ for (let i = 0; i < limit; i++) {
444
+ const idx = i;
445
+ itemTasks.push(() => {
446
+ if (idx > 0)
447
+ chunks.push(",");
448
+ process(arr[idx], `${p}[${idx}]`, d + 1);
449
+ });
450
+ }
451
+ // The close task
452
+ const closeTask = () => {
453
+ if (truncatedArray) {
454
+ chunks.push(",");
455
+ chunks.push(`"...[${arr.length - limit} more items truncated]"`);
456
+ }
457
+ chunks[closeIdx] = ""; // clear placeholder
458
+ chunks.push("]");
459
+ };
460
+ // Push close task first, then items in REVERSE order so Task(0) is on top
461
+ stack.push(closeTask);
462
+ for (let i = itemTasks.length - 1; i >= 0; i--) {
463
+ stack.push(itemTasks[i]);
464
+ }
465
+ return;
466
+ }
467
+ // --- Plain objects ---
468
+ seen.set(obj, p);
469
+ const keys = opts.includeNonEnumerable
470
+ ? Object.getOwnPropertyNames(obj)
471
+ : Object.keys(obj);
472
+ if (keys.length === 0) {
473
+ chunks.push("{}");
474
+ return;
475
+ }
476
+ const limit = Math.min(keys.length, opts.maxObjectKeys);
477
+ const truncatedObj = limit < keys.length;
478
+ chunks.push("{");
479
+ const closeIdx = chunks.length;
480
+ chunks.push(""); // placeholder
481
+ const keyTasks = [];
482
+ for (let i = 0; i < limit; i++) {
483
+ const k = keys[i];
484
+ const idx = i;
485
+ keyTasks.push(() => {
486
+ if (idx > 0)
487
+ chunks.push(",");
488
+ chunks.push(JSON.stringify(k));
489
+ chunks.push(":");
490
+ let v;
491
+ try {
492
+ v = obj[k];
493
+ }
494
+ catch {
495
+ v = "[Property Access Error]";
496
+ }
497
+ process(v, `${p}.${k}`, d + 1);
498
+ });
499
+ }
500
+ const closeTask = () => {
501
+ if (truncatedObj) {
502
+ chunks.push(",");
503
+ chunks.push(`"...[${keys.length - limit} more keys truncated]":null`);
504
+ }
505
+ chunks[closeIdx] = "";
506
+ chunks.push("}");
507
+ };
508
+ stack.push(closeTask);
509
+ for (let i = keyTasks.length - 1; i >= 0; i--) {
510
+ stack.push(keyTasks[i]);
511
+ }
512
+ };
513
+ // Seed the stack with the root
514
+ stack.push(() => process(value, path, depth));
515
+ // Drain the stack — this is the non-recursive loop
516
+ while (stack.length > 0) {
517
+ const task = stack.pop();
518
+ task();
519
+ }
520
+ }
521
+ // -------------------------------------------------------------------------
522
+ // UTILITIES
523
+ // -------------------------------------------------------------------------
524
+ /** Merge user options with defaults, always producing a fully-defined object */
525
+ static mergeOptions(options) {
526
+ return { ...this.DEFAULT_OPTIONS, ...options };
527
+ }
528
+ /**
529
+ * Truncate a string at a safe Unicode boundary (no split surrogates).
530
+ */
531
+ static safeTruncate(s, maxLen) {
532
+ if (s.length <= maxLen)
533
+ return s;
534
+ // Walk back from maxLen until we find a non-low-surrogate boundary
535
+ let i = maxLen;
536
+ while (i > 0 && s.charCodeAt(i) >= 0xdc00 && s.charCodeAt(i) <= 0xdfff) {
537
+ i--;
538
+ }
539
+ return s.substring(0, i);
540
+ }
541
+ /** Redact sensitive HTTP headers */
542
+ static sanitizeHeaders(headers) {
543
+ if (!headers || typeof headers !== "object")
544
+ return headers;
545
+ const sanitized = {};
546
+ for (const [k, v] of Object.entries(headers)) {
547
+ sanitized[k] = SENSITIVE_HEADERS.has(k.toLowerCase()) ? "[REDACTED]" : v;
548
+ }
549
+ return sanitized;
550
+ }
551
+ /** Duck-type detection for XyPriss req/res objects */
552
+ static isXyPrissObject(arg) {
553
+ if (!arg || typeof arg !== "object")
554
+ return false;
555
+ const a = arg;
556
+ const name = a.constructor?.name;
557
+ if (name === "IncomingMessage" ||
558
+ name === "ServerResponse" ||
559
+ name === "Request" ||
560
+ name === "Response")
561
+ return true;
562
+ if (a.method && a.url && a.headers)
563
+ return true;
564
+ if (a.statusCode !== undefined && a.headersSent !== undefined)
565
+ return true;
566
+ return false;
567
+ }
321
568
  }
322
569
  exports.SafeSerializer = SafeSerializer;
323
- SafeSerializer.DEFAULT_OPTIONS = {
324
- maxDepth: 10,
325
- maxLength: 10000,
326
- includeNonEnumerable: false,
327
- truncateStrings: 1000,
328
- fastMode: false,
329
- };
330
570
  //# sourceMappingURL=safe-serializer.js.map