@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,174 @@
1
+ /**
2
+ * NPM Registry Attribute Sanitization (APPS-4190)
3
+ *
4
+ * Sanitization helpers for the telemetry attributes that P6.2 (APPS-4189
5
+ * metrics) and P6.3 (APPS-4191 audit events) emit from every npm install
6
+ * path. Every emitter MUST pass values through these helpers before
7
+ * attaching them to a span, metric, log, or audit event so that cloud-prem
8
+ * telemetry never carries customer infra hostnames or registry tokens.
9
+ *
10
+ * Policy split — package_name is destination-dependent (decision-point
11
+ * captured in PR / README "NPM Registry Sanitization"):
12
+ *
13
+ * - For metric destinations (Datadog tags, OTel metric attributes,
14
+ * anything where cardinality is a billing/scale concern OR the
15
+ * destination is shared across orgs): emit a low-cardinality bucket
16
+ * instead of the raw name. Use `npmAttributesForMetric` →
17
+ * `package_name_bucket` ∈ { public | private | unknown | invalid }.
18
+ *
19
+ * - For audit / log destinations (per-org event streams, forensic
20
+ * value, internally retained): emit the lowercased name verbatim,
21
+ * gated against known-secret value shapes. Use `npmAttributesForAudit`
22
+ * → `package_name` ∈ { <verbatim> | unknown | invalid }.
23
+ *
24
+ * Other attributes (the same across destinations):
25
+ * - registry_host → bounded enum: public_npm | private | unknown
26
+ * - npm.outcome → closed allowlist; unknown values → `other`
27
+ * - tokens → never appear in any attribute (regression-tested)
28
+ */
29
+ export declare const NPM_REGISTRY_BUCKETS: readonly ["public_npm", "private", "unknown"];
30
+ export type NpmRegistryBucket = (typeof NPM_REGISTRY_BUCKETS)[number];
31
+ /**
32
+ * Map a registry URL or bare hostname to a bounded bucket. Raw hostnames
33
+ * are never emitted; downstream emitters always pass values through here.
34
+ *
35
+ * Rules:
36
+ * - null/undefined/blank → `unknown`
37
+ * - parse failure → `unknown`
38
+ * - hostname exact-matches PUBLIC_NPM_HOSTS (case-insensitive) → `public_npm`
39
+ * - anything else → `private`
40
+ *
41
+ * `npm.pkg.github.com` is conservatively bucketed as `private`: customers
42
+ * deploy it as their private registry, and bucketing it as private avoids
43
+ * the false-negative of suppressing private-registry behavior in telemetry.
44
+ */
45
+ export declare function bucketNpmRegistryHost(input: string | null | undefined): NpmRegistryBucket;
46
+ /**
47
+ * Audit/log-destination form of `registry_host`. Unlike the metric/span
48
+ * path (which buckets to `public_npm | private | unknown` so a raw customer
49
+ * hostname never reaches a shared, cross-tenant destination), logs are
50
+ * per-org and retained internally, so on-call needs the **raw** hostname for
51
+ * forensic value ("which registry did org X actually hit?"). This helper is
52
+ * the log-boundary guard: it returns the host verbatim EXCEPT when it
53
+ *
54
+ * - matches a known-secret value shape (`containsForbiddenPattern`) — a
55
+ * developer-error backstop against a token smuggled in via a host arg, or
56
+ * - contains an ASCII C0 control character or DEL — a log-injection /
57
+ * line-spoofing payload,
58
+ *
59
+ * in which case it returns `"invalid"`. null/blank → `"unknown"`. The value
60
+ * is trimmed but otherwise unchanged (not lowercased): the host is the
61
+ * forensic signal and case is preserved as attempted.
62
+ */
63
+ export declare function auditNpmRegistryHost(input: string | null | undefined): string;
64
+ /**
65
+ * Audit/log-destination form of the `package_name` attribute. Returns the
66
+ * lowercased input verbatim when it parses as an npm name AND does not
67
+ * match a known-secret value shape. Use only for destinations that can
68
+ * tolerate per-package cardinality and per-org name sensitivity (audit
69
+ * event streams, internal logs). For metric destinations, use
70
+ * `bucketNpmPackageName` instead — emitting raw names as metric tags is
71
+ * unbounded cardinality and leaks customer-internal package names across
72
+ * orgs sharing a metric destination.
73
+ *
74
+ * - null/undefined/blank → `unknown`
75
+ * - matches a known-secret value pattern (token, JWT, AWS key, etc.)
76
+ * → `invalid` (defense for developer error
77
+ * that stuffs a secret into a name arg)
78
+ * - input fails npm name spec → `invalid`
79
+ * - otherwise → lowercased input verbatim
80
+ *
81
+ * The known-secret check uses `containsForbiddenPattern` from the tier-2
82
+ * traces contract, so this helper stays in sync with the existing
83
+ * runtime/lint guardrails as new secret shapes are added there.
84
+ */
85
+ export declare function sanitizeNpmPackageName(input: string | null | undefined): string;
86
+ /**
87
+ * Metric-destination bucket for the package name. Low-cardinality and
88
+ * agnostic of the specific name — dashboards split installs by registry
89
+ * source without ever seeing customer-internal package names.
90
+ *
91
+ * - public_npm registry + valid name → `public`
92
+ * - private registry + valid name → `private`
93
+ * - unknown registry + valid name → `unknown`
94
+ * - name fails npm spec OR matches a known-secret value pattern → `invalid`
95
+ * - name is null/empty → `unknown`
96
+ *
97
+ * The registry bucket is taken as input rather than recomputed so callers
98
+ * never accidentally bucket a name without also bucketing its registry.
99
+ */
100
+ export declare const NPM_PACKAGE_NAME_BUCKETS: readonly ["public", "private", "unknown", "invalid"];
101
+ export type NpmPackageNameBucket = (typeof NPM_PACKAGE_NAME_BUCKETS)[number];
102
+ export declare function bucketNpmPackageName(input: string | null | undefined, registryBucket: NpmRegistryBucket): NpmPackageNameBucket;
103
+ /**
104
+ * Closed outcome enum. First five values match the structured error from
105
+ * APPS-4179 (P1.2) and APPS-4195 (P1.5); `tls_failed` was added in
106
+ * APPS-4381 to give TLS handshake failures their own dashboard facet
107
+ * (previously routed to `other` and lost the actionable CA-bundle signal);
108
+ * `other` is the catch-all so dashboards never see free-form strings.
109
+ */
110
+ export declare const NPM_OUTCOMES: readonly ["success", "not_in_registry", "registry_unreachable", "registry_auth_failed", "tls_failed", "lockfile_url_mismatch", "package_substituted", "vulnerable_package", "vulnerability_triaged", "vulnerability_user_override", "other"];
111
+ export type NpmOutcome = (typeof NPM_OUTCOMES)[number];
112
+ /** Coerce an outcome to the closed enum at the emit boundary. */
113
+ export declare function normalizeNpmOutcome(input: string | null | undefined): NpmOutcome;
114
+ export interface NpmAttributesInput {
115
+ registry_host?: string | null;
116
+ package_name?: string | null;
117
+ outcome?: string | null;
118
+ }
119
+ /** Metric-destination output shape. Low-cardinality across all fields. */
120
+ export interface NpmMetricAttributes {
121
+ registry_host: NpmRegistryBucket;
122
+ package_name_bucket: NpmPackageNameBucket;
123
+ outcome: NpmOutcome;
124
+ }
125
+ /**
126
+ * Audit/log-destination output shape. `package_name` is verbatim
127
+ * (lowercased) — use only for destinations that can tolerate per-package
128
+ * cardinality and per-org name sensitivity.
129
+ */
130
+ export interface NpmAuditAttributes {
131
+ registry_host: NpmRegistryBucket;
132
+ package_name: string;
133
+ outcome: NpmOutcome;
134
+ }
135
+ /**
136
+ * Metric-safe attributes. Emitters of OTel metrics, Datadog metric tags,
137
+ * Tempo span attributes that flow to shared metric destinations, etc.
138
+ * MUST use this wrapper. The output never contains a raw package name.
139
+ */
140
+ export declare function npmAttributesForMetric(input: NpmAttributesInput): NpmMetricAttributes;
141
+ /**
142
+ * Audit/log-safe attributes. Emitters of audit events and internal logs
143
+ * that retain per-org context (and that operators rely on for forensic
144
+ * value, e.g. "which package failed for org X?") use this wrapper. The
145
+ * verbatim name is gated by the known-secret value pattern set.
146
+ */
147
+ export declare function npmAttributesForAudit(input: NpmAttributesInput): NpmAuditAttributes;
148
+ /**
149
+ * Redact registry URLs and bare hostnames from a free-text npm/pnpm error
150
+ * blob before it lands in a shared operational log line (auto-upgrade
151
+ * `[npm-install-blocked]` and any future caller). The destination is shared
152
+ * across orgs, so even an incidental hostname in the npm-emitted phrasing —
153
+ * `"401 Unauthorized - GET https://customer.jfrog.io/..."`,
154
+ * `"request to https://npm.private/... failed, reason: ENOTFOUND npm.private"`,
155
+ * `"connect ECONNREFUSED 10.0.0.5:4873"` — is a customer-infra leak that
156
+ * `bucketNpmRegistryHost` already prevents for the structured `registry_host`
157
+ * facet. This helper extends the same rule to free-text fields:
158
+ *
159
+ * - Every `http(s)://...` URL is replaced with its registry bucket token
160
+ * (`<public_npm>` or `<private>`), preserving the diagnostic shape of
161
+ * the line while erasing the host.
162
+ * - Bare `ENOTFOUND <host>` / `EAI_AGAIN <host>` payloads — npm emits the
163
+ * host as the next whitespace-delimited token after these DNS error
164
+ * codes — get the host replaced with `<host>`.
165
+ * - `connect E* <ip>[:port]` payloads (ECONNREFUSED, ETIMEDOUT, …) — the
166
+ * IP literal carries the same private-infra signal as a hostname — get
167
+ * the address replaced with `<host>`.
168
+ *
169
+ * The replacement is intentionally lossy: the npm phrasing ("Unauthorized",
170
+ * "self-signed certificate in chain", "ENOTFOUND") is what operators need
171
+ * for triage; the host identity is what we must drop.
172
+ */
173
+ export declare function redactNpmRegistryHostsFromText(input: string): string;
174
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/npm-registry/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,eAAO,MAAM,oBAAoB,+CAIvB,CAAC;AACX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AA2BtE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,iBAAiB,CAkBnB;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS7E;AAeD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAWR;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,sDAK3B,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,cAAc,EAAE,iBAAiB,GAChC,oBAAoB,CAetB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,8OAYf,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAIvD,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,UAAU,CAGZ;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,iBAAiB,CAAC;IACjC,mBAAmB,EAAE,oBAAoB,CAAC;IAC1C,OAAO,EAAE,UAAU,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,iBAAiB,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,CAUrB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,CAMpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA0BpE"}
@@ -0,0 +1,331 @@
1
+ /**
2
+ * NPM Registry Attribute Sanitization (APPS-4190)
3
+ *
4
+ * Sanitization helpers for the telemetry attributes that P6.2 (APPS-4189
5
+ * metrics) and P6.3 (APPS-4191 audit events) emit from every npm install
6
+ * path. Every emitter MUST pass values through these helpers before
7
+ * attaching them to a span, metric, log, or audit event so that cloud-prem
8
+ * telemetry never carries customer infra hostnames or registry tokens.
9
+ *
10
+ * Policy split — package_name is destination-dependent (decision-point
11
+ * captured in PR / README "NPM Registry Sanitization"):
12
+ *
13
+ * - For metric destinations (Datadog tags, OTel metric attributes,
14
+ * anything where cardinality is a billing/scale concern OR the
15
+ * destination is shared across orgs): emit a low-cardinality bucket
16
+ * instead of the raw name. Use `npmAttributesForMetric` →
17
+ * `package_name_bucket` ∈ { public | private | unknown | invalid }.
18
+ *
19
+ * - For audit / log destinations (per-org event streams, forensic
20
+ * value, internally retained): emit the lowercased name verbatim,
21
+ * gated against known-secret value shapes. Use `npmAttributesForAudit`
22
+ * → `package_name` ∈ { <verbatim> | unknown | invalid }.
23
+ *
24
+ * Other attributes (the same across destinations):
25
+ * - registry_host → bounded enum: public_npm | private | unknown
26
+ * - npm.outcome → closed allowlist; unknown values → `other`
27
+ * - tokens → never appear in any attribute (regression-tested)
28
+ */
29
+ import { containsForbiddenPattern } from "../common/contracts/tier2-traces.js";
30
+ export const NPM_REGISTRY_BUCKETS = [
31
+ "public_npm",
32
+ "private",
33
+ "unknown",
34
+ ];
35
+ /**
36
+ * Exact-match allowlist for the `public_npm` bucket: hostnames that serve the
37
+ * public npm package set and need no login, so labelling them `public_npm`
38
+ * leaks nothing and keeps them out of the `private` count.
39
+ *
40
+ * - `registry.npmjs.org` / `registry.npmjs.com`: the npm public registry
41
+ * (the `.com` form is a long-standing alias customers still pin).
42
+ * - `registry.npmmirror.com`: the public npmmirror CDN (popular in Asia),
43
+ * formerly `registry.npm.taobao.org`.
44
+ * - `registry.npm.taobao.org`: the old taobao mirror hostname, still set in
45
+ * older lockfiles and `.npmrc` files.
46
+ *
47
+ * Deliberately NOT here: `npm.pkg.github.com` (GitHub Packages), `*.jfrog.io`,
48
+ * and AWS CodeArtifact hostnames. Customers deploy those as their own private
49
+ * registry and each needs a login, so even when one proxies the public npm
50
+ * registry the access is scoped to a single org. Bucketing them `private` is
51
+ * the correct and safe choice.
52
+ */
53
+ const PUBLIC_NPM_HOSTS = new Set([
54
+ "registry.npmjs.org",
55
+ "registry.npmjs.com",
56
+ "registry.npmmirror.com",
57
+ "registry.npm.taobao.org",
58
+ ]);
59
+ /**
60
+ * Map a registry URL or bare hostname to a bounded bucket. Raw hostnames
61
+ * are never emitted; downstream emitters always pass values through here.
62
+ *
63
+ * Rules:
64
+ * - null/undefined/blank → `unknown`
65
+ * - parse failure → `unknown`
66
+ * - hostname exact-matches PUBLIC_NPM_HOSTS (case-insensitive) → `public_npm`
67
+ * - anything else → `private`
68
+ *
69
+ * `npm.pkg.github.com` is conservatively bucketed as `private`: customers
70
+ * deploy it as their private registry, and bucketing it as private avoids
71
+ * the false-negative of suppressing private-registry behavior in telemetry.
72
+ */
73
+ export function bucketNpmRegistryHost(input) {
74
+ if (input == null)
75
+ return "unknown";
76
+ const trimmed = input.trim();
77
+ if (trimmed === "")
78
+ return "unknown";
79
+ let host;
80
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
81
+ try {
82
+ host = new URL(trimmed).hostname.toLowerCase();
83
+ }
84
+ catch {
85
+ return "unknown";
86
+ }
87
+ }
88
+ else {
89
+ host = trimmed.split(/[/:]/, 1)[0].toLowerCase();
90
+ }
91
+ if (host === "")
92
+ return "unknown";
93
+ return PUBLIC_NPM_HOSTS.has(host) ? "public_npm" : "private";
94
+ }
95
+ /**
96
+ * Control characters (ASCII C0 range + DEL) that must never reach a log
97
+ * line: a newline/carriage-return lets a hostile value forge a second log
98
+ * record, and other C0 bytes corrupt structured-log parsers.
99
+ */
100
+ function hasControlChar(value) {
101
+ // ASCII C0 controls (0x00-0x1F) + DEL (0x7F). A regex literal holding these
102
+ // raw bytes is invisible in source and trips no-control-regex, so we scan
103
+ // code points instead.
104
+ for (let i = 0; i < value.length; i++) {
105
+ const code = value.charCodeAt(i);
106
+ if (code <= 0x1f || code === 0x7f)
107
+ return true;
108
+ }
109
+ return false;
110
+ }
111
+ /**
112
+ * Audit/log-destination form of `registry_host`. Unlike the metric/span
113
+ * path (which buckets to `public_npm | private | unknown` so a raw customer
114
+ * hostname never reaches a shared, cross-tenant destination), logs are
115
+ * per-org and retained internally, so on-call needs the **raw** hostname for
116
+ * forensic value ("which registry did org X actually hit?"). This helper is
117
+ * the log-boundary guard: it returns the host verbatim EXCEPT when it
118
+ *
119
+ * - matches a known-secret value shape (`containsForbiddenPattern`) — a
120
+ * developer-error backstop against a token smuggled in via a host arg, or
121
+ * - contains an ASCII C0 control character or DEL — a log-injection /
122
+ * line-spoofing payload,
123
+ *
124
+ * in which case it returns `"invalid"`. null/blank → `"unknown"`. The value
125
+ * is trimmed but otherwise unchanged (not lowercased): the host is the
126
+ * forensic signal and case is preserved as attempted.
127
+ */
128
+ export function auditNpmRegistryHost(input) {
129
+ if (input == null)
130
+ return "unknown";
131
+ const trimmed = input.trim();
132
+ if (trimmed === "")
133
+ return "unknown";
134
+ // Secret-shape check runs on the trimmed (case-preserved) value: AKIA/JWT
135
+ // patterns are case-sensitive and must be caught before they reach a log.
136
+ if (containsForbiddenPattern(trimmed))
137
+ return "invalid";
138
+ if (hasControlChar(trimmed))
139
+ return "invalid";
140
+ return trimmed;
141
+ }
142
+ /** npm package name spec (lowercased): optional `@scope/` prefix + name body. */
143
+ const VALID_PACKAGE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
144
+ /**
145
+ * npm name length cap from the package-name spec. The character-set regex
146
+ * alone admits arbitrarily long strings of `[a-z0-9._-]`; without this cap
147
+ * a hostile or buggy caller can flow a 10k-char payload through the
148
+ * verbatim audit/log path. Enforced on both the audit and metric helpers
149
+ * so an oversize input is `invalid` in either destination.
150
+ */
151
+ const NPM_PACKAGE_NAME_MAX_LENGTH = 214;
152
+ /**
153
+ * Audit/log-destination form of the `package_name` attribute. Returns the
154
+ * lowercased input verbatim when it parses as an npm name AND does not
155
+ * match a known-secret value shape. Use only for destinations that can
156
+ * tolerate per-package cardinality and per-org name sensitivity (audit
157
+ * event streams, internal logs). For metric destinations, use
158
+ * `bucketNpmPackageName` instead — emitting raw names as metric tags is
159
+ * unbounded cardinality and leaks customer-internal package names across
160
+ * orgs sharing a metric destination.
161
+ *
162
+ * - null/undefined/blank → `unknown`
163
+ * - matches a known-secret value pattern (token, JWT, AWS key, etc.)
164
+ * → `invalid` (defense for developer error
165
+ * that stuffs a secret into a name arg)
166
+ * - input fails npm name spec → `invalid`
167
+ * - otherwise → lowercased input verbatim
168
+ *
169
+ * The known-secret check uses `containsForbiddenPattern` from the tier-2
170
+ * traces contract, so this helper stays in sync with the existing
171
+ * runtime/lint guardrails as new secret shapes are added there.
172
+ */
173
+ export function sanitizeNpmPackageName(input) {
174
+ if (input == null)
175
+ return "unknown";
176
+ const trimmed = input.trim();
177
+ if (trimmed === "")
178
+ return "unknown";
179
+ if (trimmed.length > NPM_PACKAGE_NAME_MAX_LENGTH)
180
+ return "invalid";
181
+ // Check against secret patterns BEFORE lowercasing: AKIA/JWT/PEM patterns
182
+ // are case-sensitive and would otherwise slip through after toLowerCase().
183
+ if (containsForbiddenPattern(trimmed))
184
+ return "invalid";
185
+ const normalized = trimmed.toLowerCase();
186
+ if (!VALID_PACKAGE_NAME_RE.test(normalized))
187
+ return "invalid";
188
+ return normalized;
189
+ }
190
+ /**
191
+ * Metric-destination bucket for the package name. Low-cardinality and
192
+ * agnostic of the specific name — dashboards split installs by registry
193
+ * source without ever seeing customer-internal package names.
194
+ *
195
+ * - public_npm registry + valid name → `public`
196
+ * - private registry + valid name → `private`
197
+ * - unknown registry + valid name → `unknown`
198
+ * - name fails npm spec OR matches a known-secret value pattern → `invalid`
199
+ * - name is null/empty → `unknown`
200
+ *
201
+ * The registry bucket is taken as input rather than recomputed so callers
202
+ * never accidentally bucket a name without also bucketing its registry.
203
+ */
204
+ export const NPM_PACKAGE_NAME_BUCKETS = [
205
+ "public",
206
+ "private",
207
+ "unknown",
208
+ "invalid",
209
+ ];
210
+ export function bucketNpmPackageName(input, registryBucket) {
211
+ if (input == null)
212
+ return "unknown";
213
+ const trimmed = input.trim();
214
+ if (trimmed === "")
215
+ return "unknown";
216
+ if (trimmed.length > NPM_PACKAGE_NAME_MAX_LENGTH)
217
+ return "invalid";
218
+ if (containsForbiddenPattern(trimmed))
219
+ return "invalid";
220
+ if (!VALID_PACKAGE_NAME_RE.test(trimmed.toLowerCase()))
221
+ return "invalid";
222
+ switch (registryBucket) {
223
+ case "public_npm":
224
+ return "public";
225
+ case "private":
226
+ return "private";
227
+ case "unknown":
228
+ return "unknown";
229
+ }
230
+ }
231
+ /**
232
+ * Closed outcome enum. First five values match the structured error from
233
+ * APPS-4179 (P1.2) and APPS-4195 (P1.5); `tls_failed` was added in
234
+ * APPS-4381 to give TLS handshake failures their own dashboard facet
235
+ * (previously routed to `other` and lost the actionable CA-bundle signal);
236
+ * `other` is the catch-all so dashboards never see free-form strings.
237
+ */
238
+ export const NPM_OUTCOMES = [
239
+ "success",
240
+ "not_in_registry",
241
+ "registry_unreachable",
242
+ "registry_auth_failed",
243
+ "tls_failed",
244
+ "lockfile_url_mismatch",
245
+ "package_substituted",
246
+ "vulnerable_package",
247
+ "vulnerability_triaged",
248
+ "vulnerability_user_override",
249
+ "other",
250
+ ];
251
+ const NPM_OUTCOME_SET = new Set(NPM_OUTCOMES);
252
+ /** Coerce an outcome to the closed enum at the emit boundary. */
253
+ export function normalizeNpmOutcome(input) {
254
+ if (typeof input !== "string")
255
+ return "other";
256
+ return NPM_OUTCOME_SET.has(input) ? input : "other";
257
+ }
258
+ /**
259
+ * Metric-safe attributes. Emitters of OTel metrics, Datadog metric tags,
260
+ * Tempo span attributes that flow to shared metric destinations, etc.
261
+ * MUST use this wrapper. The output never contains a raw package name.
262
+ */
263
+ export function npmAttributesForMetric(input) {
264
+ const registry_host = bucketNpmRegistryHost(input.registry_host);
265
+ return {
266
+ registry_host,
267
+ package_name_bucket: bucketNpmPackageName(input.package_name, registry_host),
268
+ outcome: normalizeNpmOutcome(input.outcome),
269
+ };
270
+ }
271
+ /**
272
+ * Audit/log-safe attributes. Emitters of audit events and internal logs
273
+ * that retain per-org context (and that operators rely on for forensic
274
+ * value, e.g. "which package failed for org X?") use this wrapper. The
275
+ * verbatim name is gated by the known-secret value pattern set.
276
+ */
277
+ export function npmAttributesForAudit(input) {
278
+ return {
279
+ registry_host: bucketNpmRegistryHost(input.registry_host),
280
+ package_name: sanitizeNpmPackageName(input.package_name),
281
+ outcome: normalizeNpmOutcome(input.outcome),
282
+ };
283
+ }
284
+ /**
285
+ * Redact registry URLs and bare hostnames from a free-text npm/pnpm error
286
+ * blob before it lands in a shared operational log line (auto-upgrade
287
+ * `[npm-install-blocked]` and any future caller). The destination is shared
288
+ * across orgs, so even an incidental hostname in the npm-emitted phrasing —
289
+ * `"401 Unauthorized - GET https://customer.jfrog.io/..."`,
290
+ * `"request to https://npm.private/... failed, reason: ENOTFOUND npm.private"`,
291
+ * `"connect ECONNREFUSED 10.0.0.5:4873"` — is a customer-infra leak that
292
+ * `bucketNpmRegistryHost` already prevents for the structured `registry_host`
293
+ * facet. This helper extends the same rule to free-text fields:
294
+ *
295
+ * - Every `http(s)://...` URL is replaced with its registry bucket token
296
+ * (`<public_npm>` or `<private>`), preserving the diagnostic shape of
297
+ * the line while erasing the host.
298
+ * - Bare `ENOTFOUND <host>` / `EAI_AGAIN <host>` payloads — npm emits the
299
+ * host as the next whitespace-delimited token after these DNS error
300
+ * codes — get the host replaced with `<host>`.
301
+ * - `connect E* <ip>[:port]` payloads (ECONNREFUSED, ETIMEDOUT, …) — the
302
+ * IP literal carries the same private-infra signal as a hostname — get
303
+ * the address replaced with `<host>`.
304
+ *
305
+ * The replacement is intentionally lossy: the npm phrasing ("Unauthorized",
306
+ * "self-signed certificate in chain", "ENOTFOUND") is what operators need
307
+ * for triage; the host identity is what we must drop.
308
+ */
309
+ export function redactNpmRegistryHostsFromText(input) {
310
+ if (input === "")
311
+ return input;
312
+ // URLs first. The match is greedy on non-whitespace/closing-paren chars to
313
+ // capture query strings and paths along with the host so the whole URL is
314
+ // replaced as a unit (a trailing `/path?token=...` containing a leaked
315
+ // host substring would otherwise survive a host-only replacement).
316
+ let out = input.replace(/https?:\/\/[^\s)]+/gi, (match) => {
317
+ const bucket = bucketNpmRegistryHost(match);
318
+ return bucket === "public_npm" ? "<public_npm>" : "<private>";
319
+ });
320
+ // DNS error codes name the host on the same line. Match the code as a
321
+ // word boundary so substrings inside other tokens don't trigger. The
322
+ // captured host token is whatever sits between the code and the next
323
+ // whitespace / punctuation that npm uses as a separator.
324
+ out = out.replace(/\b(ENOTFOUND|EAI_AGAIN)\s+([^\s,;:'")\]]+)/gi, (_m, code) => `${code} <host>`);
325
+ // `connect E<CODE> <ip>[:port]` — npm phrasing for low-level socket
326
+ // errors. The IP literal is the host facet to redact; the port is dropped
327
+ // alongside because port + private-IP together is the same infra signal.
328
+ out = out.replace(/\bconnect\s+(E[A-Z]+)\s+([0-9a-fA-F.:]+)(?::\d+)?/g, (_m, code) => `connect ${code} <host>`);
329
+ return out;
330
+ }
331
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/npm-registry/sanitize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * NPM Registry Spans (APPS-4378)
3
+ *
4
+ * OTel-native `startActiveSpan` helpers for the four named npm-registry spans.
5
+ * Using the global tracer (`trace.getTracer`) means LLMObs interop is
6
+ * automatic: a span started inside an `@llmobs.decorate`'d context becomes a
7
+ * child of that task span (shared global tracer provider); outside any LLMObs
8
+ * context (server verify, CLI bootstrap) it is a root span.
9
+ *
10
+ * `org_id` rides on spans (per-trace context), never on a metric label. Raw
11
+ * registry hostnames are bucketed via {@link bucketNpmRegistryHost} before
12
+ * they reach a span attribute, matching the metric path — the one exception
13
+ * is the structured-log path, which keeps the raw host (see
14
+ * {@link auditNpmRegistryHost} in ./sanitize).
15
+ */
16
+ import { type Attributes, type Span } from "@opentelemetry/api";
17
+ /** The four named spans (APPS-4378). */
18
+ export declare const NPM_REGISTRY_SPAN: {
19
+ readonly VERIFY: "npm_registry.verify";
20
+ readonly LOOKUP_PACKAGE: "npm_registry.lookup_package";
21
+ readonly PREPARE_FOR_PRIVATE_REGISTRY: "npm_registry.prepare_for_private_registry";
22
+ readonly SYNC_HOME_NPMRC: "npm_registry.sync_home_npmrc";
23
+ };
24
+ /**
25
+ * Canonical span-attribute keys, shared across the four spans so the
26
+ * dimension names never drift between call sites.
27
+ */
28
+ export declare const NPM_REGISTRY_SPAN_ATTR: {
29
+ readonly ORG_ID: "org_id";
30
+ /** Bucketed (`public_npm | private | unknown`) — never the raw host. */
31
+ readonly REGISTRY_HOST: "registry_host";
32
+ readonly OUTCOME: "outcome";
33
+ readonly DURATION_MS: "duration_ms";
34
+ /** Richer `NpmRegistryFetchResult.source` for drill-down. */
35
+ readonly SOURCE: "source";
36
+ readonly REGISTRY_CONFIGURED: "registry_configured";
37
+ /** Tri-valued: `true | false | "unknown"`. */
38
+ readonly ALLOW_INSTALL_SCRIPTS: "allow_install_scripts";
39
+ readonly PACKAGE_NAME_BUCKET: "package_name_bucket";
40
+ /** Span-attribute ONLY — raw byte counts are unbounded, never a metric label. */
41
+ readonly PACKAGE_UNPACKED_SIZE_BYTES: "package_unpacked_size_bytes";
42
+ };
43
+ /**
44
+ * Start an active npm-registry span over the global tracer, run `fn`, and
45
+ * end the span in `finally`. On throw, the error is recorded
46
+ * (`span.recordException`) and the span status set to ERROR before the throw
47
+ * propagates. `fn` receives the span so it can attach final attributes
48
+ * (outcome / duration) once the operation resolves.
49
+ */
50
+ export declare function withNpmRegistrySpan<T>(spanName: string, initialAttributes: Attributes, fn: (span: Span) => Promise<T>): Promise<T>;
51
+ /**
52
+ * Coerce a registry-reported `dist.unpackedSize` to a span-safe value: a
53
+ * finite, non-negative number, else `undefined`. A hostile or buggy registry
54
+ * cannot smuggle a non-numeric byte count onto the span through this guard.
55
+ *
56
+ * Callers MUST omit the attribute when this returns `undefined` rather than
57
+ * calling `setAttribute(key, undefined)` (some OTel exporters mishandle
58
+ * undefined values). The lookup call site guards with `if (size !== undefined)`.
59
+ */
60
+ export declare function npmPackageUnpackedSizeBytes(raw: unknown): number | undefined;
61
+ //# sourceMappingURL=spans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.d.ts","sourceRoot":"","sources":["../../src/npm-registry/spans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,KAAK,UAAU,EACf,KAAK,IAAI,EAGV,MAAM,oBAAoB,CAAC;AAK5B,wCAAwC;AACxC,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;IAEjC,wEAAwE;;;;IAIxE,6DAA6D;;;IAG7D,8CAA8C;;;IAG9C,iFAAiF;;CAEzE,CAAC;AAEX;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,UAAU,EAC7B,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI5E"}