dd-trace 3.16.0 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +49 -0
  3. package/package.json +7 -3
  4. package/packages/datadog-esbuild/index.js +104 -0
  5. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +1 -1
  6. package/packages/datadog-instrumentations/src/helpers/hook.js +13 -3
  7. package/packages/datadog-instrumentations/src/helpers/instrument.js +6 -0
  8. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  9. package/packages/datadog-instrumentations/src/pg.js +16 -11
  10. package/packages/dd-trace/src/appsec/{templates/blocked.html → blocked_templates.js} +19 -1
  11. package/packages/dd-trace/src/appsec/blocking.js +9 -24
  12. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +12 -0
  13. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  14. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +9 -3
  15. package/packages/dd-trace/src/appsec/iast/index.js +2 -1
  16. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +9 -2
  17. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +9 -0
  18. package/packages/dd-trace/src/appsec/index.js +4 -18
  19. package/packages/dd-trace/src/appsec/recommended.json +43 -14
  20. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -1
  21. package/packages/dd-trace/src/appsec/sdk/index.js +2 -2
  22. package/packages/dd-trace/src/config.js +21 -18
  23. package/packages/dd-trace/src/datastreams/encoding.js +5 -5
  24. package/packages/dd-trace/src/dcitm.js +51 -0
  25. package/packages/dd-trace/src/opentracing/propagation/log.js +23 -7
  26. package/packages/dd-trace/src/opentracing/propagation/text_map.js +28 -2
  27. package/packages/dd-trace/src/opentracing/span.js +14 -3
  28. package/packages/dd-trace/src/opentracing/span_context.js +3 -1
  29. package/packages/dd-trace/src/opentracing/tracer.js +3 -1
  30. package/packages/dd-trace/src/appsec/templates/blocked.json +0 -8
@@ -36,6 +36,7 @@ dev,chalk,MIT,Copyright Sindre Sorhus
36
36
  dev,checksum,MIT,Copyright Daniel D. Shaw
37
37
  dev,cli-table3,MIT,Copyright 2014 James Talmage
38
38
  dev,dotenv,BSD-2-Clause,Copyright 2015 Scott Motte
39
+ dev,esbuild,MIT,Copyright (c) 2020 Evan Wallace
39
40
  dev,eslint,MIT,Copyright JS Foundation and other contributors https://js.foundation
40
41
  dev,eslint-config-standard,MIT,Copyright Feross Aboukhadijeh
41
42
  dev,eslint-plugin-import,MIT,Copyright 2015 Ben Mosher
