dd-trace 3.16.0 → 3.17.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.
- package/LICENSE-3rdparty.csv +1 -0
- package/README.md +49 -0
- package/package.json +8 -4
- package/packages/datadog-esbuild/index.js +104 -0
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hook.js +13 -3
- package/packages/datadog-instrumentations/src/helpers/instrument.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +16 -11
- package/packages/datadog-plugin-next/src/index.js +6 -0
- package/packages/dd-trace/src/appsec/{templates/blocked.html → blocked_templates.js} +19 -1
- package/packages/dd-trace/src/appsec/blocking.js +9 -24
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +12 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +9 -3
- package/packages/dd-trace/src/appsec/iast/index.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +9 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +9 -0
- package/packages/dd-trace/src/appsec/index.js +4 -18
- package/packages/dd-trace/src/appsec/recommended.json +43 -14
- package/packages/dd-trace/src/appsec/remote_config/index.js +1 -1
- package/packages/dd-trace/src/appsec/sdk/index.js +2 -2
- package/packages/dd-trace/src/config.js +21 -18
- package/packages/dd-trace/src/datastreams/encoding.js +5 -5
- package/packages/dd-trace/src/dcitm.js +51 -0
- package/packages/dd-trace/src/lambda/handler.js +30 -6
- package/packages/dd-trace/src/opentracing/propagation/log.js +23 -7
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +28 -2
- package/packages/dd-trace/src/opentracing/span.js +14 -3
- package/packages/dd-trace/src/opentracing/span_context.js +3 -1
- package/packages/dd-trace/src/opentracing/tracer.js +3 -1
- package/scripts/install_plugin_modules.js +2 -0
- package/packages/dd-trace/src/appsec/templates/blocked.json +0 -8
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "3.17.1",
|
|
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
|
|
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.
|
|
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",
|
|
@@ -88,7 +91,7 @@
|
|
|
88
91
|
"path-to-regexp": "^0.1.2",
|
|
89
92
|
"protobufjs": "^7.1.2",
|
|
90
93
|
"retry": "^0.10.1",
|
|
91
|
-
"semver": "^
|
|
94
|
+
"semver": "^7.3.8"
|
|
92
95
|
},
|
|
93
96
|
"devDependencies": {
|
|
94
97
|
"@types/node": ">=14",
|
|
@@ -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
|
|
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) {
|
|
@@ -68,6 +68,12 @@ class NextPlugin extends Plugin {
|
|
|
68
68
|
const span = store.span
|
|
69
69
|
const req = this._requests.get(span)
|
|
70
70
|
|
|
71
|
+
// Only use error page names if there's not already a name
|
|
72
|
+
const current = span.context()._tags['next.page']
|
|
73
|
+
if (current && (page === '/404' || page === '/500' || page === '/_error')) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
span.addTags({
|
|
72
78
|
[COMPONENT]: this.constructor.id,
|
|
73
79
|
'resource.name': `${req.method} ${page}`.trim(),
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
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
|
|
4
|
+
const blockedTemplates = require('./blocked_templates')
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
let
|
|
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
|
|
46
|
-
if (
|
|
47
|
-
templateHtml =
|
|
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
|
-
|
|
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', ({
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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.
|
|
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": "
|
|
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",
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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
|
-
|
|
335
|
-
|
|
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
|
+
}
|
|
@@ -50,7 +50,7 @@ function crashFlush () {
|
|
|
50
50
|
[ERROR_TYPE]: error.name
|
|
51
51
|
})
|
|
52
52
|
} else {
|
|
53
|
-
log.
|
|
53
|
+
log.debug('An impending timeout was reached, but no root span was found. No error will be tagged.')
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
tracer._processor.killAll()
|
|
@@ -59,6 +59,23 @@ function crashFlush () {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Extracts the context from the given Lambda handler arguments.
|
|
64
|
+
*
|
|
65
|
+
* @param {*} args any amount of arguments
|
|
66
|
+
* @returns the context, if extraction was succesful.
|
|
67
|
+
*/
|
|
68
|
+
function extractContext (args) {
|
|
69
|
+
let context = args.length > 1 ? args[1] : undefined
|
|
70
|
+
if (context === undefined || context.getRemainingTimeInMillis === undefined) {
|
|
71
|
+
context = args.length > 2 ? args[2] : undefined
|
|
72
|
+
if (context === undefined || context.getRemainingTimeInMillis === undefined) {
|
|
73
|
+
throw Error('Could not extract context')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return context
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
/**
|
|
63
80
|
* Patches your AWS Lambda handler function to add some tracing support.
|
|
64
81
|
*
|
|
@@ -66,14 +83,21 @@ function crashFlush () {
|
|
|
66
83
|
*/
|
|
67
84
|
exports.datadog = function datadog (lambdaHandler) {
|
|
68
85
|
return (...args) => {
|
|
69
|
-
const context = args[1]
|
|
70
86
|
const patched = lambdaHandler.apply(this, args)
|
|
71
|
-
checkTimeout(context)
|
|
72
87
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
try {
|
|
89
|
+
const context = extractContext(args)
|
|
90
|
+
|
|
91
|
+
checkTimeout(context)
|
|
92
|
+
|
|
93
|
+
if (patched) {
|
|
94
|
+
// clear the timeout as soon as a result is returned
|
|
95
|
+
patched.then(_ => clearTimeout(__lambdaTimeout))
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {
|
|
98
|
+
log.debug('Error patching AWS Lambda handler. Timeout spans will not be generated.')
|
|
76
99
|
}
|
|
100
|
+
|
|
77
101
|
return patched
|
|
78
102
|
}
|
|
79
103
|
}
|
|
@@ -14,7 +14,12 @@ class LogPropagator {
|
|
|
14
14
|
carrier.dd = {}
|
|
15
15
|
|
|
16
16
|
if (spanContext) {
|
|
17
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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] =
|
|
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 =
|
|
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.
|
|
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)
|
|
@@ -89,6 +89,8 @@ async function assertInstrumentation (instrumentation, external) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
async function assertModules (name, version, external) {
|
|
92
|
+
const range = process.env.RANGE
|
|
93
|
+
if (range && !semver.subset(version, range)) return
|
|
92
94
|
addFolder(name)
|
|
93
95
|
addFolder(name, version)
|
|
94
96
|
assertFolder(name)
|