newrelic 10.6.0 → 10.6.1

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 (3) hide show
  1. package/NEWS.md +6 -0
  2. package/esm-loader.mjs +40 -25
  3. package/package.json +1 -1
package/NEWS.md CHANGED
@@ -1,3 +1,9 @@
1
+ ### v10.6.1 (2023-08-01)
2
+
3
+ #### Security Improvements
4
+
5
+ * updated ESM loader to track instrumentation by url in a map instead of in url to avoid remote code execution. ([#1741](https://github.com/newrelic/node-newrelic/pull/1741)) ([c8dc779](https://github.com/newrelic/node-newrelic/commit/c8dc779c7799b234290b6f7eb1d0a4e07d692ef9))
6
+
1
7
  ### v10.6.0 (2023-07-26)
2
8
 
3
9
  #### Miscellaneous Chores
package/esm-loader.mjs CHANGED
@@ -89,14 +89,10 @@ export async function resolve(specifier, context, nextResolve) {
89
89
  })
90
90
 
91
91
  // Keep track of what we've registered so we don't double register (see: https://github.com/newrelic/node-newrelic/issues/1646)
92
- registeredSpecifiers.set(url, specifier)
92
+ registeredSpecifiers.set(url, { specifier })
93
93
  } else if (format === 'module') {
94
- registeredSpecifiers.set(url, specifier)
95
- const modifiedUrl = new URL(url)
96
- // add a query param to the resolved url so the load hook below knows
97
- // to rewrite and wrap the source code
98
- modifiedUrl.searchParams.set('hasNrInstrumentation', 'true')
99
- resolvedModule.url = modifiedUrl.href
94
+ addNrInstrumentation(resolvedModule)
95
+ registeredSpecifiers.set(url, { specifier, hasNrInstrumentation: true })
100
96
  } else {
101
97
  logger.debug(`${specifier} is not a CommonJS nor ESM package, skipping for now.`)
102
98
  }
@@ -105,6 +101,38 @@ export async function resolve(specifier, context, nextResolve) {
105
101
  return resolvedModule
106
102
  }
107
103
 
104
+ /**
105
+ * This is purely done so that we can import the incoming specifier
106
+ * in the load hook. It used to be used to determine if instrumentation existed
107
+ * for specifier but we found a RCE with that solution.
108
+ *
109
+ * @param {object} resolvedModule the result of call resolve on a specifier
110
+ */
111
+ function addNrInstrumentation(resolvedModule) {
112
+ const modifiedUrl = new URL(resolvedModule.url)
113
+ modifiedUrl.searchParams.set('hasNrInstrumentation', 'true')
114
+ resolvedModule.url = modifiedUrl.href
115
+ }
116
+
117
+ /**
118
+ * Extracts the href without query params
119
+ *
120
+ * @param {string} url url of specifier
121
+ * @returns {string} new url without hasNrInstrumentation query param
122
+ */
123
+ function removeNrInstrumentation(url) {
124
+ let parsedUrl
125
+
126
+ try {
127
+ parsedUrl = new URL(url)
128
+ url = parsedUrl.href.split('?')[0]
129
+ } catch (err) {
130
+ logger.error('Unable to parse url: %s, msg: %s', url, err.message)
131
+ }
132
+
133
+ return url
134
+ }
135
+
108
136
  /**
109
137
  * Hook chain responsible for determining how a URL should be interpreted, retrieved, and parsed.
110
138
  *
@@ -123,30 +151,17 @@ export async function load(url, context, nextLoad) {
123
151
  return nextLoad(url, context, nextLoad)
124
152
  }
125
153
 
126
- let parsedUrl
127
-
128
- try {
129
- parsedUrl = new URL(url)
130
- } catch (err) {
131
- logger.error('Unable to parse url: %s, msg: %s', url, err.message)
132
- return nextLoad(url, context, nextLoad)
133
- }
154
+ url = removeNrInstrumentation(url)
134
155
 
135
- const hasNrInstrumentation = parsedUrl.searchParams.get('hasNrInstrumentation')
156
+ const pkg = registeredSpecifiers.get(url)
136
157
 
137
- if (!hasNrInstrumentation) {
158
+ if (!pkg?.hasNrInstrumentation) {
138
159
  return nextLoad(url, context, nextLoad)
139
160
  }
140
161
 
141
- /**
142
- * undo the work we did in the resolve hook above
143
- * so we can properly rewrite source and not get in an infinite loop
144
- */
145
- parsedUrl.searchParams.delete('hasNrInstrumentation')
162
+ const { specifier } = pkg
146
163
 
147
- const originalUrl = parsedUrl.href
148
- const specifier = registeredSpecifiers.get(originalUrl)
149
- const rewrittenSource = await wrapEsmSource(originalUrl, specifier)
164
+ const rewrittenSource = await wrapEsmSource(url, specifier)
150
165
  logger.debug(`Registered module instrumentation for ${specifier}.`)
151
166
 
152
167
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "10.6.0",
3
+ "version": "10.6.1",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [