@superblocksteam/telemetry 2.0.173-next.4 → 2.0.174

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 (304) hide show
  1. package/dist/browser/console-logging.js +7 -2
  2. package/dist/browser/console-logging.js.map +1 -1
  3. package/dist/browser/early-console-buffer.js +14 -12
  4. package/dist/browser/early-console-buffer.js.map +1 -1
  5. package/dist/browser/index.js +41 -12
  6. package/dist/browser/index.js.map +1 -1
  7. package/dist/browser/init.d.ts +1 -1
  8. package/dist/browser/init.d.ts.map +1 -1
  9. package/dist/browser/init.js +52 -42
  10. package/dist/browser/init.js.map +1 -1
  11. package/dist/browser/instrumentations.js +15 -11
  12. package/dist/browser/instrumentations.js.map +1 -1
  13. package/dist/browser/logs.d.ts +1 -1
  14. package/dist/browser/logs.d.ts.map +1 -1
  15. package/dist/browser/logs.js +18 -11
  16. package/dist/browser/logs.js.map +1 -1
  17. package/dist/browser/metrics.js +12 -9
  18. package/dist/browser/metrics.js.map +1 -1
  19. package/dist/browser/resilient-exporter.d.ts +1 -1
  20. package/dist/browser/resilient-exporter.d.ts.map +1 -1
  21. package/dist/browser/resilient-exporter.js +12 -8
  22. package/dist/browser/resilient-exporter.js.map +1 -1
  23. package/dist/browser/sanitizer.js +6 -2
  24. package/dist/browser/sanitizer.js.map +1 -1
  25. package/dist/browser/traced-socket.js +18 -14
  26. package/dist/browser/traced-socket.js.map +1 -1
  27. package/dist/common/contracts/tier2-traces.js +44 -33
  28. package/dist/common/contracts/tier2-traces.js.map +1 -1
  29. package/dist/common/deployment-type.js +8 -5
  30. package/dist/common/deployment-type.js.map +1 -1
  31. package/dist/common/guardrails.js +38 -26
  32. package/dist/common/guardrails.js.map +1 -1
  33. package/dist/common/index.js +24 -8
  34. package/dist/common/index.js.map +1 -1
  35. package/dist/common/log-sanitizer.js +55 -37
  36. package/dist/common/log-sanitizer.js.map +1 -1
  37. package/dist/common/policy-evaluator.d.ts +1 -1
  38. package/dist/common/policy-evaluator.d.ts.map +1 -1
  39. package/dist/common/policy-evaluator.js +9 -5
  40. package/dist/common/policy-evaluator.js.map +1 -1
  41. package/dist/common/resource.js +25 -18
  42. package/dist/common/resource.js.map +1 -1
  43. package/dist/common/tier-hints.js +22 -12
  44. package/dist/common/tier-hints.js.map +1 -1
  45. package/dist/common/trace-sanitizer.js +10 -6
  46. package/dist/common/trace-sanitizer.js.map +1 -1
  47. package/dist/common/traced-socket-types.js +2 -1
  48. package/dist/common/traced-socket-types.js.map +1 -1
  49. package/dist/index.js +38 -6
  50. package/dist/index.js.map +1 -1
  51. package/dist/lint/forbidden-attributes.js +24 -19
  52. package/dist/lint/forbidden-attributes.js.map +1 -1
  53. package/dist/lint/index.js +8 -1
  54. package/dist/lint/index.js.map +1 -1
  55. package/dist/llmobs/index.js +9 -2
  56. package/dist/llmobs/index.js.map +1 -1
  57. package/dist/llmobs/tier1-exporter.js +12 -6
  58. package/dist/llmobs/tier1-exporter.js.map +1 -1
  59. package/dist/llmobs/tier2-summarizer.js +5 -1
  60. package/dist/llmobs/tier2-summarizer.js.map +1 -1
  61. package/dist/node/dynamic-attribute-processor.js +7 -2
  62. package/dist/node/dynamic-attribute-processor.js.map +1 -1
  63. package/dist/node/exporters/resilient-exporter.d.ts +1 -1
  64. package/dist/node/exporters/resilient-exporter.d.ts.map +1 -1
  65. package/dist/node/exporters/resilient-exporter.js +12 -8
  66. package/dist/node/exporters/resilient-exporter.js.map +1 -1
  67. package/dist/node/index.js +26 -7
  68. package/dist/node/index.js.map +1 -1
  69. package/dist/node/init.d.ts +3 -3
  70. package/dist/node/init.d.ts.map +1 -1
  71. package/dist/node/init.js +72 -66
  72. package/dist/node/init.js.map +1 -1
  73. package/dist/node/local-obs-mode.js +4 -1
  74. package/dist/node/local-obs-mode.js.map +1 -1
  75. package/dist/node/log-processor.d.ts +1 -1
  76. package/dist/node/log-processor.d.ts.map +1 -1
  77. package/dist/node/log-processor.js +55 -47
  78. package/dist/node/log-processor.js.map +1 -1
  79. package/dist/node/metrics-client.js +5 -1
  80. package/dist/node/metrics-client.js.map +1 -1
  81. package/dist/node/safe-logger.js +12 -8
  82. package/dist/node/safe-logger.js.map +1 -1
  83. package/dist/node/sanitizing-processor.d.ts +2 -2
  84. package/dist/node/sanitizing-processor.d.ts.map +1 -1
  85. package/dist/node/sanitizing-processor.js +10 -6
  86. package/dist/node/sanitizing-processor.js.map +1 -1
  87. package/dist/node/service-tracers.js +10 -7
  88. package/dist/node/service-tracers.js.map +1 -1
  89. package/dist/node/traced-socket.d.ts +3 -3
  90. package/dist/node/traced-socket.d.ts.map +1 -1
  91. package/dist/node/traced-socket.js +52 -48
  92. package/dist/node/traced-socket.js.map +1 -1
  93. package/dist/npm-registry/emitter.js +47 -34
  94. package/dist/npm-registry/emitter.js.map +1 -1
  95. package/dist/npm-registry/index.js +19 -3
  96. package/dist/npm-registry/index.js.map +1 -1
  97. package/dist/npm-registry/sanitize.js +27 -16
  98. package/dist/npm-registry/sanitize.js.map +1 -1
  99. package/dist/npm-registry/spans.js +12 -7
  100. package/dist/npm-registry/spans.js.map +1 -1
  101. package/dist/testing/in-memory-exporter.d.ts +1 -1
  102. package/dist/testing/in-memory-exporter.d.ts.map +1 -1
  103. package/dist/testing/in-memory-exporter.js +12 -6
  104. package/dist/testing/in-memory-exporter.js.map +1 -1
  105. package/dist/testing/index.js +15 -3
  106. package/dist/testing/index.js.map +1 -1
  107. package/dist/testing/metrics-capture.js +15 -12
  108. package/dist/testing/metrics-capture.js.map +1 -1
  109. package/dist/testing/test-init.js +31 -24
  110. package/dist/testing/test-init.js.map +1 -1
  111. package/dist/types/index.js +17 -1
  112. package/dist/types/index.js.map +1 -1
  113. package/dist/types/policy.js +24 -21
  114. package/dist/types/policy.js.map +1 -1
  115. package/dist-esm/browser/console-logging.d.ts +18 -0
  116. package/dist-esm/browser/console-logging.d.ts.map +1 -0
  117. package/dist-esm/browser/console-logging.js +27 -0
  118. package/dist-esm/browser/console-logging.js.map +1 -0
  119. package/dist-esm/browser/early-console-buffer.d.ts +49 -0
  120. package/dist-esm/browser/early-console-buffer.d.ts.map +1 -0
  121. package/dist-esm/browser/early-console-buffer.js +181 -0
  122. package/dist-esm/browser/early-console-buffer.js.map +1 -0
  123. package/dist-esm/browser/index.d.ts +18 -0
  124. package/dist-esm/browser/index.d.ts.map +1 -0
  125. package/dist-esm/browser/index.js +23 -0
  126. package/dist-esm/browser/index.js.map +1 -0
  127. package/dist-esm/browser/init.d.ts +100 -0
  128. package/dist-esm/browser/init.d.ts.map +1 -0
  129. package/dist-esm/browser/init.js +317 -0
  130. package/dist-esm/browser/init.js.map +1 -0
  131. package/dist-esm/browser/instrumentations.d.ts +67 -0
  132. package/dist-esm/browser/instrumentations.d.ts.map +1 -0
  133. package/dist-esm/browser/instrumentations.js +79 -0
  134. package/dist-esm/browser/instrumentations.js.map +1 -0
  135. package/dist-esm/browser/logs.d.ts +57 -0
  136. package/dist-esm/browser/logs.d.ts.map +1 -0
  137. package/dist-esm/browser/logs.js +80 -0
  138. package/dist-esm/browser/logs.js.map +1 -0
  139. package/dist-esm/browser/metrics.d.ts +29 -0
  140. package/dist-esm/browser/metrics.d.ts.map +1 -0
  141. package/dist-esm/browser/metrics.js +46 -0
  142. package/dist-esm/browser/metrics.js.map +1 -0
  143. package/dist-esm/browser/resilient-exporter.d.ts +43 -0
  144. package/dist-esm/browser/resilient-exporter.d.ts.map +1 -0
  145. package/dist-esm/browser/resilient-exporter.js +84 -0
  146. package/dist-esm/browser/resilient-exporter.js.map +1 -0
  147. package/dist-esm/browser/sanitizer.d.ts +35 -0
  148. package/dist-esm/browser/sanitizer.d.ts.map +1 -0
  149. package/dist-esm/browser/sanitizer.js +207 -0
  150. package/dist-esm/browser/sanitizer.js.map +1 -0
  151. package/dist-esm/browser/traced-socket.d.ts +102 -0
  152. package/dist-esm/browser/traced-socket.d.ts.map +1 -0
  153. package/dist-esm/browser/traced-socket.js +199 -0
  154. package/dist-esm/browser/traced-socket.js.map +1 -0
  155. package/dist-esm/common/contracts/tier2-traces.d.ts +116 -0
  156. package/dist-esm/common/contracts/tier2-traces.d.ts.map +1 -0
  157. package/dist-esm/common/contracts/tier2-traces.js +725 -0
  158. package/dist-esm/common/contracts/tier2-traces.js.map +1 -0
  159. package/dist-esm/common/deployment-type.d.ts +18 -0
  160. package/dist-esm/common/deployment-type.d.ts.map +1 -0
  161. package/dist-esm/common/deployment-type.js +27 -0
  162. package/dist-esm/common/deployment-type.js.map +1 -0
  163. package/dist-esm/common/guardrails.d.ts +132 -0
  164. package/dist-esm/common/guardrails.d.ts.map +1 -0
  165. package/dist-esm/common/guardrails.js +230 -0
  166. package/dist-esm/common/guardrails.js.map +1 -0
  167. package/dist-esm/common/index.d.ts +17 -0
  168. package/dist-esm/common/index.d.ts.map +1 -0
  169. package/dist-esm/common/index.js +17 -0
  170. package/dist-esm/common/index.js.map +1 -0
  171. package/dist-esm/common/log-sanitizer.d.ts +163 -0
  172. package/dist-esm/common/log-sanitizer.d.ts.map +1 -0
  173. package/dist-esm/common/log-sanitizer.js +745 -0
  174. package/dist-esm/common/log-sanitizer.js.map +1 -0
  175. package/dist-esm/common/policy-evaluator.d.ts +103 -0
  176. package/dist-esm/common/policy-evaluator.d.ts.map +1 -0
  177. package/dist-esm/common/policy-evaluator.js +196 -0
  178. package/dist-esm/common/policy-evaluator.js.map +1 -0
  179. package/dist-esm/common/resource.d.ts +79 -0
  180. package/dist-esm/common/resource.d.ts.map +1 -0
  181. package/dist-esm/common/resource.js +121 -0
  182. package/dist-esm/common/resource.js.map +1 -0
  183. package/dist-esm/common/tier-hints.d.ts +182 -0
  184. package/dist-esm/common/tier-hints.d.ts.map +1 -0
  185. package/dist-esm/common/tier-hints.js +199 -0
  186. package/dist-esm/common/tier-hints.js.map +1 -0
  187. package/dist-esm/common/trace-sanitizer.d.ts +82 -0
  188. package/dist-esm/common/trace-sanitizer.d.ts.map +1 -0
  189. package/dist-esm/common/trace-sanitizer.js +230 -0
  190. package/dist-esm/common/trace-sanitizer.js.map +1 -0
  191. package/dist-esm/common/traced-socket-types.d.ts +43 -0
  192. package/dist-esm/common/traced-socket-types.d.ts.map +1 -0
  193. package/dist-esm/common/traced-socket-types.js +16 -0
  194. package/dist-esm/common/traced-socket-types.js.map +1 -0
  195. package/dist-esm/index.d.ts +45 -0
  196. package/dist-esm/index.d.ts.map +1 -0
  197. package/dist-esm/index.js +56 -0
  198. package/dist-esm/index.js.map +1 -0
  199. package/dist-esm/lint/forbidden-attributes.d.ts +149 -0
  200. package/dist-esm/lint/forbidden-attributes.d.ts.map +1 -0
  201. package/dist-esm/lint/forbidden-attributes.js +210 -0
  202. package/dist-esm/lint/forbidden-attributes.js.map +1 -0
  203. package/dist-esm/lint/index.d.ts +9 -0
  204. package/dist-esm/lint/index.d.ts.map +1 -0
  205. package/dist-esm/lint/index.js +9 -0
  206. package/dist-esm/lint/index.js.map +1 -0
  207. package/dist-esm/llmobs/index.d.ts +22 -0
  208. package/dist-esm/llmobs/index.d.ts.map +1 -0
  209. package/dist-esm/llmobs/index.js +22 -0
  210. package/dist-esm/llmobs/index.js.map +1 -0
  211. package/dist-esm/llmobs/tier1-exporter.d.ts +146 -0
  212. package/dist-esm/llmobs/tier1-exporter.d.ts.map +1 -0
  213. package/dist-esm/llmobs/tier1-exporter.js +193 -0
  214. package/dist-esm/llmobs/tier1-exporter.js.map +1 -0
  215. package/dist-esm/llmobs/tier2-summarizer.d.ts +268 -0
  216. package/dist-esm/llmobs/tier2-summarizer.d.ts.map +1 -0
  217. package/dist-esm/llmobs/tier2-summarizer.js +652 -0
  218. package/dist-esm/llmobs/tier2-summarizer.js.map +1 -0
  219. package/dist-esm/node/dynamic-attribute-processor.d.ts +43 -0
  220. package/dist-esm/node/dynamic-attribute-processor.d.ts.map +1 -0
  221. package/dist-esm/node/dynamic-attribute-processor.js +74 -0
  222. package/dist-esm/node/dynamic-attribute-processor.js.map +1 -0
  223. package/dist-esm/node/exporters/resilient-exporter.d.ts +91 -0
  224. package/dist-esm/node/exporters/resilient-exporter.d.ts.map +1 -0
  225. package/dist-esm/node/exporters/resilient-exporter.js +132 -0
  226. package/dist-esm/node/exporters/resilient-exporter.js.map +1 -0
  227. package/dist-esm/node/index.d.ts +13 -0
  228. package/dist-esm/node/index.d.ts.map +1 -0
  229. package/dist-esm/node/index.js +13 -0
  230. package/dist-esm/node/index.js.map +1 -0
  231. package/dist-esm/node/init.d.ts +97 -0
  232. package/dist-esm/node/init.d.ts.map +1 -0
  233. package/dist-esm/node/init.js +359 -0
  234. package/dist-esm/node/init.js.map +1 -0
  235. package/dist-esm/node/local-obs-mode.d.ts +17 -0
  236. package/dist-esm/node/local-obs-mode.d.ts.map +1 -0
  237. package/dist-esm/node/local-obs-mode.js +19 -0
  238. package/dist-esm/node/local-obs-mode.js.map +1 -0
  239. package/dist-esm/node/log-processor.d.ts +132 -0
  240. package/dist-esm/node/log-processor.d.ts.map +1 -0
  241. package/dist-esm/node/log-processor.js +389 -0
  242. package/dist-esm/node/log-processor.js.map +1 -0
  243. package/dist-esm/node/metrics-client.d.ts +88 -0
  244. package/dist-esm/node/metrics-client.d.ts.map +1 -0
  245. package/dist-esm/node/metrics-client.js +205 -0
  246. package/dist-esm/node/metrics-client.js.map +1 -0
  247. package/dist-esm/node/safe-logger.d.ts +55 -0
  248. package/dist-esm/node/safe-logger.d.ts.map +1 -0
  249. package/dist-esm/node/safe-logger.js +154 -0
  250. package/dist-esm/node/safe-logger.js.map +1 -0
  251. package/dist-esm/node/sanitizing-processor.d.ts +67 -0
  252. package/dist-esm/node/sanitizing-processor.d.ts.map +1 -0
  253. package/dist-esm/node/sanitizing-processor.js +163 -0
  254. package/dist-esm/node/sanitizing-processor.js.map +1 -0
  255. package/dist-esm/node/service-tracers.d.ts +52 -0
  256. package/dist-esm/node/service-tracers.d.ts.map +1 -0
  257. package/dist-esm/node/service-tracers.js +78 -0
  258. package/dist-esm/node/service-tracers.js.map +1 -0
  259. package/dist-esm/node/traced-socket.d.ts +120 -0
  260. package/dist-esm/node/traced-socket.d.ts.map +1 -0
  261. package/dist-esm/node/traced-socket.js +334 -0
  262. package/dist-esm/node/traced-socket.js.map +1 -0
  263. package/dist-esm/npm-registry/emitter.d.ts +330 -0
  264. package/dist-esm/npm-registry/emitter.d.ts.map +1 -0
  265. package/dist-esm/npm-registry/emitter.js +546 -0
  266. package/dist-esm/npm-registry/emitter.js.map +1 -0
  267. package/dist-esm/npm-registry/index.d.ts +12 -0
  268. package/dist-esm/npm-registry/index.d.ts.map +1 -0
  269. package/dist-esm/npm-registry/index.js +12 -0
  270. package/dist-esm/npm-registry/index.js.map +1 -0
  271. package/dist-esm/npm-registry/sanitize.d.ts +174 -0
  272. package/dist-esm/npm-registry/sanitize.d.ts.map +1 -0
  273. package/dist-esm/npm-registry/sanitize.js +331 -0
  274. package/dist-esm/npm-registry/sanitize.js.map +1 -0
  275. package/dist-esm/npm-registry/spans.d.ts +61 -0
  276. package/dist-esm/npm-registry/spans.d.ts.map +1 -0
  277. package/dist-esm/npm-registry/spans.js +86 -0
  278. package/dist-esm/npm-registry/spans.js.map +1 -0
  279. package/dist-esm/package.json +1 -0
  280. package/dist-esm/testing/in-memory-exporter.d.ts +179 -0
  281. package/dist-esm/testing/in-memory-exporter.d.ts.map +1 -0
  282. package/dist-esm/testing/in-memory-exporter.js +250 -0
  283. package/dist-esm/testing/in-memory-exporter.js.map +1 -0
  284. package/dist-esm/testing/index.d.ts +9 -0
  285. package/dist-esm/testing/index.d.ts.map +1 -0
  286. package/dist-esm/testing/index.js +9 -0
  287. package/dist-esm/testing/index.js.map +1 -0
  288. package/dist-esm/testing/metrics-capture.d.ts +29 -0
  289. package/dist-esm/testing/metrics-capture.d.ts.map +1 -0
  290. package/dist-esm/testing/metrics-capture.js +71 -0
  291. package/dist-esm/testing/metrics-capture.js.map +1 -0
  292. package/dist-esm/testing/test-init.d.ts +80 -0
  293. package/dist-esm/testing/test-init.d.ts.map +1 -0
  294. package/dist-esm/testing/test-init.js +137 -0
  295. package/dist-esm/testing/test-init.js.map +1 -0
  296. package/dist-esm/types/index.d.ts +84 -0
  297. package/dist-esm/types/index.d.ts.map +1 -0
  298. package/dist-esm/types/index.js +7 -0
  299. package/dist-esm/types/index.js.map +1 -0
  300. package/dist-esm/types/policy.d.ts +92 -0
  301. package/dist-esm/types/policy.d.ts.map +1 -0
  302. package/dist-esm/types/policy.js +122 -0
  303. package/dist-esm/types/policy.js.map +1 -0
  304. package/package.json +17 -11
