dd-trace 4.18.0 → 4.23.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.
- package/LICENSE-3rdparty.csv +3 -2
- package/README.md +3 -3
- package/ext/kinds.d.ts +1 -0
- package/ext/kinds.js +2 -1
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +6 -1
- package/index.d.ts +29 -0
- package/package.json +12 -11
- package/packages/datadog-core/src/storage/async_resource.js +1 -1
- package/packages/datadog-esbuild/index.js +1 -20
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
- package/packages/datadog-instrumentations/src/child-process.js +4 -5
- package/packages/datadog-instrumentations/src/couchbase.js +5 -4
- package/packages/datadog-instrumentations/src/crypto.js +2 -1
- package/packages/datadog-instrumentations/src/dns.js +2 -1
- package/packages/datadog-instrumentations/src/graphql.js +18 -4
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +10 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
- package/packages/datadog-instrumentations/src/http/client.js +12 -2
- package/packages/datadog-instrumentations/src/http/server.js +7 -4
- package/packages/datadog-instrumentations/src/http2/client.js +3 -1
- package/packages/datadog-instrumentations/src/http2/server.js +3 -1
- package/packages/datadog-instrumentations/src/jest.js +12 -6
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/net.js +10 -2
- package/packages/datadog-instrumentations/src/next.js +18 -6
- package/packages/datadog-instrumentations/src/restify.js +14 -1
- package/packages/datadog-instrumentations/src/rhea.js +15 -9
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-cucumber/src/index.js +34 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
- package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-jest/src/index.js +38 -4
- package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
- package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
- package/packages/datadog-plugin-mocha/src/index.js +32 -1
- package/packages/datadog-plugin-next/src/index.js +40 -14
- package/packages/datadog-plugin-playwright/src/index.js +17 -1
- package/packages/dd-trace/src/appsec/activation.js +29 -0
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
- package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
- package/packages/dd-trace/src/appsec/blocking.js +95 -43
- package/packages/dd-trace/src/appsec/channels.js +5 -2
- package/packages/dd-trace/src/appsec/graphql.js +146 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +33 -32
- package/packages/dd-trace/src/appsec/recommended.json +1737 -120
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +40 -15
- package/packages/dd-trace/src/appsec/reporter.js +50 -34
- package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +110 -59
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +40 -7
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
- package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
- package/packages/dd-trace/src/config.js +145 -63
- package/packages/dd-trace/src/datastreams/processor.js +166 -26
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
- package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
- package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
- package/packages/dd-trace/src/format.js +6 -1
- package/packages/dd-trace/src/id.js +12 -0
- package/packages/dd-trace/src/iitm.js +1 -1
- package/packages/dd-trace/src/log/channels.js +1 -1
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentelemetry/span.js +95 -2
- package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
- package/packages/dd-trace/src/opentracing/span.js +6 -0
- package/packages/dd-trace/src/opentracing/span_context.js +5 -2
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugin_manager.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +46 -9
- package/packages/dd-trace/src/plugins/database.js +1 -1
- package/packages/dd-trace/src/plugins/index.js +6 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/ci.js +6 -19
- package/packages/dd-trace/src/plugins/util/exec.js +23 -2
- package/packages/dd-trace/src/plugins/util/git.js +98 -22
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
- package/packages/dd-trace/src/plugins/util/test.js +3 -2
- package/packages/dd-trace/src/plugins/util/url.js +26 -0
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
- package/packages/dd-trace/src/priority_sampler.js +30 -38
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +26 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
- package/packages/dd-trace/src/profiling/profiler.js +17 -10
- package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
- package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
- package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
- package/packages/dd-trace/src/proxy.js +25 -1
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/sampling_rule.js +130 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
- package/packages/dd-trace/src/span_processor.js +4 -0
- package/packages/dd-trace/src/span_sampler.js +6 -64
- package/packages/dd-trace/src/spanleak.js +98 -0
- package/packages/dd-trace/src/startup-log.js +7 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
- package/packages/dd-trace/src/telemetry/index.js +171 -41
- package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +47 -5
- package/packages/dd-trace/src/tracer.js +8 -2
- package/scripts/install_plugin_modules.js +11 -3
- package/packages/diagnostics_channel/index.js +0 -3
- package/packages/diagnostics_channel/src/index.js +0 -121
|
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
|
6
6
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
7
7
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
8
8
|
|
|
9
|
+
const errorPages = ['/404', '/500', '/_error', '/_not-found']
|
|
10
|
+
|
|
9
11
|
class NextPlugin extends ServerPlugin {
|
|
10
12
|
static get id () {
|
|
11
13
|
return 'next'
|
|
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
error ({ span, error }) {
|
|
45
|
+
if (!span) {
|
|
46
|
+
const store = storage.getStore()
|
|
47
|
+
if (!store) return
|
|
48
|
+
|
|
49
|
+
span = store.span
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
this.addError(error, span)
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
|
|
|
50
59
|
|
|
51
60
|
const span = store.span
|
|
52
61
|
const error = span.context()._tags['error']
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
const requestError = req.error || nextRequest.error
|
|
63
|
+
|
|
64
|
+
if (requestError) {
|
|
65
|
+
// prioritize user-set errors from API routes
|
|
66
|
+
span.setTag('error', requestError)
|
|
67
|
+
web.addError(req, requestError)
|
|
68
|
+
} else if (error) {
|
|
69
|
+
// general error handling
|
|
70
|
+
span.setTag('error', error)
|
|
71
|
+
web.addError(req, requestError || error)
|
|
72
|
+
} else if (!this.config.validateStatus(res.statusCode)) {
|
|
73
|
+
// where there's no error, we still need to validate status
|
|
74
|
+
span.setTag('error', true)
|
|
75
|
+
web.addError(req, true)
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
span.addTags({
|
|
@@ -65,7 +84,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
65
84
|
span.finish()
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
pageLoad ({ page, isAppPath = false }) {
|
|
87
|
+
pageLoad ({ page, isAppPath = false, isStatic = false }) {
|
|
69
88
|
const store = storage.getStore()
|
|
70
89
|
|
|
71
90
|
if (!store) return
|
|
@@ -73,21 +92,28 @@ class NextPlugin extends ServerPlugin {
|
|
|
73
92
|
const span = store.span
|
|
74
93
|
const req = this._requests.get(span)
|
|
75
94
|
|
|
95
|
+
// safeguard against missing req in complicated timeout scenarios
|
|
96
|
+
if (!req) return
|
|
97
|
+
|
|
76
98
|
// Only use error page names if there's not already a name
|
|
77
99
|
const current = span.context()._tags['next.page']
|
|
78
|
-
|
|
100
|
+
const isErrorPage = errorPages.includes(page)
|
|
101
|
+
|
|
102
|
+
if (current && isErrorPage) {
|
|
79
103
|
return
|
|
80
104
|
}
|
|
81
105
|
|
|
82
106
|
// remove ending /route or /page for appDir projects
|
|
83
|
-
if
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
// need to check if not an error page too, as those are marked as app directory
|
|
108
|
+
// in newer versions
|
|
109
|
+
if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
|
|
110
|
+
|
|
111
|
+
// handle static resource
|
|
112
|
+
if (isStatic) {
|
|
113
|
+
page = req.url.includes('_next/static')
|
|
114
|
+
? '/_next/static/*'
|
|
115
|
+
: '/public/*'
|
|
116
|
+
}
|
|
91
117
|
|
|
92
118
|
span.addTags({
|
|
93
119
|
[COMPONENT]: this.constructor.id,
|
|
@@ -8,10 +8,15 @@ const {
|
|
|
8
8
|
finishAllTraceSpans,
|
|
9
9
|
getTestSuitePath,
|
|
10
10
|
getTestSuiteCommonTags,
|
|
11
|
-
TEST_SOURCE_START
|
|
11
|
+
TEST_SOURCE_START,
|
|
12
|
+
TEST_CODE_OWNERS
|
|
12
13
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
13
14
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
14
15
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
16
|
+
const {
|
|
17
|
+
TELEMETRY_EVENT_CREATED,
|
|
18
|
+
TELEMETRY_EVENT_FINISHED
|
|
19
|
+
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
15
20
|
|
|
16
21
|
class PlaywrightPlugin extends CiPlugin {
|
|
17
22
|
static get id () {
|
|
@@ -28,7 +33,9 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
28
33
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
29
34
|
|
|
30
35
|
this.testModuleSpan.finish()
|
|
36
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
31
37
|
this.testSessionSpan.finish()
|
|
38
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
32
39
|
finishAllTraceSpans(this.testSessionSpan)
|
|
33
40
|
this.tracer._exporter.flush(onDone)
|
|
34
41
|
})
|
|
@@ -52,6 +59,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
52
59
|
...testSuiteMetadata
|
|
53
60
|
}
|
|
54
61
|
})
|
|
62
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
55
63
|
this.enter(testSuiteSpan, store)
|
|
56
64
|
|
|
57
65
|
this._testSuites.set(testSuite, testSuiteSpan)
|
|
@@ -63,6 +71,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
63
71
|
if (!span) return
|
|
64
72
|
span.setTag(TEST_STATUS, status)
|
|
65
73
|
span.finish()
|
|
74
|
+
this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
66
75
|
})
|
|
67
76
|
|
|
68
77
|
this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
|
|
@@ -104,6 +113,13 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
104
113
|
})
|
|
105
114
|
|
|
106
115
|
span.finish()
|
|
116
|
+
|
|
117
|
+
this.telemetry.ciVisEvent(
|
|
118
|
+
TELEMETRY_EVENT_FINISHED,
|
|
119
|
+
'test',
|
|
120
|
+
{ hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
|
|
121
|
+
)
|
|
122
|
+
|
|
107
123
|
finishAllTraceSpans(span)
|
|
108
124
|
})
|
|
109
125
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Activation = {
|
|
4
|
+
ONECLICK: 'OneClick',
|
|
5
|
+
ENABLED: 'Enabled',
|
|
6
|
+
DISABLED: 'Disabled',
|
|
7
|
+
|
|
8
|
+
fromConfig (config) {
|
|
9
|
+
switch (config.appsec.enabled) {
|
|
10
|
+
// ASM is activated by an env var DD_APPSEC_ENABLED=true
|
|
11
|
+
case true:
|
|
12
|
+
return Activation.ENABLED
|
|
13
|
+
|
|
14
|
+
// ASM is disabled by an env var DD_APPSEC_ENABLED=false
|
|
15
|
+
case false:
|
|
16
|
+
return Activation.DISABLED
|
|
17
|
+
|
|
18
|
+
// ASM is activated by one click remote config
|
|
19
|
+
case undefined:
|
|
20
|
+
return Activation.ONECLICK
|
|
21
|
+
|
|
22
|
+
// Any other value should never occur
|
|
23
|
+
default:
|
|
24
|
+
return Activation.DISABLED
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = Activation
|
|
@@ -13,8 +13,10 @@ module.exports = {
|
|
|
13
13
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
14
14
|
// TODO: 'server.response.trailers',
|
|
15
15
|
HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
|
|
16
|
+
HTTP_INCOMING_GRAPHQL_RESOLVER: 'graphql.server.resolver',
|
|
16
17
|
|
|
17
18
|
HTTP_CLIENT_IP: 'http.client_ip',
|
|
18
19
|
|
|
19
|
-
USER_ID: 'usr.id'
|
|
20
|
+
USER_ID: 'usr.id',
|
|
21
|
+
WAF_CONTEXT_PROCESSOR: 'waf.context.processor'
|
|
20
22
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
5
|
+
let enabled
|
|
6
|
+
let requestSampling
|
|
7
|
+
|
|
8
|
+
function configure ({ apiSecurity }) {
|
|
9
|
+
enabled = apiSecurity.enabled
|
|
10
|
+
setRequestSampling(apiSecurity.requestSampling)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function disable () {
|
|
14
|
+
enabled = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setRequestSampling (sampling) {
|
|
18
|
+
requestSampling = parseRequestSampling(sampling)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseRequestSampling (requestSampling) {
|
|
22
|
+
let parsed = parseFloat(requestSampling)
|
|
23
|
+
|
|
24
|
+
if (isNaN(parsed)) {
|
|
25
|
+
log.warn(`Incorrect API Security request sampling value: ${requestSampling}`)
|
|
26
|
+
|
|
27
|
+
parsed = 0
|
|
28
|
+
} else {
|
|
29
|
+
parsed = Math.min(1, Math.max(0, parsed))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return parsed
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sampleRequest () {
|
|
36
|
+
if (!enabled || !requestSampling) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Math.random() <= requestSampling
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
configure,
|
|
45
|
+
disable,
|
|
46
|
+
setRequestSampling,
|
|
47
|
+
sampleRequest
|
|
48
|
+
}
|
|
@@ -5,7 +5,10 @@ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta n
|
|
|
5
5
|
|
|
6
6
|
const json = `{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
|
|
7
7
|
|
|
8
|
+
const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}`
|
|
9
|
+
|
|
8
10
|
module.exports = {
|
|
9
11
|
html,
|
|
10
|
-
json
|
|
12
|
+
json,
|
|
13
|
+
graphqlJson
|
|
11
14
|
}
|
|
@@ -3,93 +3,142 @@
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const blockedTemplates = require('./blocked_templates')
|
|
5
5
|
|
|
6
|
+
const detectedSpecificEndpoints = {}
|
|
7
|
+
|
|
6
8
|
let templateHtml = blockedTemplates.html
|
|
7
9
|
let templateJson = blockedTemplates.json
|
|
10
|
+
let templateGraphqlJson = blockedTemplates.graphqlJson
|
|
8
11
|
let blockingConfiguration
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})
|
|
13
|
+
const specificBlockingTypes = {
|
|
14
|
+
GRAPHQL: 'graphql'
|
|
15
|
+
}
|
|
14
16
|
|
|
17
|
+
function getSpecificKey (method, url) {
|
|
18
|
+
return `${method}+${url}`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function addSpecificEndpoint (method, url, type) {
|
|
22
|
+
detectedSpecificEndpoints[getSpecificKey(method, url)] = type
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getBlockWithRedirectData (rootSpan) {
|
|
15
26
|
let statusCode = blockingConfiguration.parameters.status_code
|
|
16
27
|
if (!statusCode || statusCode < 300 || statusCode >= 400) {
|
|
17
28
|
statusCode = 303
|
|
18
29
|
}
|
|
19
|
-
|
|
20
|
-
res.writeHead(statusCode, {
|
|
30
|
+
const headers = {
|
|
21
31
|
'Location': blockingConfiguration.parameters.location
|
|
22
|
-
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
rootSpan.addTags({
|
|
35
|
+
'appsec.blocked': 'true'
|
|
36
|
+
})
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
return { headers, statusCode }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getSpecificBlockingData (type) {
|
|
42
|
+
switch (type) {
|
|
43
|
+
case specificBlockingTypes.GRAPHQL:
|
|
44
|
+
return {
|
|
45
|
+
type: 'application/json',
|
|
46
|
+
body: templateGraphqlJson
|
|
47
|
+
}
|
|
26
48
|
}
|
|
27
49
|
}
|
|
28
50
|
|
|
29
|
-
function
|
|
51
|
+
function getBlockWithContentData (req, specificType, rootSpan) {
|
|
30
52
|
let type
|
|
31
53
|
let body
|
|
54
|
+
let statusCode
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
const specificBlockingType = specificType || detectedSpecificEndpoints[getSpecificKey(req.method, req.url)]
|
|
57
|
+
if (specificBlockingType) {
|
|
58
|
+
const specificBlockingContent = getSpecificBlockingData(specificBlockingType)
|
|
59
|
+
type = specificBlockingContent?.type
|
|
60
|
+
body = specificBlockingContent?.body
|
|
61
|
+
}
|
|
35
62
|
|
|
36
|
-
if (!
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
if (!type) {
|
|
64
|
+
// parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
|
|
65
|
+
const accept = req.headers.accept?.split(',').map((str) => str.split(';', 1)[0].trim())
|
|
66
|
+
|
|
67
|
+
if (!blockingConfiguration || blockingConfiguration.parameters.type === 'auto') {
|
|
68
|
+
if (accept?.includes('text/html') && !accept.includes('application/json')) {
|
|
69
|
+
type = 'text/html; charset=utf-8'
|
|
70
|
+
body = templateHtml
|
|
71
|
+
} else {
|
|
72
|
+
type = 'application/json'
|
|
73
|
+
body = templateJson
|
|
74
|
+
}
|
|
40
75
|
} else {
|
|
41
|
-
type
|
|
42
|
-
|
|
76
|
+
if (blockingConfiguration.parameters.type === 'html') {
|
|
77
|
+
type = 'text/html; charset=utf-8'
|
|
78
|
+
body = templateHtml
|
|
79
|
+
} else {
|
|
80
|
+
type = 'application/json'
|
|
81
|
+
body = templateJson
|
|
82
|
+
}
|
|
43
83
|
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (blockingConfiguration?.type === 'block_request' && blockingConfiguration.parameters.status_code) {
|
|
87
|
+
statusCode = blockingConfiguration.parameters.status_code
|
|
44
88
|
} else {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
89
|
+
statusCode = 403
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const headers = {
|
|
93
|
+
'Content-Type': type,
|
|
94
|
+
'Content-Length': Buffer.byteLength(body)
|
|
52
95
|
}
|
|
53
96
|
|
|
54
97
|
rootSpan.addTags({
|
|
55
98
|
'appsec.blocked': 'true'
|
|
56
99
|
})
|
|
57
100
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
res.statusCode = blockingConfiguration.parameters.status_code
|
|
61
|
-
} else {
|
|
62
|
-
res.statusCode = 403
|
|
63
|
-
}
|
|
64
|
-
res.setHeader('Content-Type', type)
|
|
65
|
-
res.setHeader('Content-Length', Buffer.byteLength(body))
|
|
66
|
-
res.end(body)
|
|
101
|
+
return { body, statusCode, headers }
|
|
102
|
+
}
|
|
67
103
|
|
|
68
|
-
|
|
69
|
-
|
|
104
|
+
function getBlockingData (req, specificType, rootSpan) {
|
|
105
|
+
if (blockingConfiguration?.type === 'redirect_request' && blockingConfiguration.parameters.location) {
|
|
106
|
+
return getBlockWithRedirectData(rootSpan)
|
|
107
|
+
} else {
|
|
108
|
+
return getBlockWithContentData(req, specificType, rootSpan)
|
|
70
109
|
}
|
|
71
110
|
}
|
|
72
111
|
|
|
73
|
-
function block (req, res, rootSpan, abortController) {
|
|
112
|
+
function block (req, res, rootSpan, abortController, type) {
|
|
74
113
|
if (res.headersSent) {
|
|
75
114
|
log.warn('Cannot send blocking response when headers have already been sent')
|
|
76
115
|
return
|
|
77
116
|
}
|
|
78
117
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
118
|
+
const { body, headers, statusCode } = getBlockingData(req, type, rootSpan)
|
|
119
|
+
|
|
120
|
+
res.writeHead(statusCode, headers).end(body)
|
|
121
|
+
|
|
122
|
+
abortController?.abort()
|
|
85
123
|
}
|
|
86
124
|
|
|
87
125
|
function setTemplates (config) {
|
|
88
126
|
if (config.appsec.blockedTemplateHtml) {
|
|
89
127
|
templateHtml = config.appsec.blockedTemplateHtml
|
|
128
|
+
} else {
|
|
129
|
+
templateHtml = blockedTemplates.html
|
|
90
130
|
}
|
|
131
|
+
|
|
91
132
|
if (config.appsec.blockedTemplateJson) {
|
|
92
133
|
templateJson = config.appsec.blockedTemplateJson
|
|
134
|
+
} else {
|
|
135
|
+
templateJson = blockedTemplates.json
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (config.appsec.blockedTemplateGraphql) {
|
|
139
|
+
templateGraphqlJson = config.appsec.blockedTemplateGraphql
|
|
140
|
+
} else {
|
|
141
|
+
templateGraphqlJson = blockedTemplates.graphqlJson
|
|
93
142
|
}
|
|
94
143
|
}
|
|
95
144
|
|
|
@@ -98,7 +147,10 @@ function updateBlockingConfiguration (newBlockingConfiguration) {
|
|
|
98
147
|
}
|
|
99
148
|
|
|
100
149
|
module.exports = {
|
|
150
|
+
addSpecificEndpoint,
|
|
101
151
|
block,
|
|
152
|
+
specificBlockingTypes,
|
|
153
|
+
getBlockingData,
|
|
102
154
|
setTemplates,
|
|
103
155
|
updateBlockingConfiguration
|
|
104
156
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const dc = require('
|
|
3
|
+
const dc = require('dc-polyfill')
|
|
4
4
|
|
|
5
5
|
// TODO: use TBD naming convention
|
|
6
6
|
module.exports = {
|
|
7
7
|
bodyParser: dc.channel('datadog:body-parser:read:finish'),
|
|
8
8
|
cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
|
|
9
|
-
|
|
9
|
+
startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
|
|
10
|
+
graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
|
|
11
|
+
apolloChannel: dc.tracingChannel('datadog:apollo:request'),
|
|
12
|
+
apolloServerCoreChannel: dc.tracingChannel('datadog:apollo-server-core:request'),
|
|
10
13
|
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
|
|
11
14
|
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
12
15
|
passportVerify: dc.channel('datadog:passport:verify:finish'),
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../../datadog-core')
|
|
4
|
+
const { addSpecificEndpoint, specificBlockingTypes, getBlockingData } = require('./blocking')
|
|
5
|
+
const waf = require('./waf')
|
|
6
|
+
const addresses = require('./addresses')
|
|
7
|
+
const web = require('../plugins/util/web')
|
|
8
|
+
const {
|
|
9
|
+
startGraphqlResolve,
|
|
10
|
+
graphqlMiddlewareChannel,
|
|
11
|
+
apolloChannel,
|
|
12
|
+
apolloServerCoreChannel
|
|
13
|
+
} = require('./channels')
|
|
14
|
+
|
|
15
|
+
const graphqlRequestData = new WeakMap()
|
|
16
|
+
|
|
17
|
+
function enable () {
|
|
18
|
+
enableApollo()
|
|
19
|
+
enableGraphql()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function disable () {
|
|
23
|
+
disableApollo()
|
|
24
|
+
disableGraphql()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onGraphqlStartResolve ({ context, resolverInfo }) {
|
|
28
|
+
const req = storage.getStore()?.req
|
|
29
|
+
|
|
30
|
+
if (!req) return
|
|
31
|
+
|
|
32
|
+
if (!resolverInfo || typeof resolverInfo !== 'object') return
|
|
33
|
+
|
|
34
|
+
const actions = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
|
|
35
|
+
if (actions?.includes('block')) {
|
|
36
|
+
const requestData = graphqlRequestData.get(req)
|
|
37
|
+
if (requestData?.isInGraphqlRequest) {
|
|
38
|
+
requestData.blocked = true
|
|
39
|
+
context?.abortController?.abort()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function enterInApolloMiddleware (data) {
|
|
45
|
+
const req = data?.req || storage.getStore()?.req
|
|
46
|
+
if (!req) return
|
|
47
|
+
|
|
48
|
+
graphqlRequestData.set(req, {
|
|
49
|
+
inApolloMiddleware: true,
|
|
50
|
+
blocked: false
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function enterInApolloServerCoreRequest () {
|
|
55
|
+
const req = storage.getStore()?.req
|
|
56
|
+
if (!req) return
|
|
57
|
+
|
|
58
|
+
graphqlRequestData.set(req, {
|
|
59
|
+
isInGraphqlRequest: true,
|
|
60
|
+
blocked: false
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function exitFromApolloMiddleware (data) {
|
|
65
|
+
const req = data?.req || storage.getStore()?.req
|
|
66
|
+
const requestData = graphqlRequestData.get(req)
|
|
67
|
+
if (requestData) requestData.inApolloMiddleware = false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function enterInApolloRequest () {
|
|
71
|
+
const req = storage.getStore()?.req
|
|
72
|
+
|
|
73
|
+
const requestData = graphqlRequestData.get(req)
|
|
74
|
+
if (requestData?.inApolloMiddleware) {
|
|
75
|
+
requestData.isInGraphqlRequest = true
|
|
76
|
+
addSpecificEndpoint(req.method, req.originalUrl || req.url, specificBlockingTypes.GRAPHQL)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
|
|
81
|
+
const req = storage.getStore()?.req
|
|
82
|
+
if (!req) return
|
|
83
|
+
|
|
84
|
+
const requestData = graphqlRequestData.get(req)
|
|
85
|
+
|
|
86
|
+
if (requestData?.blocked) {
|
|
87
|
+
const rootSpan = web.root(req)
|
|
88
|
+
if (!rootSpan) return
|
|
89
|
+
|
|
90
|
+
const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, rootSpan)
|
|
91
|
+
abortData.statusCode = blockingData.statusCode
|
|
92
|
+
abortData.headers = blockingData.headers
|
|
93
|
+
abortData.message = blockingData.body
|
|
94
|
+
|
|
95
|
+
abortController?.abort()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
graphqlRequestData.delete(req)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function enableApollo () {
|
|
102
|
+
graphqlMiddlewareChannel.subscribe({
|
|
103
|
+
start: enterInApolloMiddleware,
|
|
104
|
+
end: exitFromApolloMiddleware
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
apolloServerCoreChannel.subscribe({
|
|
108
|
+
start: enterInApolloServerCoreRequest,
|
|
109
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
apolloChannel.subscribe({
|
|
113
|
+
start: enterInApolloRequest,
|
|
114
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function disableApollo () {
|
|
119
|
+
graphqlMiddlewareChannel.unsubscribe({
|
|
120
|
+
start: enterInApolloMiddleware,
|
|
121
|
+
end: exitFromApolloMiddleware
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
apolloServerCoreChannel.unsubscribe({
|
|
125
|
+
start: enterInApolloServerCoreRequest,
|
|
126
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
apolloChannel.unsubscribe({
|
|
130
|
+
start: enterInApolloRequest,
|
|
131
|
+
asyncEnd: beforeWriteApolloGraphqlResponse
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function enableGraphql () {
|
|
136
|
+
startGraphqlResolve.subscribe(onGraphqlStartResolve)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function disableGraphql () {
|
|
140
|
+
if (startGraphqlResolve.hasSubscribers) startGraphqlResolve.unsubscribe(onGraphqlStartResolve)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
enable,
|
|
145
|
+
disable
|
|
146
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
|
|
5
5
|
'HARCODED_SECRET_ANALYZER': require('./hardcoded-secret-analyzer'),
|
|
6
|
+
'HEADER_INJECTION_ANALYZER': require('./header-injection-analyzer'),
|
|
6
7
|
'HSTS_HEADER_MISSING_ANALYZER': require('./hsts-header-missing-analyzer'),
|
|
7
8
|
'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
|
|
8
9
|
'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
|