package/README.md CHANGED
@@ -151,6 +151,19 @@ $ yarn lint
151
151
  ```
152
152
 
153
153
 
154
+ ### Experimental ESM Support
155
+
156
+ ESM support is currently in the experimental stages, while CJS has been supported
157
+ since inception. This means that code loaded using `require()` should work fine
158
+ but code loaded using `import` might not always work.
159
+
160
+ Use the following command to enable experimental ESM support with your application:
161
+
162
+ ```sh
163
+ node --loader dd-trace/loader-hook.mjs entrypoint.js
164
+ ```
165
+
166
+
154
167
  ### Benchmarks
155
168
 
156
169
  Our microbenchmarks live in `benchmark/sirun`. Each directory in there
@@ -175,6 +188,42 @@ That said, even if your application runs on Lambda, any core instrumentation iss
175
188
  Regardless of where you open the issue, someone at Datadog will try to help.
176
189
 
177
190
 
191
+ ## Bundling
192
+
193
+ Generally, `dd-trace` works by intercepting `require()` calls that a Node.js application makes when loading modules. This includes modules that are built-in to Node.js, like the `fs` module for accessing the filesystem, as well as modules installed from the npm registry, like the `pg` database module.
194
+
195
+ Also generally, bundlers work by crawling all of the `require()` calls that an application makes to files on disk, replacing the `require()` calls with custom code, and then concatenating all of the resulting JavaScript into one "bundled" file. When a built-in module is loaded, like `require('fs')`, that call can then remain the same in the resulting bundle.
196
+
197
+ Fundamentally APM tools like `dd-trace` stop working at this point. Perhaps they continue to intercept the calls for built-in modules but don't intercept calls to third party libraries. This means that by default when you bundle a `dd-trace` app with a bundler it is likely to capture information about disk access (via `fs`) and outbound HTTP requests (via `http`), but will otherwise omit calls to third party libraries (like extracting incoming request route information for the `express` framework or showing which query is run for the `mysql` database client).
198
+
199
+ To get around this, one can treat all third party modules, or at least third party modules that the APM needs to instrument, as being "external" to the bundler. With this setting the instrumented modules remain on disk and continue to be loaded via `require()` while the non-instrumented modules are bundled. Sadly this results in a build with many extraneous files and starts to defeat the purpose of bundling.
200
+
201
+ For these reasons it's necessary to have custom-built bundler plugins. Such plugins are able to instruct the bundler on how to behave, injecting intermediary code and otherwise intercepting the "translated" `require()` calls. The result is that many more packages are then included in the bundled JavaScript file. Some applications can have 100% of modules bundled, however native modules still need to remain external to the bundle.
202
+
203
+ ### Esbuild Support
204
+
205
+ This library provides experimental esbuild support in the form of an esbuild plugin, and currently requires at least Node.js v16.17 or v18.7. To use the plugin, make sure you have `dd-trace@3+` installed, and then require the `dd-trace/esbuild` module when building your bundle.
206
+
207
+ Here's an example of how one might use `dd-trace` with esbuild:
208
+
209
+ ```javascript
210
+ const ddPlugin = require('dd-trace/esbuild')
211
+ const esbuild = require('esbuild')
212
+
213
+ esbuild.build({
214
+ entryPoints: ['app.js'],
215
+ bundle: true,
216
+ outfile: 'out.js',
217
+ plugins: [ddPlugin],
218
+ platform: 'node', // allows built-in modules to be required
219
+ target: ['node16']
220
+ }).catch((err) => {
221
+ console.error(err)
222
+ process.exit(1)
223
+ })
224
+ ```
225
+
226
+
178
227
  ## Security Vulnerabilities
179
228
 
180
229
  If you have found a security issue, please contact the security team directly at [security@datadoghq.com](mailto:security@datadoghq.com).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "3.16.0",
3
+ "version": "3.17.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -32,7 +32,10 @@
32
32
  "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js",
33
33
  "test:profiler": "tap \"packages/dd-trace/test/profiling/**/*.spec.js\"",
34
34
  "test:profiler:ci": "npm run test:profiler -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/profiling/**/*.js\"",
35
- "test:integration": "mocha --colors --timeout 30000 \"integration-tests/**/*.spec.js\"",
35
+ "test:integration": "mocha --colors --timeout 30000 \"integration-tests/*.spec.js\"",
36
+ "test:integration:cucumber": "mocha --colors --timeout 30000 \"integration-tests/cucumber/*.spec.js\"",
37
+ "test:integration:cypress": "mocha --colors --timeout 30000 \"integration-tests/cypress/*.spec.js\"",
38
+ "test:integration:playwright": "mocha --colors --timeout 30000 \"integration-tests/playwright/*.spec.js\"",
36
39
  "test:shimmer": "mocha --colors 'packages/datadog-shimmer/test/**/*.spec.js'",
37
40
  "test:shimmer:ci": "nyc --no-clean --include 'packages/datadog-shimmer/src/**/*.js' -- npm run test:shimmer",
38
41
  "leak:core": "node ./scripts/install_plugin_modules && (cd packages/memwatch && yarn) && NODE_PATH=./packages/memwatch/node_modules node --no-warnings ./node_modules/.bin/tape 'packages/dd-trace/test/leak/**/*.js'",
@@ -64,7 +67,7 @@
64
67
  "dependencies": {
65
68
  "@datadog/native-appsec": "2.0.0",
66
69
  "@datadog/native-iast-rewriter": "2.0.1",
67
- "@datadog/native-iast-taint-tracking": "1.1.1",
70
+ "@datadog/native-iast-taint-tracking": "1.3.1",
68
71
  "@datadog/native-metrics": "^1.5.0",
69
72
  "@datadog/pprof": "^2.1.0",
70
73
  "@datadog/sketches-js": "^2.1.0",
@@ -101,6 +104,7 @@
101
104
  "checksum": "^0.1.1",
102
105
  "cli-table3": "^0.5.1",
103
106
  "dotenv": "8.2.0",
107
+ "esbuild": "0.16.12",
104
108
  "eslint": "^8.23.0",
105
109
  "eslint-config-standard": "^11.0.0-beta.0",
106
110
  "eslint-plugin-import": "^2.8.0",
@@ -0,0 +1,104 @@
1
+ 'use strict'
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ const NAMESPACE = 'datadog'
6
+
7
+ const instrumented = Object.keys(require('../datadog-instrumentations/src/helpers/hooks.js'))
8
+ const rawBuiltins = require('module').builtinModules
9
+
10
+ warnIfUnsupported()
11
+
12
+ const builtins = new Set()
13
+
14
+ for (const builtin of rawBuiltins) {
15
+ builtins.add(builtin)
16
+ builtins.add(`node:${builtin}`)
17
+ }
18
+
19
+ const packagesOfInterest = new Set()
20
+
21
+ const DEBUG = !!process.env.DD_TRACE_DEBUG
22
+
23
+ // We don't want to handle any built-in packages via DCITM
24
+ // Those packages will still be handled via RITM
25
+ // Attempting to instrument them would fail as they have no package.json file
26
+ for (const pkg of instrumented) {
27
+ if (builtins.has(pkg)) continue
28
+ if (pkg.startsWith('node:')) continue
29
+ packagesOfInterest.add(pkg)
30
+ }
31
+
32
+ const DC_CHANNEL = 'dd-trace:bundledModuleLoadStart'
33
+
34
+ module.exports.name = 'datadog-esbuild'
35
+
36
+ module.exports.setup = function (build) {
37
+ build.onResolve({ filter: /.*/ }, args => {
38
+ const packageName = args.path
39
+
40
+ if (args.namespace === 'file' && packagesOfInterest.has(packageName)) {
41
+ // The file namespace is used when requiring files from disk in userland
42
+ const pathToPackageJson = require.resolve(`${packageName}/package.json`, { paths: [ args.resolveDir ] })
43
+ const pkg = require(pathToPackageJson)
44
+
45
+ if (DEBUG) {
46
+ console.log(`resolve ${packageName}@${pkg.version}`)
47
+ }
48
+
49
+ // https://esbuild.github.io/plugins/#on-resolve-arguments
50
+ return {
51
+ path: packageName,
52
+ namespace: NAMESPACE,
53
+ pluginData: {
54
+ version: pkg.version
55
+ }
56
+ }
57
+ } else if (args.namespace === 'datadog') {
58
+ // The datadog namespace is used when requiring files that are injected during the onLoad stage
59
+ // see note in onLoad
60
+
61
+ if (builtins.has(packageName)) return
62
+
63
+ return {
64
+ path: require.resolve(packageName, { paths: [ args.resolveDir ] }),
65
+ namespace: 'file'
66
+ }
67
+ }
68
+ })
69
+
70
+ build.onLoad({ filter: /.*/, namespace: NAMESPACE }, args => {
71
+ if (DEBUG) {
72
+ console.log(`load ${args.path}@${args.pluginData.version}`)
73
+ }
74
+
75
+ // JSON.stringify adds double quotes. For perf gain could simply add in quotes when we know it's safe.
76
+ const contents = `
77
+ const dc = require('diagnostics_channel');
78
+ const ch = dc.channel(${JSON.stringify(DC_CHANNEL + ':' + args.path)});
79
+ const mod = require(${JSON.stringify(args.path)});
80
+ const payload = {
81
+ module: mod,
82
+ path: ${JSON.stringify(args.path)},
83
+ version: ${JSON.stringify(args.pluginData.version)}
84
+ };
85
+ ch.publish(payload);
86
+ module.exports = payload.module;
87
+ `
88
+ // https://esbuild.github.io/plugins/#on-load-results
89
+ return {
90
+ contents,
91
+ loader: 'js'
92
+ }
93
+ })
94
+ }
95
+
96
+ function warnIfUnsupported () {
97
+ const [major, minor] = process.versions.node.split('.').map(Number)
98
+ if (major < 14 || (major === 14 && minor < 17)) {
99
+ console.error('WARNING: Esbuild support isn\'t available for older versions of Node.js.')
100
+ console.error(`Expected: Node.js >= v14.17. Actual: Node.js = ${process.version}.`)
101
+ console.error('This application may build properly with this version of Node.js, but unless a')
102
+ console.error('more recent version is used at runtime, third party packages won\'t be instrumented.')
103
+ }
104
+ }
@@ -63,7 +63,7 @@ function wrapMethod (method) {
63
63
  const api = method.name
64
64
 
65
65
  return function (request) {
66
- if (!requestStartCh.hasSubscribers) return request.apply(this, arguments)
66
+ if (!requestStartCh.hasSubscribers) return method.apply(this, arguments)
67
67
 
68
68
  const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
69
69
 
@@ -3,13 +3,21 @@
3
3
  const path = require('path')
4
4
  const iitm = require('../../../dd-trace/src/iitm')
5
5
  const ritm = require('../../../dd-trace/src/ritm')
6
-
6
+ const dcitm = require('../../../dd-trace/src/dcitm')
7
+
8
+ /**
9
+ * This is called for every module that dd-trace supports instrumentation for.
10
+ * In practice, `modules` is always an array with a single entry.
11
+ *
12
+ * @param {string[]} modules list of modules to hook into
13
+ * @param {Function} onrequire callback to be executed upon encountering module
14
+ */
7
15
  function Hook (modules, onrequire) {
8
16
  if (!(this instanceof Hook)) return new Hook(modules, onrequire)
9
17
 
10
18
  this._patched = Object.create(null)
11
19
 
12
- const safeHook = (moduleExports, moduleName, moduleBaseDir) => {
20
+ const safeHook = (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
13
21
  const parts = [moduleBaseDir, moduleName].filter(v => v)
14
22
  const filename = path.join(...parts)
15
23
 
@@ -17,7 +25,7 @@ function Hook (modules, onrequire) {
17
25
 
18
26
  this._patched[filename] = true
19
27
 
20
- return onrequire(moduleExports, moduleName, moduleBaseDir)
28
+ return onrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion)
21
29
  }
22
30
 
23
31
  this._ritmHook = ritm(modules, {}, safeHook)
@@ -33,11 +41,13 @@ function Hook (modules, onrequire) {
33
41
  return safeHook(moduleExports, moduleName, moduleBaseDir)
34
42
  }
35
43
  })
44
+ this._dcitmHook = dcitm(modules, {}, safeHook)
36
45
  }
37
46
 
38
47
  Hook.prototype.unhook = function () {
39
48
  this._ritmHook.unhook()
40
49
  this._iitmHook.unhook()
50
+ this._dcitmHook.unhook()
41
51
  this._patched = Object.create(null)
42
52
  }
43
53
 
@@ -14,6 +14,12 @@ exports.channel = function (name) {
14
14
  return ch
15
15
  }
16
16
 
17
+ /**
18
+ * @param {string} args.name module name
19
+ * @param {string[]} args.versions array of semver range strings
20
+ * @param {string} args.file path to file within package to instrument?
21
+ * @param Function hook
22
+ */
17
23
  exports.addHook = function addHook ({ name, versions, file }, hook) {
18
24
  if (!instrumentations[name]) {
19
25
  instrumentations[name] = []
@@ -17,7 +17,7 @@ const loadChannel = channel('dd-trace:instrumentation:load')
17
17
  // TODO: make this more efficient
18
18
 
19
19
  for (const packageName of names) {
20
- Hook([packageName], (moduleExports, moduleName, moduleBaseDir) => {
20
+ Hook([packageName], (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
21
21
  moduleName = moduleName.replace(pathSepExpr, '/')
22
22
 
23
23
  hooks[packageName]()
@@ -26,7 +26,7 @@ for (const packageName of names) {
26
26
  const fullFilename = filename(name, file)
27
27
 
28
28
  if (moduleName === fullFilename) {
29
- const version = getVersion(moduleBaseDir)
29
+ const version = moduleVersion || getVersion(moduleBaseDir)
30
30
 
31
31
  if (matchVersion(version, versions)) {
32
32
  try {
@@ -27,27 +27,22 @@ function wrapQuery (query) {
27
27
  return query.apply(this, arguments)
28
28
  }
29
29
 
30
- const retval = query.apply(this, arguments)
31
-
32
- const queryQueue = this.queryQueue || this._queryQueue
33
- const activeQuery = this.activeQuery || this._activeQuery
34
- const pgQuery = queryQueue[queryQueue.length - 1] || activeQuery
35
-
36
- if (!pgQuery) {
37
- return retval
38
- }
39
-
40
30
  const callbackResource = new AsyncResource('bound-anonymous-fn')
41
31
  const asyncResource = new AsyncResource('bound-anonymous-fn')
42
32
  const processId = this.processID
33
+ let pgQuery = {
34
+ text: arguments[0]
35
+ }
36
+
43
37
  return asyncResource.runInAsyncScope(() => {
44
38
  startCh.publish({
45
39
  params: this.connectionParameters,
46
- originalQuery: pgQuery.text,
47
40
  query: pgQuery,
48
41
  processId
49
42
  })
50
43
 
44
+ arguments[0] = pgQuery.text
45
+
51
46
  const finish = asyncResource.bind(function (error) {
52
47
  if (error) {
53
48
  errorCh.publish(error)
@@ -55,6 +50,16 @@ function wrapQuery (query) {
55
50
  finishCh.publish()
56
51
  })
57
52
 
53
+ const retval = query.apply(this, arguments)
54
+ const queryQueue = this.queryQueue || this._queryQueue
55
+ const activeQuery = this.activeQuery || this._activeQuery
56
+
57
+ pgQuery = queryQueue[queryQueue.length - 1] || activeQuery
58
+
59
+ if (!pgQuery) {
60
+ return retval
61
+ }
62
+
58
63
  if (pgQuery.callback) {
59
64
  const originalCallback = callbackResource.bind(pgQuery.callback)
60
65
  pgQuery.callback = function (err, res) {
@@ -1,4 +1,7 @@
1
- <!-- Sorry, you’ve been blocked -->
1
+ /* eslint-disable max-len */
2
+ 'use strict'
3
+
4
+ const html = `<!-- Sorry, you've been blocked -->
2
5
  <!DOCTYPE html>
3
6
  <html lang="en">
4
7
 
@@ -97,3 +100,18 @@
97
100
  </body>
98
101
 
99
102
  </html>
103
+ `
104
+
105
+ const json = `{
106
+ "errors": [
107
+ {
108
+ "title": "You've been blocked",
109
+ "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."
110
+ }
111
+ ]
112
+ }`
113
+
114
+ module.exports = {
115
+ html,
116
+ json
117
+ }
@@ -1,12 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../log')
4
- const fs = require('fs')
4
+ const blockedTemplates = require('./blocked_templates')
5
5
 
6
- // TODO: move template loading to a proper spot.
7
- let templateLoaded = false
8
- let templateHtml = ''
9
- let templateJson = ''
6
+ let templateHtml = blockedTemplates.html
7
+ let templateJson = blockedTemplates.json
10
8
 
11
9
  function block (req, res, rootSpan, abortController) {
12
10
  if (res.headersSent) {
@@ -42,29 +40,16 @@ function block (req, res, rootSpan, abortController) {
42
40
  }
43
41
  }
44
42
 
45
- function loadTemplates (config) {
46
- if (!templateLoaded) {
47
- templateHtml = fs.readFileSync(config.appsec.blockedTemplateHtml)
48
- templateJson = fs.readFileSync(config.appsec.blockedTemplateJson)
49
- templateLoaded = true
43
+ function setTemplates (config) {
44
+ if (config.appsec.blockedTemplateHtml) {
45
+ templateHtml = config.appsec.blockedTemplateHtml
50
46
  }
51
- }
52
-
53
- async function loadTemplatesAsync (config) {
54
- if (!templateLoaded) {
55
- templateHtml = await fs.promises.readFile(config.appsec.blockedTemplateHtml)
56
- templateJson = await fs.promises.readFile(config.appsec.blockedTemplateJson)
57
- templateLoaded = true
47
+ if (config.appsec.blockedTemplateJson) {
48
+ templateJson = config.appsec.blockedTemplateJson
58
49
  }
59
50
  }
60
51
 
61
- function resetTemplates () {
62
- templateLoaded = false
63
- }
64
-
65
52
  module.exports = {
66
53
  block,
67
- loadTemplates,
68
- loadTemplatesAsync,
69
- resetTemplates
54
+ setTemplates
70
55
  }
@@ -1,4 +1,6 @@
1
1
  'use strict'
2
+
3
+ const path = require('path')
2
4
  const { getIastContext } = require('../iast-context')
3
5
  const { storage } = require('../../../../../datadog-core')
4
6
  const InjectionAnalyzer = require('./injection-analyzer')
@@ -37,6 +39,16 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
37
39
  }
38
40
  this.analyze(pathArguments)
39
41
  })
42
+
43
+ this.exclusionList = [ path.join('node_modules', 'send') + path.sep ]
44
+ }
45
+
46
+ _isExcluded (location) {
47
+ let ret = false
48
+ if (location && location.path) {
49
+ ret = this.exclusionList.some(elem => location.path.includes(elem))
50
+ }
51
+ return ret
40
52
  }
41
53
 
42
54
  analyze (value) {
@@ -6,7 +6,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
6
6
  super('SQL_INJECTION')
7
7
  this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
8
8
  this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
9
- this.addSub('apm:pg:query:start', ({ originalQuery }) => this.analyze(originalQuery))
9
+ this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text))
10
10
  }
11
11
  }
12
12
 
@@ -32,12 +32,18 @@ class Analyzer extends Plugin {
32
32
  return false
33
33
  }
34
34
 
35
+ _isExcluded (location) {
36
+ return false
37
+ }
38
+
35
39
  _report (value, context) {
36
40
  const evidence = this._getEvidence(value, context)
37
41
  const location = this._getLocation()
38
- const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
39
- const vulnerability = createVulnerability(this._type, evidence, spanId, location)
40
- addVulnerability(context, vulnerability)
42
+ if (!this._isExcluded(location)) {
43
+ const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
44
+ const vulnerability = createVulnerability(this._type, evidence, spanId, location)
45
+ addVulnerability(context, vulnerability)
46
+ }
41
47
  }
42
48
 
43
49
  _reportIfVulnerable (value, context) {
@@ -6,6 +6,7 @@ const overheadController = require('./overhead-controller')
6
6
  const dc = require('diagnostics_channel')
7
7
  const iastContextFunctions = require('./iast-context')
8
8
  const { enableTaintTracking, disableTaintTracking, createTransaction, removeTransaction } = require('./taint-tracking')
9
+
9
10
  const telemetryLogs = require('./telemetry/logs')
10
11
  const IAST_ENABLED_TAG_KEY = '_dd.iast.enabled'
11
12
 
@@ -16,7 +17,7 @@ const requestClose = dc.channel('dd-trace:incomingHttpRequestEnd')
16
17
 
17
18
  function enable (config, _tracer) {
18
19
  enableAllAnalyzers()
19
- enableTaintTracking()
20
+ enableTaintTracking(config.iast)
20
21
  requestStart.subscribe(onIncomingHttpRequestStart)
21
22
  requestClose.subscribe(onIncomingHttpRequestEnd)
22
23
  overheadController.configure(config.iast)
@@ -1,20 +1,27 @@
1
1
  'use strict'
2
2
 
3
3
  const { enableRewriter, disableRewriter } = require('./rewriter')
4
- const { createTransaction, removeTransaction, enableTaintOperations, disableTaintOperations } = require('./operations')
4
+ const { createTransaction,
5
+ removeTransaction,
6
+ setMaxTransactions,
7
+ enableTaintOperations,
8
+ disableTaintOperations } = require('./operations')
9
+
5
10
  const taintTrackingPlugin = require('./plugin')
6
11
 
7
12
  module.exports = {
8
- enableTaintTracking () {
13
+ enableTaintTracking (config) {
9
14
  enableRewriter()
10
15
  enableTaintOperations()
11
16
  taintTrackingPlugin.enable()
17
+ setMaxTransactions(config.maxConcurrentRequests)
12
18
  },
13
19
  disableTaintTracking () {
14
20
  disableRewriter()
15
21
  disableTaintOperations()
16
22
  taintTrackingPlugin.disable()
17
23
  },
24
+ setMaxTransactions: setMaxTransactions,
18
25
  createTransaction: createTransaction,
19
26
  removeTransaction: removeTransaction
20
27
  }
@@ -87,6 +87,14 @@ function disableTaintOperations () {
87
87
  global._ddiast = TaintTrackingDummy
88
88
  }
89
89
 
90
+ function setMaxTransactions (transactions) {
91
+ if (!transactions) {
92
+ return
93
+ }
94
+
95
+ TaintedUtils.setMaxTransactions(transactions)
96
+ }
97
+
90
98
  module.exports = {
91
99
  createTransaction,
92
100
  removeTransaction,
@@ -96,5 +104,6 @@ module.exports = {
96
104
  getRanges,
97
105
  enableTaintOperations,
98
106
  disableTaintOperations,
107
+ setMaxTransactions,
99
108
  IAST_TRANSACTION_ID
100
109
  }
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const fs = require('fs')
4
- const path = require('path')
5
3
  const log = require('../log')
6
4
  const RuleManager = require('./rule_manager')
7
5
  const remoteConfig = require('./remote_config')
@@ -12,7 +10,7 @@ const Reporter = require('./reporter')
12
10
  const web = require('../plugins/util/web')
13
11
  const { extractIp } = require('../plugins/util/ip_extractor')
14
12
  const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
15
- const { block, loadTemplates, loadTemplatesAsync } = require('./blocking')
13
+ const { block, setTemplates } = require('./blocking')
16
14
 
17
15
  let isEnabled = false
18
16
  let config
@@ -21,21 +19,10 @@ function enable (_config) {
21
19
  if (isEnabled) return
22
20
 
23
21
  try {
24
- loadTemplates(_config)
25
- const rules = fs.readFileSync(_config.appsec.rules || path.join(__dirname, 'recommended.json'))
26
- enableFromRules(_config, JSON.parse(rules))
27
- } catch (err) {
28
- abortEnable(err)
29
- }
30
- }
22
+ setTemplates(_config)
31
23
 
32
- async function enableAsync (_config) {
33
- if (isEnabled) return
34
-
35
- try {
36
- await loadTemplatesAsync(_config)
37
- const rules = await fs.promises.readFile(_config.appsec.rules || path.join(__dirname, 'recommended.json'))
38
- enableFromRules(_config, JSON.parse(rules))
24
+ // TODO: inline this function
25
+ enableFromRules(_config, _config.appsec.rules)
39
26
  } catch (err) {
40
27
  abortEnable(err)
41
28
  }
@@ -169,7 +156,6 @@ function disable () {
169
156
 
170
157
  module.exports = {
171
158
  enable,
172
- enableAsync,
173
159
  disable,
174
160
  incomingHttpStartTranslator,
175
161
  incomingHttpEndTranslator
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "2.2",
3
3
  "metadata": {
4
- "rules_version": "1.5.2"
4
+ "rules_version": "1.6.0"
5
5
  },
6
6
  "rules": [
7
7
  {
@@ -2907,7 +2907,8 @@
2907
2907
  }
2908
2908
  ],
2909
2909
  "transformers": [
2910
- "removeNulls"
2910
+ "removeNulls",
2911
+ "urlDecodeUni"
2911
2912
  ]
2912
2913
  },
2913
2914
  {
@@ -2957,7 +2958,8 @@
2957
2958
  }
2958
2959
  ],
2959
2960
  "transformers": [
2960
- "removeNulls"
2961
+ "removeNulls",
2962
+ "urlDecodeUni"
2961
2963
  ]
2962
2964
  },
2963
2965
  {
@@ -3007,7 +3009,8 @@
3007
3009
  }
3008
3010
  ],
3009
3011
  "transformers": [
3010
- "removeNulls"
3012
+ "removeNulls",
3013
+ "urlDecodeUni"
3011
3014
  ]
3012
3015
  },
3013
3016
  {
@@ -3054,7 +3057,8 @@
3054
3057
  }
3055
3058
  ],
3056
3059
  "transformers": [
3057
- "removeNulls"
3060
+ "removeNulls",
3061
+ "urlDecodeUni"
3058
3062
  ]
3059
3063
  },
3060
3064
  {
@@ -3088,8 +3092,7 @@
3088
3092
  ".parentnode",
3089
3093
  ".innerhtml",
3090
3094
  "window.location",
3091
- "-moz-binding",
3092
- "<![cdata["
3095
+ "-moz-binding"
3093
3096
  ]
3094
3097
  },
3095
3098
  "operator": "phrase_match"
@@ -3545,7 +3548,7 @@
3545
3548
  "address": "grpc.server.request.message"
3546
3549
  }
3547
3550
  ],
3548
- "regex": "\\b(?i:eval|settimeout|setinterval|new\\s+Function|alert|prompt)\\s*\\([^\\)]",
3551
+ "regex": "\\b(?i:eval|settimeout|setinterval|new\\s+Function|alert|prompt)[\\s+]*\\([^\\)]",
3549
3552
  "options": {
3550
3553
  "case_sensitive": true,
3551
3554
  "min_length": 5
@@ -5347,14 +5350,12 @@
5347
5350
  "address": "grpc.server.request.message"
5348
5351
  }
5349
5352
  ],
5350
- "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com)"
5353
+ "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com|vii.one|act1on3.ru)"
5351
5354
  },
5352
5355
  "operator": "match_regex"
5353
5356
  }
5354
5357
  ],
5355
- "transformers": [
5356
- "lowercase"
5357
- ]
5358
+ "transformers": []
5358
5359
  },
5359
5360
  {
5360
5361
  "id": "sqr-000-015",
@@ -5429,7 +5430,9 @@
5429
5430
  "operator": "match_regex"
5430
5431
  }
5431
5432
  ],
5432
- "transformers": []
5433
+ "transformers": [
5434
+ "unicode_normalize"
5435
+ ]
5433
5436
  },
5434
5437
  {
5435
5438
  "id": "ua0-600-0xx",
@@ -5905,7 +5908,7 @@
5905
5908
  "tags": {
5906
5909
  "type": "security_scanner",
5907
5910
  "category": "attack_attempt",
5908
- "confidence": "1"
5911
+ "confidence": "0"
5909
5912
  },
5910
5913
  "conditions": [
5911
5914
  {
@@ -6616,6 +6619,32 @@
6616
6619
  "block"
6617
6620
  ]
6618
6621
  },
6622
+ {
6623
+ "id": "ua0-600-57x",
6624
+ "name": "AlertLogic",
6625
+ "tags": {
6626
+ "type": "security_scanner",
6627
+ "category": "attack_attempt",
6628
+ "confidence": "0"
6629
+ },
6630
+ "conditions": [
6631
+ {
6632
+ "parameters": {
6633
+ "inputs": [
6634
+ {
6635
+ "address": "server.request.headers.no_cookies",
6636
+ "key_path": [
6637
+ "user-agent"
6638
+ ]
6639
+ }
6640
+ ],
6641
+ "regex": "\\bAlertLogic-MDR-"
6642
+ },
6643
+ "operator": "match_regex"
6644
+ }
6645
+ ],
6646
+ "transformers": []
6647
+ },
6619
6648
  {
6620
6649
  "id": "ua0-600-5xx",
6621
6650
  "name": "Blind SQL Injection Brute Forcer",
@@ -23,7 +23,7 @@ function enable (config) {
23
23
  }
24
24
 
25
25
  if (shouldEnable) {
26
- require('..').enableAsync(config).catch(() => {})
26
+ require('..').enable(config)
27
27
  } else {
28
28
  require('..').disable()
29
29
  }
@@ -2,14 +2,14 @@
2
2
 
3
3
  const { trackUserLoginSuccessEvent, trackUserLoginFailureEvent, trackCustomEvent } = require('./track_event')
4
4
  const { checkUserAndSetUser, blockRequest } = require('./user_blocking')
5
- const { loadTemplates } = require('../blocking')
5
+ const { setTemplates } = require('../blocking')
6
6
  const { setUser } = require('./set_user')
7
7
 
8
8
  class AppsecSdk {
9
9
  constructor (tracer, config) {
10
10
  this._tracer = tracer
11
11
  if (config) {
12
- loadTemplates(config)
12
+ setTemplates(config)
13
13
  }
14
14
  }
15
15
 
@@ -9,7 +9,6 @@ const coalesce = require('koalas')
9
9
  const tagger = require('./tagger')
10
10
  const { isTrue, isFalse } = require('./util')
11
11
  const uuid = require('crypto-randomuuid')
12
- const path = require('path')
13
12
 
14
13
  const fromEntries = Object.fromEntries || (entries =>
15
14
  entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
@@ -22,16 +21,7 @@ function maybeFile (filepath) {
22
21
  try {
23
22
  return fs.readFileSync(filepath, 'utf8')
24
23
  } catch (e) {
25
- return undefined
26
- }
27
- }
28
-
29
- function maybePath (filepath) {
30
- if (!filepath) return
31
- try {
32
- fs.openSync(filepath, 'r')
33
- return filepath
34
- } catch (e) {
24
+ log.error(e)
35
25
  return undefined
36
26
  }
37
27
  }
@@ -282,6 +272,18 @@ class Config {
282
272
  false
283
273
  )
284
274
 
275
+ const DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED = coalesce(
276
+ options.traceId128BitGenerationEnabled,
277
+ process.env.DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED,
278
+ false
279
+ )
280
+
281
+ const DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED = coalesce(
282
+ options.traceId128BitLoggingEnabled,
283
+ process.env.DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED,
284
+ false
285
+ )
286
+
285
287
  let appsec = options.appsec != null ? options.appsec : options.experimental && options.experimental.appsec
286
288
 
287
289
  if (typeof appsec === 'boolean') {
@@ -326,14 +328,12 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
326
328
  |[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}`