@@ -0,0 +1,745 @@
1
+ /**
2
+ * Log Sanitizer for Application-Level Secret Protection
3
+ *
4
+ * Sanitizes secrets from log messages while preserving debugging information.
5
+ * This operates at the APPLICATION layer and removes values that should NEVER
6
+ * appear in any log, anywhere.
7
+ *
8
+ * ARCHITECTURE:
9
+ * - Application layer (this file): secret sanitization only (unconditional, all deployments)
10
+ * - Policy layer (DEFAULT_SENSITIVE_PATTERNS / export path): PII redaction (deployment-specific;
11
+ * cloud-prem redacts emails, cloud does not)
12
+ * - Pipeline layer (OTel Collector): Tier-based filtering for export
13
+ *
14
+ * Stack traces and file paths are preserved for debugging. The pipeline layer
15
+ * handles additional tier-based redaction for exported logs.
16
+ *
17
+ * @see https://github.com/superblocksteam/engineering/blob/main/projects/o11y-refactor/epics/epic-c4-logging-strategy.md
18
+ */
19
+ /**
20
+ * Patterns that indicate tokens, keys, and secrets in log messages.
21
+ * These use capture groups to preserve prefixes while redacting values.
22
+ * Exported as DEFAULT_SECRET_PATTERNS for use in LoggingPolicyConfig (e.g. cloud-prem).
23
+ */
24
+ export const DEFAULT_SECRET_PATTERNS = [
25
+ // Tokens and keys with prefixes
26
+ {
27
+ pattern: /(\bbearer\s+)[a-zA-Z0-9\-._~+/]+=*/gi,
28
+ replacement: "$1[REDACTED]",
29
+ },
30
+ { pattern: /(\bbasic\s+)\S+/gi, replacement: "$1[REDACTED]" },
31
+ { pattern: /(\bjwt\s+)[a-zA-Z0-9\-._~+/]+=*/gi, replacement: "$1[REDACTED]" },
32
+ {
33
+ pattern: /(\btoken[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
34
+ replacement: "$1[REDACTED]",
35
+ },
36
+ {
37
+ pattern: /(\bapi[_\s]?key[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
38
+ replacement: "$1[REDACTED]",
39
+ },
40
+ {
41
+ pattern: /(\baccess[_\s]?token[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
42
+ replacement: "$1[REDACTED]",
43
+ },
44
+ {
45
+ pattern: /(\brefresh[_\s]?token[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
46
+ replacement: "$1[REDACTED]",
47
+ },
48
+ // JWT pattern (base64.base64.base64) - standalone tokens
49
+ {
50
+ pattern: /\b[A-Za-z0-9-_]{20,}\.[A-Za-z0-9-_]{20,}\.[A-Za-z0-9-_]{20,}\b/g,
51
+ replacement: "[JWT_REDACTED]",
52
+ },
53
+ // OpenAI API keys
54
+ { pattern: /\bsk-[A-Za-z0-9]{48}\b/g, replacement: "[OPENAI_KEY_REDACTED]" },
55
+ // Anthropic API keys
56
+ {
57
+ pattern: /\bsk-ant-[A-Za-z0-9-]{32,}\b/g,
58
+ replacement: "[ANTHROPIC_KEY_REDACTED]",
59
+ },
60
+ // AWS access key IDs
61
+ { pattern: /\bAKIA[A-Z0-9]{16}\b/g, replacement: "[AWS_KEY_REDACTED]" },
62
+ // GitHub tokens
63
+ {
64
+ pattern: /\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b/g,
65
+ replacement: "[GITHUB_TOKEN_REDACTED]",
66
+ },
67
+ // Generic API key patterns
68
+ {
69
+ pattern: /\b(sk|pk)-[a-zA-Z0-9]{32,}\b/g,
70
+ replacement: "[API_KEY_REDACTED]",
71
+ },
72
+ // Connection strings with credentials
73
+ {
74
+ pattern: /\b(postgres|mysql|mongodb|redis):\/\/[^\s]+:[^\s]+@[^\s]+/gi,
75
+ replacement: "[CONNECTION_STRING_REDACTED]",
76
+ },
77
+ // PEM blocks
78
+ {
79
+ pattern: /-----BEGIN\s+(RSA\s+)?(PRIVATE|PUBLIC)\s+KEY-----[\s\S]*?-----END\s+(RSA\s+)?(PRIVATE|PUBLIC)\s+KEY-----/g,
80
+ replacement: "[PEM_KEY_REDACTED]",
81
+ },
82
+ ];
83
+ const DEFAULT_PII_PATTERNS = [
84
+ // Match email addresses; requires an alphabetic TLD to avoid false positives on @mentions and
85
+ // npm/pnpm version strings (e.g. package@1.2.3 has a numeric suffix, not a TLD)
86
+ {
87
+ pattern: /[\w.+-]+@(?:[\w-]+\.)+[a-zA-Z]{2,}/g,
88
+ replacement: "[EMAIL REDACTED]",
89
+ },
90
+ ];
91
+ const PROMPT_PAYLOAD_LOG_PATTERNS = [
92
+ {
93
+ pattern: /^(\[llm-generating\] (?:System prompt|User message):)\s*[\s\S]*$/g,
94
+ replacement: "$1 [REDACTED]",
95
+ },
96
+ ];
97
+ /**
98
+ * Fields that contain secrets and should be stripped from log objects.
99
+ * Only includes actual secret field names - NOT debugging info.
100
+ *
101
+ * Pipeline-layer concerns (AI content, stack traces, PII) are NOT included here.
102
+ * Those are handled by the OTel Collector for tier-based export filtering.
103
+ */
104
+ export const SECRET_FIELDS = new Set([
105
+ // Auth/secrets
106
+ "password",
107
+ "passwd",
108
+ "secret",
109
+ "private_key",
110
+ "privatekey",
111
+ "credentials",
112
+ "token",
113
+ "accesstoken",
114
+ "access_token",
115
+ "refreshtoken",
116
+ "refresh_token",
117
+ "jwt",
118
+ "bearer",
119
+ "apikey",
120
+ "api_key",
121
+ "auth_token",
122
+ "authorization",
123
+ "cookie",
124
+ "x-api-key",
125
+ ]);
126
+ /**
127
+ * Patterns for compound field names that contain secrets.
128
+ * Uses word boundary matching to avoid false positives.
129
+ * Exported for use in LoggingPolicyConfig (e.g. cloud-prem).
130
+ */
131
+ export const SECRET_FIELD_PATTERNS = [
132
+ // Password patterns
133
+ /(?:^|[._-])password(?:[._-]|$)/i,
134
+ /(?:^|[._-])passwd(?:[._-]|$)/i,
135
+ // Secret patterns
136
+ /(?:^|[._-])secret(?:[._-]|$)/i,
137
+ /(?:^|[._-])private[._-]?key(?:[._-]|$)/i,
138
+ // Token patterns
139
+ /(?:^|[._-])token(?:[._-]|$)/i,
140
+ /(?:^|[._-])jwt(?:[._-]|$)/i,
141
+ /(?:^|[._-])bearer(?:[._-]|$)/i,
142
+ // Credential patterns
143
+ /(?:^|[._-])credentials?(?:[._-]|$)/i,
144
+ // API key patterns
145
+ /api[._-]?key/i,
146
+ /x[._-]api[._-]key/i,
147
+ // Auth token patterns
148
+ /auth[._-]token/i,
149
+ ];
150
+ /**
151
+ * Check if a field name contains secrets using word boundary matching.
152
+ */
153
+ export function isSecretField(fieldName) {
154
+ return isSecretFieldFromPolicy(fieldName, SECRET_FIELDS, SECRET_FIELD_PATTERNS);
155
+ }
156
+ export function isSecretFieldFromPolicy(fieldName, secretFieldNames, secretFieldPatterns) {
157
+ if (secretFieldNames.size === 0 && secretFieldPatterns.length === 0) {
158
+ return false;
159
+ }
160
+ const lowerKey = fieldName.toLowerCase();
161
+ if (secretFieldNames.has(lowerKey)) {
162
+ return true;
163
+ }
164
+ return secretFieldPatterns.some((pattern) => pattern.test(lowerKey));
165
+ }
166
+ /**
167
+ * Placeholder string used when redacting forbidden or secret fields in logs.
168
+ * Use this constant so all packages (server, telemetry) share one value.
169
+ */
170
+ export const REDACTED_PLACEHOLDER = "[REDACTED]";
171
+ /**
172
+ * Field names that must never be included in exported logs (stack, prompt, code, etc.).
173
+ * Used as the base for both cloud and cloud-prem.
174
+ */
175
+ export const BASE_EXPORT_FORBIDDEN_FIELDS = new Set([
176
+ "code",
177
+ "error.stack",
178
+ "exception.stacktrace",
179
+ "filecontent",
180
+ "filepath",
181
+ "prompt",
182
+ "stack",
183
+ ]);
184
+ /** Email-related keys forbidden in cloud-prem export only (allowed in cloud). */
185
+ const EXPORT_FORBIDDEN_EMAIL_KEYS = new Set([
186
+ "user.email",
187
+ "user-email", // server req.logTags key (OBS_TAG_USER_EMAIL) for child logger bindings
188
+ ]);
189
+ /** Export-forbidden fields for cloud (base only; email keys allowed). */
190
+ export const EXPORT_FORBIDDEN_FIELDS_CLOUD = new Set(BASE_EXPORT_FORBIDDEN_FIELDS);
191
+ /** Export-forbidden fields for cloud-prem (base + email keys). */
192
+ export const EXPORT_FORBIDDEN_FIELDS_CLOUD_PREM = new Set([
193
+ ...BASE_EXPORT_FORBIDDEN_FIELDS,
194
+ ...EXPORT_FORBIDDEN_EMAIL_KEYS,
195
+ ]);
196
+ /**
197
+ * Attribute keys that conventionally hold stack traces (epic C4). Value-based
198
+ * sanitization (containsStackTrace) redacts stack content in any key.
199
+ */
200
+ const STACK_ATTRIBUTE_KEYS = new Set([
201
+ "error.stack",
202
+ "exception.stacktrace",
203
+ "stack",
204
+ ]);
205
+ export function isStackAttributeKey(key) {
206
+ return STACK_ATTRIBUTE_KEYS.has(key.toLowerCase());
207
+ }
208
+ /**
209
+ * Check if a string contains a stack trace (for export redaction) in linear time.
210
+ */
211
+ const GO_STACK_FRAME = /goroutine \d+ \[[^[\]\r\n\u2028\u2029]+\]:/;
212
+ const PYTHON_STACK_FRAME = /File "[^"\r\n\u2028\u2029]+", line \d+/;
213
+ function findLineEnd(text, start) {
214
+ for (let index = start; index < text.length; index += 1) {
215
+ const char = text.charCodeAt(index);
216
+ if (char === 10 || char === 13 || char === 0x2028 || char === 0x2029) {
217
+ return index;
218
+ }
219
+ }
220
+ return text.length;
221
+ }
222
+ function hasLocationSuffix(text, colon) {
223
+ let index = colon + 1;
224
+ const lineStart = index;
225
+ while (text.charCodeAt(index) >= 48 && text.charCodeAt(index) <= 57) {
226
+ index += 1;
227
+ }
228
+ if (index === lineStart || text[index] !== ":")
229
+ return false;
230
+ index += 1;
231
+ const columnStart = index;
232
+ while (text.charCodeAt(index) >= 48 && text.charCodeAt(index) <= 57) {
233
+ index += 1;
234
+ }
235
+ return index > columnStart && text[index] === ")";
236
+ }
237
+ function detectStackTrace(text) {
238
+ let frameStart = -1;
239
+ let pathStart = -1;
240
+ let pathParenthesisDepth = 0;
241
+ for (let index = 0; index < text.length; index += 1) {
242
+ const char = text.charCodeAt(index);
243
+ if (char === 10 || char === 13 || char === 0x2028 || char === 0x2029) {
244
+ frameStart = -1;
245
+ pathStart = -1;
246
+ pathParenthesisDepth = 0;
247
+ }
248
+ else if (frameStart === -1 &&
249
+ char === 97 &&
250
+ text.charCodeAt(index + 1) === 116 &&
251
+ text.charCodeAt(index + 2) === 32) {
252
+ frameStart = index;
253
+ }
254
+ else if (frameStart !== -1 && char === 40 && index >= frameStart + 4) {
255
+ if (pathStart === -1)
256
+ pathStart = index;
257
+ pathParenthesisDepth += 1;
258
+ }
259
+ else if (pathStart !== -1 &&
260
+ char === 58 &&
261
+ index > pathStart + 1 &&
262
+ hasLocationSuffix(text, index)) {
263
+ return true;
264
+ }
265
+ else if (pathStart !== -1 && char === 41) {
266
+ pathParenthesisDepth -= 1;
267
+ if (pathParenthesisDepth === 0)
268
+ pathStart = -1;
269
+ }
270
+ }
271
+ return GO_STACK_FRAME.test(text) || PYTHON_STACK_FRAME.test(text);
272
+ }
273
+ let reportedStackTraceDetectionFailure = false;
274
+ function handleStackTraceDetectionFailure(error) {
275
+ if (!(error instanceof RangeError))
276
+ throw error;
277
+ if (!reportedStackTraceDetectionFailure && typeof process !== "undefined") {
278
+ reportedStackTraceDetectionFailure = true;
279
+ process.stderr.write("Stack trace detection failed; redacting the complete value\n");
280
+ }
281
+ }
282
+ export function containsStackTrace(text) {
283
+ if (!text || typeof text !== "string")
284
+ return false;
285
+ try {
286
+ return detectStackTrace(text);
287
+ }
288
+ catch (error) {
289
+ handleStackTraceDetectionFailure(error);
290
+ return true;
291
+ }
292
+ }
293
+ /** Placeholder for stack trace redaction in exported logs. Exported for use in log-processor. */
294
+ export const STACK_TRACE_REDACTED_PLACEHOLDER = "[STACK TRACE REDACTED - Tier 1 only]";
295
+ /**
296
+ * Apply redact patterns to a string with a single replacement (resets lastIndex on each pattern).
297
+ */
298
+ export function applyRedactPatterns(str, patterns, replacement = REDACTED_PLACEHOLDER) {
299
+ if (!str || typeof str !== "string")
300
+ return str;
301
+ let out = str;
302
+ for (const pattern of patterns) {
303
+ pattern.lastIndex = 0;
304
+ out = out.replace(pattern, replacement);
305
+ }
306
+ return out;
307
+ }
308
+ /**
309
+ * File extensions whose paths are redacted on the export path. Covers front-end
310
+ * source/build artifacts so customer file paths in bundler output are redacted.
311
+ * Declaration order here does NOT matter — the alternation is sorted
312
+ * longest-first at construction (below).
313
+ */
314
+ const PATH_REDACTION_EXTENSIONS = [
315
+ "tsx",
316
+ "jsx",
317
+ "json",
318
+ "mjs",
319
+ "cjs",
320
+ "html",
321
+ "css",
322
+ "scss",
323
+ "ts",
324
+ "js",
325
+ "go",
326
+ "py",
327
+ "java",
328
+ "sql",
329
+ ];
330
+ // Sort longest-first so overlapping suffixes always match fully (e.g. `.tsx`
331
+ // before `.ts`, `.json` before `.js`) instead of leaving a dangling fragment
332
+ // like `[PATH REDACTED]x`. A prefix is always strictly shorter than the string
333
+ // it prefixes, so length-descending order makes the invariant self-enforcing —
334
+ // adding a new extension can never break it regardless of where it's inserted.
335
+ const PATH_REDACTION_ALTERNATION = [...PATH_REDACTION_EXTENSIONS]
336
+ .sort((a, b) => b.length - a.length)
337
+ .join("|");
338
+ /** Sensitive patterns for cloud: path only (emails are not redacted in cloud). */
339
+ export const SENSITIVE_PATTERNS_CLOUD = [
340
+ {
341
+ // Trailing \b stops a partial match like `.jsonl`/`.json5` from matching
342
+ // `.json` and leaving a dangling fragment (`[PATH REDACTED]l`); such paths
343
+ // are left untouched instead.
344
+ pattern: new RegExp(`/[\\w\\-./]+\\.(${PATH_REDACTION_ALTERNATION})\\b`, "gi"),
345
+ replacement: "[PATH REDACTED]",
346
+ },
347
+ ];
348
+ /** Default sensitive patterns (path, email) for cloud-prem and when export is strict. */
349
+ export const DEFAULT_SENSITIVE_PATTERNS = [
350
+ ...SENSITIVE_PATTERNS_CLOUD,
351
+ ...DEFAULT_PII_PATTERNS,
352
+ ];
353
+ /**
354
+ * Regex patterns for cloud-prem log export: matched content is replaced with [REDACTED].
355
+ * Used as redactPatterns in LoggingPolicyConfig when deploymentType is CLOUD_PREM.
356
+ */
357
+ export const CLOUD_PREM_REDACT_PATTERNS = [
358
+ // JWT pattern (base64.base64.base64) - standalone tokens
359
+ /\b[A-Za-z0-9-_]{20,}\.[A-Za-z0-9-_]{20,}\.[A-Za-z0-9-_]{20,}\b/g,
360
+ // Bearer tokens
361
+ /\bbearer\s+[a-zA-Z0-9\-._~+/]+=*/gi,
362
+ // API keys
363
+ /\bapi[_\s]?key[:\s=]+[a-zA-Z0-9\-._~+/]{16,}=*/gi,
364
+ ];
365
+ /**
366
+ * Apply sensitive patterns (pattern + replacement) to text.
367
+ * Used with policy.sensitivePatterns whenever redacting values for export.
368
+ */
369
+ export function redactSensitivePatterns(text, patterns) {
370
+ if (!text || typeof text !== "string")
371
+ return text;
372
+ let out = text;
373
+ for (const { pattern, replacement } of patterns) {
374
+ pattern.lastIndex = 0;
375
+ out = out.replace(pattern, replacement);
376
+ }
377
+ return out;
378
+ }
379
+ /**
380
+ * Redact stack trace for export: keep first line (error message) after sanitizing
381
+ * it with sensitive patterns, replace the rest with a placeholder.
382
+ * Use when exporting logs so stack traces never leave the environment.
383
+ *
384
+ * @param messageSecretPatterns - When non-empty, applied to first line before sensitivePatterns (policy-driven). When empty, uses sanitizeLogMessage for first line.
385
+ */
386
+ export function redactStackTraceForExport(text, sensitivePatterns = [], messageSecretPatterns = []) {
387
+ if (!text || typeof text !== "string")
388
+ return text;
389
+ try {
390
+ if (!detectStackTrace(text))
391
+ return text;
392
+ }
393
+ catch (error) {
394
+ handleStackTraceDetectionFailure(error);
395
+ // Detection did not complete, so no part of the value is known to be safe.
396
+ return STACK_TRACE_REDACTED_PLACEHOLDER;
397
+ }
398
+ const firstLineEnd = findLineEnd(text, 0);
399
+ if (firstLineEnd === text.length)
400
+ return STACK_TRACE_REDACTED_PLACEHOLDER;
401
+ const firstLineText = text.slice(0, firstLineEnd);
402
+ const firstLineRaw = messageSecretPatterns.length > 0
403
+ ? redactSensitivePatterns(firstLineText, messageSecretPatterns)
404
+ : sanitizeLogMessage(firstLineText);
405
+ const firstLine = redactSensitivePatterns(firstLineRaw, sensitivePatterns);
406
+ return firstLine + "\n" + STACK_TRACE_REDACTED_PLACEHOLDER;
407
+ }
408
+ /**
409
+ * Sanitize a log message string for export: sensitive patterns, then policy redact patterns, then
410
+ * stack trace redaction. Use for the message argument in log calls (e.g. safe logger stdout path).
411
+ */
412
+ export function sanitizeLogMessageForExport(message, redactPatterns, sensitivePatterns) {
413
+ if (!message || typeof message !== "string")
414
+ return message;
415
+ let out = redactSensitivePatterns(sanitizeLogMessage(message), sensitivePatterns);
416
+ out = applyRedactPatterns(out, redactPatterns);
417
+ return redactStackTraceForExport(out, sensitivePatterns);
418
+ }
419
+ function sanitizeStringValueForExport(value, key, redactPatterns, sensitivePatterns) {
420
+ let out = redactSensitivePatterns(sanitizeLogMessage(value), sensitivePatterns);
421
+ out = applyRedactPatterns(out, redactPatterns);
422
+ const redacted = redactStackTraceForExport(out, sensitivePatterns);
423
+ return isStackAttributeKey(key) && redacted === out
424
+ ? STACK_TRACE_REDACTED_PLACEHOLDER
425
+ : redacted;
426
+ }
427
+ /**
428
+ * @deprecated Use SECRET_FIELDS instead. This export is kept for backward
429
+ * compatibility but now only contains secret fields, not Tier 1 content fields.
430
+ */
431
+ export const TIER1_FORBIDDEN_LOG_FIELDS = SECRET_FIELDS;
432
+ /**
433
+ * Sanitizes secrets from a log message string.
434
+ * Preserves debugging info (emails, file paths, stack traces, etc.) for troubleshooting.
435
+ * Email redaction is deployment-specific and handled via DEFAULT_SENSITIVE_PATTERNS
436
+ * on the export path (cloud-prem only).
437
+ *
438
+ * @param message - The log message to sanitize
439
+ * @returns Message with secrets redacted
440
+ */
441
+ export function sanitizeLogMessage(message) {
442
+ if (!message || typeof message !== "string") {
443
+ return message;
444
+ }
445
+ let sanitized = message;
446
+ for (const { pattern, replacement } of [
447
+ ...PROMPT_PAYLOAD_LOG_PATTERNS,
448
+ ...DEFAULT_SECRET_PATTERNS,
449
+ ]) {
450
+ pattern.lastIndex = 0; // Reset regex state for global patterns
451
+ sanitized = sanitized.replace(pattern, replacement);
452
+ }
453
+ return sanitized;
454
+ }
455
+ /**
456
+ * Sanitizes secrets within a stack trace while preserving the full trace.
457
+ * Stack traces are essential for debugging - only redact secrets within them.
458
+ *
459
+ * @param stack - The stack trace string
460
+ * @returns Stack trace with secrets redacted but structure preserved
461
+ */
462
+ export function redactStackTrace(stack) {
463
+ if (!stack || typeof stack !== "string") {
464
+ return stack;
465
+ }
466
+ // Preserve full stack trace, only sanitize secrets within
467
+ return sanitizeLogMessage(stack);
468
+ }
469
+ /**
470
+ * Recursively sanitizes secrets and high-risk PII from an object.
471
+ * Strips secret fields entirely, sanitizes string values.
472
+ * Preserves low-risk debugging info (stack traces, file paths).
473
+ *
474
+ * @param obj - The object to sanitize
475
+ * @param depth - Current recursion depth (default: 0)
476
+ * @returns Object with secrets removed
477
+ */
478
+ export function sanitizeLogObject(obj, depth = 0) {
479
+ // Prevent infinite recursion
480
+ if (depth > 10) {
481
+ return "[MAX_DEPTH_REACHED]";
482
+ }
483
+ if (obj === null || obj === undefined) {
484
+ return obj;
485
+ }
486
+ if (typeof obj === "string") {
487
+ return sanitizeLogMessage(obj);
488
+ }
489
+ if (typeof obj === "number" || typeof obj === "boolean") {
490
+ return obj;
491
+ }
492
+ if (typeof obj !== "object") {
493
+ return obj;
494
+ }
495
+ if (Array.isArray(obj)) {
496
+ return obj.map((item) => sanitizeLogObject(item, depth + 1));
497
+ }
498
+ const sanitized = {};
499
+ for (const [key, value] of Object.entries(obj)) {
500
+ // Skip secret fields entirely
501
+ if (isSecretField(key)) {
502
+ continue;
503
+ }
504
+ // Recursively sanitize all other fields
505
+ sanitized[key] = sanitizeLogObject(value, depth + 1);
506
+ }
507
+ return sanitized;
508
+ }
509
+ /**
510
+ * Sanitize an object for export: exclude forbidden keys (omitted), redact secret keys (key kept, value [REDACTED]),
511
+ * and apply sensitive patterns to string values. Used by PolicyAwareLogProcessor when exporting log body objects.
512
+ */
513
+ export function sanitizeLogObjectForExport(obj, forbiddenFields, depth = 0, sensitivePatterns = []) {
514
+ if (depth > 10) {
515
+ return "[MAX_DEPTH_REACHED]";
516
+ }
517
+ if (obj === null || obj === undefined) {
518
+ return obj;
519
+ }
520
+ if (typeof obj === "string") {
521
+ return sanitizeStringValueForExport(obj, "", [], sensitivePatterns);
522
+ }
523
+ if (typeof obj === "number" || typeof obj === "boolean") {
524
+ return obj;
525
+ }
526
+ if (typeof obj !== "object") {
527
+ return obj;
528
+ }
529
+ if (Array.isArray(obj)) {
530
+ return obj.map((item) => sanitizeLogObjectForExport(item, forbiddenFields, depth + 1, sensitivePatterns));
531
+ }
532
+ const result = {};
533
+ for (const [key, value] of Object.entries(obj)) {
534
+ const lowerKey = key.toLowerCase();
535
+ // Secret fields: keep key, redact value (consistent with sanitizeAttributes)
536
+ if (isSecretField(key)) {
537
+ result[key] = REDACTED_PLACEHOLDER;
538
+ continue;
539
+ }
540
+ // Forbidden fields (e.g. prompt, code, stack): exclude from export (omit key and value).
541
+ if (forbiddenFields.has(lowerKey)) {
542
+ continue;
543
+ }
544
+ // String values: redact stack traces by key or content, then sensitive patterns
545
+ if (typeof value === "string") {
546
+ result[key] = sanitizeStringValueForExport(value, key, [], sensitivePatterns);
547
+ continue;
548
+ }
549
+ result[key] = sanitizeLogObjectForExport(value, forbiddenFields, depth + 1, sensitivePatterns);
550
+ }
551
+ return result;
552
+ }
553
+ /**
554
+ * Sanitize a log record for export with both key-based and value-based redaction.
555
+ * Applies sensitivePatterns and redactPatterns to every string value.
556
+ * Use for the server stdout path (safe logger and RemoteLogger redactRecord) so exported logs match OTLP behavior.
557
+ */
558
+ export function sanitizeLogRecordForExport(obj, forbiddenFields, redactPatterns, sensitivePatterns, depth = 0) {
559
+ if (depth > 10) {
560
+ return "[MAX_DEPTH_REACHED]";
561
+ }
562
+ if (obj === null || obj === undefined) {
563
+ return obj;
564
+ }
565
+ if (typeof obj === "string") {
566
+ return sanitizeLogMessageForExport(obj, redactPatterns, sensitivePatterns);
567
+ }
568
+ if (typeof obj === "number" || typeof obj === "boolean") {
569
+ return obj;
570
+ }
571
+ if (typeof obj !== "object") {
572
+ return obj;
573
+ }
574
+ // Error instances have non-enumerable message/name/stack; convert so we can redact them.
575
+ if (obj instanceof Error) {
576
+ const plain = {
577
+ message: obj.message,
578
+ name: obj.name,
579
+ stack: obj.stack,
580
+ };
581
+ for (const key of Object.keys(obj)) {
582
+ if (!Object.prototype.hasOwnProperty.call(plain, key)) {
583
+ plain[key] = obj[key];
584
+ }
585
+ }
586
+ return sanitizeLogRecordForExport(plain, forbiddenFields, redactPatterns, sensitivePatterns, depth);
587
+ }
588
+ if (Array.isArray(obj)) {
589
+ return obj.map((item) => sanitizeLogRecordForExport(item, forbiddenFields, redactPatterns, sensitivePatterns, depth + 1));
590
+ }
591
+ const result = {};
592
+ for (const [key, value] of Object.entries(obj)) {
593
+ const lowerKey = key.toLowerCase();
594
+ if (isSecretField(key)) {
595
+ result[key] = REDACTED_PLACEHOLDER;
596
+ continue;
597
+ }
598
+ if (forbiddenFields.has(lowerKey)) {
599
+ continue;
600
+ }
601
+ if (typeof value === "string") {
602
+ result[key] = sanitizeStringValueForExport(value, key, redactPatterns, sensitivePatterns);
603
+ continue;
604
+ }
605
+ result[key] = sanitizeLogRecordForExport(value, forbiddenFields, redactPatterns, sensitivePatterns, depth + 1);
606
+ }
607
+ return result;
608
+ }
609
+ /**
610
+ * Sanitizes secrets from an error object.
611
+ * Preserves full stack traces for debugging.
612
+ *
613
+ * @param error - The error to sanitize
614
+ * @returns Error object with secrets redacted
615
+ */
616
+ export function sanitizeLogError(error) {
617
+ if (!error) {
618
+ return error;
619
+ }
620
+ // Check if already sanitized
621
+ const errorWithSanitized = error;
622
+ if (errorWithSanitized._sanitized) {
623
+ return error;
624
+ }
625
+ if (error instanceof Error) {
626
+ const sanitized = {
627
+ name: error.name,
628
+ message: sanitizeLogMessage(error.message),
629
+ // Preserve full stack trace, only sanitize secrets within
630
+ stack: sanitizeLogMessage(error.stack || ""),
631
+ _sanitized: true,
632
+ };
633
+ // Copy and sanitize enumerable properties
634
+ for (const [key, value] of Object.entries(error)) {
635
+ if (key !== "name" && key !== "message" && key !== "stack") {
636
+ // Skip secret fields
637
+ if (isSecretField(key)) {
638
+ continue;
639
+ }
640
+ sanitized[key] = sanitizeLogObject(value);
641
+ }
642
+ }
643
+ return sanitized;
644
+ }
645
+ // For non-Error objects, sanitize the entire object
646
+ const sanitized = sanitizeLogObject(error);
647
+ sanitized._sanitized = true;
648
+ return sanitized;
649
+ }
650
+ /**
651
+ * Safe JSON stringify with secret sanitization.
652
+ * Handles circular references and non-serializable types.
653
+ * Preserves debugging info (stack traces, file paths, emails).
654
+ *
655
+ * @param obj - Object to stringify
656
+ * @param space - Indentation (optional)
657
+ * @returns JSON string with secrets redacted
658
+ */
659
+ export function safeJsonStringify(obj, space) {
660
+ if (obj === null || obj === undefined) {
661
+ return "{}";
662
+ }
663
+ if (typeof obj === "string") {
664
+ return sanitizeLogMessage(obj);
665
+ }
666
+ const seen = new WeakSet();
667
+ const errors = new WeakMap();
668
+ const normalizeError = (value) => {
669
+ if (!(value instanceof Error))
670
+ return value;
671
+ const existing = errors.get(value);
672
+ if (existing)
673
+ return existing;
674
+ const normalized = {
675
+ name: value.name,
676
+ message: sanitizeLogMessage(value.message),
677
+ stack: sanitizeLogMessage(value.stack || ""),
678
+ get cause() {
679
+ // Arbitrary causes and custom fields can contain request/response data.
680
+ const cause = value.cause;
681
+ return cause instanceof Error ? cause : undefined;
682
+ },
683
+ };
684
+ errors.set(value, normalized);
685
+ return normalized;
686
+ };
687
+ return JSON.stringify(normalizeError(obj), (key, value) => {
688
+ // Skip secret fields
689
+ if (key && isSecretField(key)) {
690
+ return undefined;
691
+ }
692
+ // Handle null/undefined
693
+ if (value === null || value === undefined) {
694
+ return value;
695
+ }
696
+ // Also normalize Errors returned by ordinary objects' toJSON methods.
697
+ value = normalizeError(value);
698
+ // Handle circular references
699
+ if (typeof value === "object") {
700
+ if (seen.has(value)) {
701
+ return "[Circular Reference]";
702
+ }
703
+ seen.add(value);
704
+ // JSON unwraps boxed primitives itself.
705
+ if (value instanceof Number ||
706
+ value instanceof String ||
707
+ value instanceof Boolean ||
708
+ value instanceof BigInt) {
709
+ return value;
710
+ }
711
+ // JSON invokes toJSON before its replacer. Normalize child Errors on
712
+ // access so their custom toJSON cannot bypass the field allowlist.
713
+ // Lazy reads preserve ordinary getters' count, receiver, and order.
714
+ // Empty proxy targets also support frozen source properties without
715
+ // preallocating accessors for every key/index (including array holes).
716
+ const get = (_target, childKey) => normalizeError(Reflect.get(value, childKey));
717
+ if (Array.isArray(value))
718
+ return new Proxy([], { get });
719
+ return new Proxy({}, {
720
+ get,
721
+ ownKeys: () => Object.keys(value),
722
+ getOwnPropertyDescriptor: () => ({
723
+ configurable: true,
724
+ enumerable: true,
725
+ }),
726
+ });
727
+ }
728
+ // Sanitize string values
729
+ if (typeof value === "string") {
730
+ return sanitizeLogMessage(value);
731
+ }
732
+ // Handle non-serializable types
733
+ if (typeof value === "function") {
734
+ return `[Function: ${value.name || "anonymous"}]`;
735
+ }
736
+ if (typeof value === "symbol") {
737
+ return `[Symbol: ${value.toString()}]`;
738
+ }
739
+ if (typeof value === "bigint") {
740
+ return `[BigInt: ${value.toString()}]`;
741
+ }
742
+ return value;
743
+ }, space);
744
+ }
745
+ //# sourceMappingURL=log-sanitizer.js.map