327
329
  )
328
330
  const DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML = coalesce(
329
- maybePath(appsec.blockedTemplateHtml),
330
- maybePath(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML),
331
- path.join(__dirname, 'appsec', 'templates', 'blocked.html')
331
+ maybeFile(appsec.blockedTemplateHtml),
332
+ maybeFile(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML)
332
333
  )
333
334
  const DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON = coalesce(
334
- maybePath(appsec.blockedTemplateJson),
335
- maybePath(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON),
336
- path.join(__dirname, 'appsec', 'templates', 'blocked.json')
335
+ maybeFile(appsec.blockedTemplateJson),
336
+ maybeFile(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON)
337
337
  )
338
338
 
339
339
  const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
@@ -479,7 +479,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
479
479
  this.tagsHeaderMaxLength = parseInt(DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH)
480
480
  this.appsec = {
481
481
  enabled: DD_APPSEC_ENABLED,
482
- rules: DD_APPSEC_RULES,
482
+ rules: DD_APPSEC_RULES ? safeJsonParse(maybeFile(DD_APPSEC_RULES)) : require('./appsec/recommended.json'),
483
483
  rateLimit: DD_APPSEC_TRACE_RATE_LIMIT,
484
484
  wafTimeout: DD_APPSEC_WAF_TIMEOUT,
485
485
  obfuscatorKeyRegex: DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
@@ -509,6 +509,9 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
509
509
  enabled: isTrue(DD_TRACE_STATS_COMPUTATION_ENABLED)
510
510
  }
511
511
 
512
+ this.traceId128BitGenerationEnabled = isTrue(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
513
+ this.traceId128BitLoggingEnabled = isTrue(DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
514
+
512
515
  tagger.add(this.tags, {
513
516
  service: this.service,
514
517
  env: this.env,
@@ -16,13 +16,13 @@ function encodeVarint (v) {
16
16
  // decodes positive and negative numbers, using zig zag encoding to reduce the size of the variable length encoding.
17
17
  // uses high and low part to ensure those parts are under the limit for byte operations in javascript (32 bits)
18
18
  function decodeVarint (b) {
19
- const [low, high] = decodeUvarint64(b)
19
+ const [low, high, bytes] = decodeUvarint64(b)
20
20
  if (low === undefined || high === undefined) {
21
- return undefined
21
+ return [undefined, bytes]
22
22
  }
23
23
  const positive = (low & 1) === 0
24
24
  const abs = (low >>> 1) + high * 0x80000000
25
- return positive ? abs : -abs
25
+ return [positive ? abs : -abs, bytes]
26
26
  }
27
27
 
28
28
  const maxVarLen64 = 9
@@ -50,7 +50,7 @@ function decodeUvarint64 (
50
50
  let s = 0
51
51
  for (let i = 0; ; i++) {
52
52
  if (bytes.length <= i) {
53
- return [undefined, undefined]
53
+ return [undefined, undefined, bytes.slice(bytes.length)]
54
54
  }
55
55
  const n = bytes[i]
56
56
  if (n < 0x80 || i === maxVarLen64 - 1) {
@@ -61,7 +61,7 @@ function decodeUvarint64 (
61
61
  if (s > 0) {
62
62
  high |= s - 32 > 0 ? n << (s - 32) : n >> (32 - s)
63
63
  }
64
- return [low, high]
64
+ return [low, high, bytes]
65
65
  }
66
66
  if (s < 32) {
67
67
  low |= (n & 0x7f) << s
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const dc = require('diagnostics_channel')
4
+
5
+ const CHANNEL_PREFIX = 'dd-trace:bundledModuleLoadStart'
6
+
7
+ if (!dc.subscribe) {
8
+ dc.subscribe = (channel, cb) => {
9
+ dc.channel(channel).subscribe(cb)
10
+ }
11
+ }
12
+ if (!dc.unsubscribe) {
13
+ dc.unsubscribe = (channel, cb) => {
14
+ if (dc.channel(channel).hasSubscribers) {
15
+ dc.channel(channel).unsubscribe(cb)
16
+ }
17
+ }
18
+ }
19
+
20
+ module.exports = DcitmHook
21
+
22
+ /**
23
+ * This allows for listening to diagnostic channel events when a module is loaded.
24
+ * Currently it's intended use is for situations like when code runs through a bundler.
25
+ *
26
+ * Unlike RITM and IITM, which have files available on a filesystem at runtime, DCITM
27
+ * requires access to a package's version ahead of time as the package.json file likely
28
+ * won't be available.
29
+ *
30
+ * This function runs many times at startup, once for every module that dd-trace may trace.
31
+ * As it runs on a per-module basis we're creating per-module channels.
32
+ */
33
+ function DcitmHook (moduleNames, options, onrequire) {
34
+ if (!(this instanceof DcitmHook)) return new DcitmHook(moduleNames, options, onrequire)
35
+
36
+ function onModuleLoad (payload) {
37
+ payload.module = onrequire(payload.module, payload.path, undefined, payload.version)
38
+ }
39
+
40
+ for (const moduleName of moduleNames) {
41
+ // dc.channel(`${CHANNEL_PREFIX}:${moduleName}`).subscribe(onModuleLoad)
42
+ dc.subscribe(`${CHANNEL_PREFIX}:${moduleName}`, onModuleLoad)
43
+ }
44
+
45
+ this.unhook = function dcitmUnload () {
46
+ for (const moduleName of moduleNames) {
47
+ // dc.channel(`${CHANNEL_PREFIX}:${moduleName}`).unsubscribe(onModuleLoad)
48
+ dc.unsubscribe(`${CHANNEL_PREFIX}:${moduleName}`, onModuleLoad)
49
+ }
50
+ }
51
+ }
@@ -14,7 +14,12 @@ class LogPropagator {
14
14
  carrier.dd = {}
15
15
 
16
16
  if (spanContext) {
17
- carrier.dd.trace_id = spanContext.toTraceId()
17
+ if (this._config.traceId128BitLoggingEnabled && spanContext._trace.tags['_dd.p.tid']) {
18
+ carrier.dd.trace_id = spanContext._trace.tags['_dd.p.tid'] + spanContext._traceId.toString(16)
19
+ } else {
20
+ carrier.dd.trace_id = spanContext.toTraceId()
21
+ }
22
+
18
23
  carrier.dd.span_id = spanContext.toSpanId()
19
24
  }
20
25
 
@@ -28,12 +33,23 @@ class LogPropagator {
28
33
  return null
29
34
  }
30
35
 
31
- const spanContext = new DatadogSpanContext({
32
- traceId: id(carrier.dd.trace_id, 10),
33
- spanId: id(carrier.dd.span_id, 10)
34
- })
35
-
36
- return spanContext
36
+ if (carrier.dd.trace_id.length === 32) {
37
+ const hi = carrier.dd.trace_id.substring(0, 16)
38
+ const lo = carrier.dd.trace_id.substring(16, 32)
39
+ const spanContext = new DatadogSpanContext({
40
+ traceId: id(lo, 16),
41
+ spanId: id(carrier.dd.span_id, 10)
42
+ })
43
+
44
+ spanContext._trace.tags['_dd.p.tid'] = hi
45
+
46
+ return spanContext
47
+ } else {
48
+ return new DatadogSpanContext({
49
+ traceId: id(carrier.dd.trace_id, 10),
50
+ spanId: id(carrier.dd.span_id, 10)
51
+ })
52
+ }
37
53
  }
38
54
  }
39
55
 
@@ -132,7 +132,7 @@ class TextMapPropagator {
132
132
  const hasB3multi = this._hasPropagationStyle('inject', 'b3multi')
133
133
  if (!(hasB3 || hasB3multi)) return
134
134
 
135
- carrier[b3TraceKey] = spanContext._traceId.toString(16)
135
+ carrier[b3TraceKey] = this._getB3TraceId(spanContext)
136
136
  carrier[b3SpanKey] = spanContext._spanId.toString(16)
137
137
  carrier[b3SampledKey] = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
138
138
 
@@ -149,7 +149,7 @@ class TextMapPropagator {
149
149
  const hasB3SingleHeader = this._hasPropagationStyle('inject', 'b3 single header')
150
150
  if (!hasB3SingleHeader) return null
151
151
 
152
- const traceId = spanContext._traceId.toString(16)
152
+ const traceId = this._getB3TraceId(spanContext)
153
153
  const spanId = spanContext._spanId.toString(16)
154
154
  const sampled = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
155
155
 
@@ -277,6 +277,8 @@ class TextMapPropagator {
277
277
  spanContext._sampling.priority = priority
278
278
  }
279
279
 
280
+ this._extract128BitTraceId(b3[b3TraceKey], spanContext)
281
+
280
282
  return spanContext
281
283
  }
282
284
 
@@ -321,6 +323,8 @@ class TextMapPropagator {
321
323
  tracestate
322
324
  })
323
325
 
326
+ this._extract128BitTraceId(traceId, spanContext)
327
+
324
328
  tracestate.forVendor('dd', state => {
325
329
  for (const [key, value] of state.entries()) {
326
330
  switch (key) {
@@ -484,6 +488,20 @@ class TextMapPropagator {
484
488
  }
485
489
  }
486
490
 
491
+ _extract128BitTraceId (traceId, spanContext) {
492
+ if (!spanContext) return
493
+
494
+ const buffer = spanContext._traceId.toBuffer()
495
+
496
+ if (buffer.length !== 16) return
497
+
498
+ const tid = traceId.substring(0, 16)
499
+
500
+ if (tid === '0000000000000000') return
501
+
502
+ spanContext._trace.tags['_dd.p.tid'] = tid
503
+ }
504
+
487
505
  _validateTagKey (key) {
488
506
  return tagKeyExpr.test(key)
489
507
  }
@@ -501,6 +519,14 @@ class TextMapPropagator {
501
519
  return AUTO_REJECT
502
520
  }
503
521
  }
522
+
523
+ _getB3TraceId (spanContext) {
524
+ if (spanContext._traceId.toBuffer().length <= 8 && spanContext._trace.tags['_dd.p.tid']) {
525
+ return spanContext._trace.tags['_dd.p.tid'] + spanContext._traceId.toString(16)
526
+ }
527
+
528
+ return spanContext._traceId.toString(16)
529
+ }
504
530
  }
505
531
 
506
532
  module.exports = TextMapPropagator
@@ -39,7 +39,7 @@ class DatadogSpan {
39
39
  // This is necessary for span count metrics.
40
40
  this._name = operationName
41
41
 
42
- this._spanContext = this._createContext(parent)
42
+ this._spanContext = this._createContext(parent, fields)
43
43
  this._spanContext._name = operationName
44
44
  this._spanContext._tags = tags
45
45
  this._spanContext._hostname = hostname
@@ -145,7 +145,7 @@ class DatadogSpan {
145
145
  this._processor.process(this)
146
146
  }
147
147
 
148
- _createContext (parent) {
148
+ _createContext (parent, fields) {
149
149
  let spanContext
150
150
 
151
151
  if (parent) {
@@ -158,16 +158,27 @@ class DatadogSpan {
158
158
  trace: parent._trace,
159
159
  tracestate: parent._tracestate
160
160
  })
161
+
162
+ if (!spanContext._trace.startTime) {
163
+ spanContext._trace.startTime = dateNow()
164
+ }
161
165
  } else {
162
166
  const spanId = id()
167
+ const startTime = dateNow()
163
168
  spanContext = new SpanContext({
164
169
  traceId: spanId,
165
170
  spanId
166
171
  })
172
+ spanContext._trace.startTime = startTime
173
+
174
+ if (fields.traceId128BitGenerationEnabled) {
175
+ spanContext._trace.tags['_dd.p.tid'] = Math.floor(startTime / 1000).toString(16)
176
+ .padStart(8, '0')
177
+ .padEnd(16, '0')
178
+ }
167
179
  }
168
180
 
169
181
  spanContext._trace.started.push(this)
170
- spanContext._trace.startTime = spanContext._trace.startTime || dateNow()
171
182
  spanContext._trace.ticks = spanContext._trace.ticks || now()
172
183
 
173
184
  return spanContext
@@ -34,7 +34,9 @@ class DatadogSpanContext {
34
34
 
35
35
  toTraceparent () {
36
36
  const flags = this._sampling.priority >= AUTO_KEEP ? '01' : '00'
37
- const traceId = this._traceId.toString(16).padStart(32, '0')
37
+ const traceId = this._traceId.toBuffer().length <= 8 && this._trace.tags['_dd.p.tid']
38
+ ? this._trace.tags['_dd.p.tid'] + this._traceId.toString(16).padStart(16, '0')
39
+ : this._traceId.toString(16).padStart(32, '0')
38
40
  const spanId = this._spanId.toString(16).padStart(16, '0')
39
41
  const version = (this._traceparent && this._traceparent.version) || '00'
40
42
  return `${version}-${traceId}-${spanId}-${flags}`
@@ -33,6 +33,7 @@ class DatadogTracer {
33
33
  this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
34
34
  this._url = this._exporter._url
35
35
  this._enableGetRumData = config.experimental.enableGetRumData
36
+ this._traceId128BitGenerationEnabled = config.traceId128BitGenerationEnabled
36
37
  this._propagators = {
37
38
  [formats.TEXT_MAP]: new TextMapPropagator(config),
38
39
  [formats.HTTP_HEADERS]: new HttpPropagator(config),
@@ -58,7 +59,8 @@ class DatadogTracer {
58
59
  parent,
59
60
  tags,
60
61
  startTime: options.startTime,
61
- hostname: this._hostname
62
+ hostname: this._hostname,
63
+ traceId128BitGenerationEnabled: this._traceId128BitGenerationEnabled
62
64
  }, this._debug)
63
65
 
64
66
  span.addTags(this._tags)
@@ -1,8 +0,0 @@
1
- {
2
- "errors": [
3
- {
4
- "title": "You've been blocked",
5
- "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."
6
- }
7
- ]
8
- }