dd-trace 4.47.1 → 4.48.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 +0 -1
- package/ext/types.d.ts +1 -0
- package/ext/types.js +1 -0
- package/index.d.ts +26 -0
- package/package.json +6 -7
- package/packages/datadog-code-origin/index.js +38 -0
- package/packages/datadog-core/index.js +2 -2
- package/packages/datadog-instrumentations/src/avsc.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/child_process.js +17 -8
- package/packages/datadog-instrumentations/src/express.js +37 -4
- package/packages/datadog-instrumentations/src/fastify.js +12 -1
- package/packages/datadog-instrumentations/src/fs.js +27 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mysql2.js +220 -1
- package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
- package/packages/datadog-instrumentations/src/winston.js +22 -0
- package/packages/datadog-plugin-avsc/src/index.js +9 -0
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
- package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
- package/packages/datadog-plugin-fastify/src/index.js +10 -12
- package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
- package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
- package/packages/dd-trace/src/appsec/addresses.js +6 -1
- package/packages/dd-trace/src/appsec/channels.js +5 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +3 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
- package/packages/dd-trace/src/appsec/index.js +58 -43
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +24 -10
- package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
- package/packages/dd-trace/src/appsec/rasp/utils.js +2 -1
- package/packages/dd-trace/src/appsec/recommended.json +2 -4
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +5 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +8 -0
- package/packages/dd-trace/src/appsec/reporter.js +12 -5
- package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -0
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
- package/packages/dd-trace/src/config.js +12 -1
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +56 -5
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +153 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +30 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +241 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +10 -4
- package/packages/dd-trace/src/exporters/common/request.js +8 -34
- package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
- package/packages/dd-trace/src/payload-tagging/index.js +1 -1
- package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/schema.js +35 -0
- package/packages/dd-trace/src/plugins/util/ci.js +23 -1
- package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
- package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
- package/packages/dd-trace/src/plugins/util/tags.js +7 -0
- package/packages/dd-trace/src/plugins/util/test.js +20 -22
- package/packages/dd-trace/src/plugins/util/web.js +6 -4
- package/packages/dd-trace/src/profiling/profiler.js +24 -14
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +94 -66
- package/packages/dd-trace/src/proxy.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/datadog-core/src/storage/async_resource.js +0 -108
- package/packages/datadog-core/src/storage/index.js +0 -5
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const FastifyTracingPlugin = require('./tracing')
|
|
4
|
+
const FastifyCodeOriginForSpansPlugin = require('./code_origin')
|
|
5
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
4
6
|
|
|
5
|
-
class FastifyPlugin extends
|
|
6
|
-
static get id () {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.addSub('apm:fastify:request:handle', ({ req }) => {
|
|
14
|
-
this.setFramework(req, 'fastify', this.config)
|
|
15
|
-
})
|
|
7
|
+
class FastifyPlugin extends CompositePlugin {
|
|
8
|
+
static get id () { return 'fastify' }
|
|
9
|
+
static get plugins () {
|
|
10
|
+
return {
|
|
11
|
+
tracing: FastifyTracingPlugin,
|
|
12
|
+
codeOriginForSpans: FastifyCodeOriginForSpansPlugin
|
|
13
|
+
}
|
|
16
14
|
}
|
|
17
15
|
}
|
|
18
16
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const RouterPlugin = require('../../datadog-plugin-router/src')
|
|
4
|
+
|
|
5
|
+
class FastifyTracingPlugin extends RouterPlugin {
|
|
6
|
+
static get id () {
|
|
7
|
+
return 'fastify'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
constructor (...args) {
|
|
11
|
+
super(...args)
|
|
12
|
+
|
|
13
|
+
this.addSub('apm:fastify:request:handle', ({ req }) => {
|
|
14
|
+
this.setFramework(req, 'fastify', this.config)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = FastifyTracingPlugin
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const SchemaPlugin = require('../../dd-trace/src/plugins/schema')
|
|
2
|
+
const SchemaExtractor = require('./schema_iterator')
|
|
3
|
+
|
|
4
|
+
class ProtobufjsPlugin extends SchemaPlugin {
|
|
5
|
+
static get id () {
|
|
6
|
+
return 'protobufjs'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static get schemaExtractor () {
|
|
10
|
+
return SchemaExtractor
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = ProtobufjsPlugin
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const PROTOBUF = 'protobuf'
|
|
2
|
+
const {
|
|
3
|
+
SCHEMA_DEFINITION,
|
|
4
|
+
SCHEMA_ID,
|
|
5
|
+
SCHEMA_NAME,
|
|
6
|
+
SCHEMA_OPERATION,
|
|
7
|
+
SCHEMA_WEIGHT,
|
|
8
|
+
SCHEMA_TYPE
|
|
9
|
+
} = require('../../dd-trace/src/constants')
|
|
10
|
+
const log = require('../../dd-trace/src/log')
|
|
11
|
+
const {
|
|
12
|
+
SchemaBuilder
|
|
13
|
+
} = require('../../dd-trace/src/datastreams/schemas/schema_builder')
|
|
14
|
+
|
|
15
|
+
class SchemaExtractor {
|
|
16
|
+
constructor (schema) {
|
|
17
|
+
this.schema = schema
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getTypeAndFormat (type) {
|
|
21
|
+
const typeFormatMapping = {
|
|
22
|
+
int32: ['integer', 'int32'],
|
|
23
|
+
int64: ['integer', 'int64'],
|
|
24
|
+
uint32: ['integer', 'uint32'],
|
|
25
|
+
uint64: ['integer', 'uint64'],
|
|
26
|
+
sint32: ['integer', 'sint32'],
|
|
27
|
+
sint64: ['integer', 'sint64'],
|
|
28
|
+
fixed32: ['integer', 'fixed32'],
|
|
29
|
+
fixed64: ['integer', 'fixed64'],
|
|
30
|
+
sfixed32: ['integer', 'sfixed32'],
|
|
31
|
+
sfixed64: ['integer', 'sfixed64'],
|
|
32
|
+
float: ['number', 'float'],
|
|
33
|
+
double: ['number', 'double'],
|
|
34
|
+
bool: ['boolean', null],
|
|
35
|
+
string: ['string', null],
|
|
36
|
+
bytes: ['string', 'byte'],
|
|
37
|
+
Enum: ['enum', null],
|
|
38
|
+
Type: ['type', null],
|
|
39
|
+
map: ['map', null],
|
|
40
|
+
repeated: ['array', null]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return typeFormatMapping[type] || ['string', null]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static extractProperty (field, schemaName, fieldName, builder, depth) {
|
|
47
|
+
let array = false
|
|
48
|
+
let description
|
|
49
|
+
let ref
|
|
50
|
+
let enumValues
|
|
51
|
+
|
|
52
|
+
const resolvedType = field.resolvedType ? field.resolvedType.constructor.name : field.type
|
|
53
|
+
|
|
54
|
+
const isRepeatedField = field.rule === 'repeated'
|
|
55
|
+
|
|
56
|
+
let typeFormat = this.getTypeAndFormat(isRepeatedField ? 'repeated' : resolvedType)
|
|
57
|
+
let type = typeFormat[0]
|
|
58
|
+
let format = typeFormat[1]
|
|
59
|
+
|
|
60
|
+
if (type === 'array') {
|
|
61
|
+
array = true
|
|
62
|
+
typeFormat = this.getTypeAndFormat(resolvedType)
|
|
63
|
+
type = typeFormat[0]
|
|
64
|
+
format = typeFormat[1]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (type === 'type') {
|
|
68
|
+
format = null
|
|
69
|
+
ref = `#/components/schemas/${removeLeadingPeriod(field.resolvedType.fullName)}`
|
|
70
|
+
// keep a reference to the original builder iterator since when we recurse this reference will get reset to
|
|
71
|
+
// deeper schemas
|
|
72
|
+
const originalSchemaExtractor = builder.iterator
|
|
73
|
+
if (!this.extractSchema(field.resolvedType, builder, depth, this)) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
type = 'object'
|
|
77
|
+
builder.iterator = originalSchemaExtractor
|
|
78
|
+
} else if (type === 'enum') {
|
|
79
|
+
enumValues = []
|
|
80
|
+
let i = 0
|
|
81
|
+
while (field.resolvedType.valuesById[i]) {
|
|
82
|
+
enumValues.push(field.resolvedType.valuesById[i])
|
|
83
|
+
i += 1
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return builder.addProperty(schemaName, fieldName, array, type, description, ref, format, enumValues)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static extractSchema (schema, builder, depth, extractor) {
|
|
90
|
+
depth += 1
|
|
91
|
+
const schemaName = removeLeadingPeriod(schema.resolvedType ? schema.resolvedType.fullName : schema.fullName)
|
|
92
|
+
if (extractor) {
|
|
93
|
+
// if we already have a defined extractor, this is a nested schema. create a new extractor for the nested
|
|
94
|
+
// schema, ensure it is added to our schema builder's cache, and replace the builders iterator with our
|
|
95
|
+
// nested schema iterator / extractor. Once complete, add the new schema to our builder's schemas.
|
|
96
|
+
const nestedSchemaExtractor = new SchemaExtractor(schema)
|
|
97
|
+
builder.iterator = nestedSchemaExtractor
|
|
98
|
+
const nestedSchema = SchemaBuilder.getSchema(schemaName, nestedSchemaExtractor, builder)
|
|
99
|
+
for (const nestedSubSchemaName in nestedSchema.components.schemas) {
|
|
100
|
+
if (nestedSchema.components.schemas.hasOwnProperty(nestedSubSchemaName)) {
|
|
101
|
+
builder.schema.components.schemas[nestedSubSchemaName] = nestedSchema.components.schemas[nestedSubSchemaName]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return true
|
|
105
|
+
} else {
|
|
106
|
+
if (!builder.shouldExtractSchema(schemaName, depth)) {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
for (const field of schema.fieldsArray) {
|
|
110
|
+
if (!this.extractProperty(field, schemaName, field.name, builder, depth)) {
|
|
111
|
+
log.warn(`DSM: Unable to extract field with name: ${field.name} from Avro schema with name: ${schemaName}`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static extractSchemas (descriptor, dataStreamsProcessor) {
|
|
119
|
+
const schemaName = removeLeadingPeriod(
|
|
120
|
+
descriptor.resolvedType ? descriptor.resolvedType.fullName : descriptor.fullName
|
|
121
|
+
)
|
|
122
|
+
return dataStreamsProcessor.getSchema(schemaName, new SchemaExtractor(descriptor))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
iterateOverSchema (builder) {
|
|
126
|
+
this.constructor.extractSchema(this.schema, builder, 0)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static attachSchemaOnSpan (args, span, operation, tracer) {
|
|
130
|
+
const { messageClass } = args
|
|
131
|
+
const descriptor = messageClass.$type ?? messageClass
|
|
132
|
+
|
|
133
|
+
if (!descriptor || !span) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (span.context()._tags[SCHEMA_TYPE] && operation === 'serialization') {
|
|
138
|
+
// we have already added a schema to this span, this call is an encode of nested schema types
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
span.setTag(SCHEMA_TYPE, PROTOBUF)
|
|
143
|
+
span.setTag(SCHEMA_NAME, removeLeadingPeriod(descriptor.fullName))
|
|
144
|
+
span.setTag(SCHEMA_OPERATION, operation)
|
|
145
|
+
|
|
146
|
+
if (!tracer._dataStreamsProcessor.canSampleSchema(operation)) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// if the span is unsampled, do not sample the schema
|
|
151
|
+
if (!tracer._prioritySampler.isSampled(span)) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const weight = tracer._dataStreamsProcessor.trySampleSchema(operation)
|
|
156
|
+
if (weight === 0) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const schemaData = SchemaBuilder.getSchemaDefinition(
|
|
161
|
+
this.extractSchemas(descriptor, tracer._dataStreamsProcessor)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
span.setTag(SCHEMA_DEFINITION, schemaData.definition)
|
|
165
|
+
span.setTag(SCHEMA_WEIGHT, weight)
|
|
166
|
+
span.setTag(SCHEMA_ID, schemaData.id)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function removeLeadingPeriod (str) {
|
|
171
|
+
// Check if the first character is a period
|
|
172
|
+
if (str.charAt(0) === '.') {
|
|
173
|
+
// Remove the first character
|
|
174
|
+
return str.slice(1)
|
|
175
|
+
}
|
|
176
|
+
// Return the original string if the first character is not a period
|
|
177
|
+
return str
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = SchemaExtractor
|
|
@@ -23,6 +23,11 @@ module.exports = {
|
|
|
23
23
|
WAF_CONTEXT_PROCESSOR: 'waf.context.processor',
|
|
24
24
|
|
|
25
25
|
HTTP_OUTGOING_URL: 'server.io.net.url',
|
|
26
|
+
FS_OPERATION_PATH: 'server.io.fs.file',
|
|
27
|
+
|
|
26
28
|
DB_STATEMENT: 'server.db.statement',
|
|
27
|
-
DB_SYSTEM: 'server.db.system'
|
|
29
|
+
DB_SYSTEM: 'server.db.system',
|
|
30
|
+
|
|
31
|
+
LOGIN_SUCCESS: 'server.business_logic.users.login.success',
|
|
32
|
+
LOGIN_FAILURE: 'server.business_logic.users.login.failure'
|
|
28
33
|
}
|
|
@@ -17,6 +17,7 @@ module.exports = {
|
|
|
17
17
|
setCookieChannel: dc.channel('datadog:iast:set-cookie'),
|
|
18
18
|
nextBodyParsed: dc.channel('apm:next:body-parsed'),
|
|
19
19
|
nextQueryParsed: dc.channel('apm:next:query-parsed'),
|
|
20
|
+
expressProcessParams: dc.channel('datadog:express:process_params:start'),
|
|
20
21
|
responseBody: dc.channel('datadog:express:response:json:start'),
|
|
21
22
|
responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
|
|
22
23
|
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
|
|
@@ -24,5 +25,8 @@ module.exports = {
|
|
|
24
25
|
setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'),
|
|
25
26
|
pgQueryStart: dc.channel('apm:pg:query:start'),
|
|
26
27
|
pgPoolQueryStart: dc.channel('datadog:pg:pool:query:start'),
|
|
27
|
-
|
|
28
|
+
mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
|
|
29
|
+
wafRunFinished: dc.channel('datadog:waf:run:finish'),
|
|
30
|
+
fsOperationStart: dc.channel('apm:fs:operation:start'),
|
|
31
|
+
expressMiddlewareError: dc.channel('apm:express:middleware:error')
|
|
28
32
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Analyzer = require('./vulnerability-analyzer')
|
|
4
4
|
const { getNodeModulesPaths } = require('../path-line')
|
|
5
|
+
const iastLog = require('../iast-log')
|
|
5
6
|
|
|
6
7
|
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
|
|
7
8
|
|
|
@@ -11,7 +12,14 @@ class CookieAnalyzer extends Analyzer {
|
|
|
11
12
|
this.propertyToBeSafe = propertyToBeSafe.toLowerCase()
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
onConfigure () {
|
|
15
|
+
onConfigure (config) {
|
|
16
|
+
try {
|
|
17
|
+
this.cookieFilterRegExp = new RegExp(config.iast.cookieFilterPattern)
|
|
18
|
+
} catch {
|
|
19
|
+
iastLog.error('Invalid regex in cookieFilterPattern')
|
|
20
|
+
this.cookieFilterRegExp = /.{32,}/
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
this.addSub(
|
|
16
24
|
{ channelName: 'datadog:iast:set-cookie', moduleName: 'http' },
|
|
17
25
|
(cookieInfo) => this.analyze(cookieInfo)
|
|
@@ -28,6 +36,10 @@ class CookieAnalyzer extends Analyzer {
|
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
_createHashSource (type, evidence, location) {
|
|
39
|
+
if (typeof evidence.value === 'string' && evidence.value.match(this.cookieFilterRegExp)) {
|
|
40
|
+
return 'FILTERED_' + this._type
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
return `${type}:${evidence.value}`
|
|
32
44
|
}
|
|
33
45
|
|
|
@@ -29,7 +29,14 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
|
29
29
|
|
|
30
30
|
onConfigure () {
|
|
31
31
|
this.addSub('apm:fs:operation:start', (obj) => {
|
|
32
|
-
|
|
32
|
+
const store = storage.getStore()
|
|
33
|
+
const outOfReqOrChild = !store?.fs?.root
|
|
34
|
+
|
|
35
|
+
// we could filter out all the nested fs.operations based on store.fs.root
|
|
36
|
+
// but if we spect a store in the context to be present we are going to exclude
|
|
37
|
+
// all out_of_the_request fs.operations
|
|
38
|
+
// AppsecFsPlugin must be enabled
|
|
39
|
+
if (ignoredOperations.includes(obj.operation) || outOfReqOrChild) return
|
|
33
40
|
|
|
34
41
|
const pathArguments = []
|
|
35
42
|
if (obj.dest) {
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
} = require('./taint-tracking')
|
|
15
15
|
const { IAST_ENABLED_TAG_KEY } = require('./tags')
|
|
16
16
|
const iastTelemetry = require('./telemetry')
|
|
17
|
+
const { enable: enableFsPlugin, disable: disableFsPlugin, IAST_MODULE } = require('../rasp/fs-plugin')
|
|
17
18
|
|
|
18
19
|
// TODO Change to `apm:http:server:request:[start|close]` when the subscription
|
|
19
20
|
// order of the callbacks can be enforce
|
|
@@ -27,6 +28,7 @@ function enable (config, _tracer) {
|
|
|
27
28
|
if (isEnabled) return
|
|
28
29
|
|
|
29
30
|
iastTelemetry.configure(config, config.iast?.telemetryVerbosity)
|
|
31
|
+
enableFsPlugin(IAST_MODULE)
|
|
30
32
|
enableAllAnalyzers(config)
|
|
31
33
|
enableTaintTracking(config.iast, iastTelemetry.verbosity)
|
|
32
34
|
requestStart.subscribe(onIncomingHttpRequestStart)
|
|
@@ -44,6 +46,7 @@ function disable () {
|
|
|
44
46
|
isEnabled = false
|
|
45
47
|
|
|
46
48
|
iastTelemetry.stop()
|
|
49
|
+
disableFsPlugin(IAST_MODULE)
|
|
47
50
|
disableAllAnalyzers()
|
|
48
51
|
disableTaintTracking()
|
|
49
52
|
overheadController.finishGlobalContext()
|
|
@@ -29,6 +29,7 @@ const TaintTrackingNoop = {
|
|
|
29
29
|
substr: noop,
|
|
30
30
|
substring: noop,
|
|
31
31
|
stringCase: noop,
|
|
32
|
+
tplOperator: noop,
|
|
32
33
|
trim: noop,
|
|
33
34
|
trimEnd: noop
|
|
34
35
|
}
|
|
@@ -117,6 +118,20 @@ function csiMethodsOverrides (getContext) {
|
|
|
117
118
|
return res
|
|
118
119
|
},
|
|
119
120
|
|
|
121
|
+
tplOperator: function (res, ...rest) {
|
|
122
|
+
try {
|
|
123
|
+
const iastContext = getContext()
|
|
124
|
+
const transactionId = getTransactionId(iastContext)
|
|
125
|
+
if (transactionId) {
|
|
126
|
+
return TaintedUtils.concat(transactionId, res, ...rest)
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
iastLog.error('Error invoking CSI tplOperator')
|
|
130
|
+
.errorAndPublish(e)
|
|
131
|
+
}
|
|
132
|
+
return res
|
|
133
|
+
},
|
|
134
|
+
|
|
120
135
|
stringCase: getCsiFn(
|
|
121
136
|
(transactionId, res, target) => TaintedUtils.stringCase(transactionId, res, target),
|
|
122
137
|
getContext,
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
queryParser,
|
|
13
13
|
nextBodyParsed,
|
|
14
14
|
nextQueryParsed,
|
|
15
|
+
expressProcessParams,
|
|
15
16
|
responseBody,
|
|
16
17
|
responseWriteHead,
|
|
17
18
|
responseSetHeader
|
|
@@ -30,6 +31,8 @@ const { storage } = require('../../../datadog-core')
|
|
|
30
31
|
const graphql = require('./graphql')
|
|
31
32
|
const rasp = require('./rasp')
|
|
32
33
|
|
|
34
|
+
const responseAnalyzedSet = new WeakSet()
|
|
35
|
+
|
|
33
36
|
let isEnabled = false
|
|
34
37
|
let config
|
|
35
38
|
|
|
@@ -54,13 +57,14 @@ function enable (_config) {
|
|
|
54
57
|
|
|
55
58
|
apiSecuritySampler.configure(_config.appsec)
|
|
56
59
|
|
|
60
|
+
bodyParser.subscribe(onRequestBodyParsed)
|
|
61
|
+
cookieParser.subscribe(onRequestCookieParser)
|
|
57
62
|
incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
|
|
58
63
|
incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
|
|
59
|
-
|
|
64
|
+
queryParser.subscribe(onRequestQueryParsed)
|
|
60
65
|
nextBodyParsed.subscribe(onRequestBodyParsed)
|
|
61
66
|
nextQueryParsed.subscribe(onRequestQueryParsed)
|
|
62
|
-
|
|
63
|
-
cookieParser.subscribe(onRequestCookieParser)
|
|
67
|
+
expressProcessParams.subscribe(onRequestProcessParams)
|
|
64
68
|
responseBody.subscribe(onResponseBody)
|
|
65
69
|
responseWriteHead.subscribe(onResponseWriteHead)
|
|
66
70
|
responseSetHeader.subscribe(onResponseSetHeader)
|
|
@@ -79,6 +83,41 @@ function enable (_config) {
|
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
function onRequestBodyParsed ({ req, res, body, abortController }) {
|
|
87
|
+
if (body === undefined || body === null) return
|
|
88
|
+
|
|
89
|
+
if (!req) {
|
|
90
|
+
const store = storage.getStore()
|
|
91
|
+
req = store?.req
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const rootSpan = web.root(req)
|
|
95
|
+
if (!rootSpan) return
|
|
96
|
+
|
|
97
|
+
const results = waf.run({
|
|
98
|
+
persistent: {
|
|
99
|
+
[addresses.HTTP_INCOMING_BODY]: body
|
|
100
|
+
}
|
|
101
|
+
}, req)
|
|
102
|
+
|
|
103
|
+
handleResults(results, req, res, rootSpan, abortController)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function onRequestCookieParser ({ req, res, abortController, cookies }) {
|
|
107
|
+
if (!cookies || typeof cookies !== 'object') return
|
|
108
|
+
|
|
109
|
+
const rootSpan = web.root(req)
|
|
110
|
+
if (!rootSpan) return
|
|
111
|
+
|
|
112
|
+
const results = waf.run({
|
|
113
|
+
persistent: {
|
|
114
|
+
[addresses.HTTP_INCOMING_COOKIES]: cookies
|
|
115
|
+
}
|
|
116
|
+
}, req)
|
|
117
|
+
|
|
118
|
+
handleResults(results, req, res, rootSpan, abortController)
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
83
122
|
const rootSpan = web.root(req)
|
|
84
123
|
if (!rootSpan) return
|
|
@@ -122,11 +161,6 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
122
161
|
persistent[addresses.HTTP_INCOMING_BODY] = req.body
|
|
123
162
|
}
|
|
124
163
|
|
|
125
|
-
// TODO: temporary express instrumentation, will use express plugin later
|
|
126
|
-
if (req.params !== null && typeof req.params === 'object') {
|
|
127
|
-
persistent[addresses.HTTP_INCOMING_PARAMS] = req.params
|
|
128
|
-
}
|
|
129
|
-
|
|
130
164
|
// we need to keep this to support other cookie parsers
|
|
131
165
|
if (req.cookies !== null && typeof req.cookies === 'object') {
|
|
132
166
|
persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
|
|
@@ -145,24 +179,16 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
145
179
|
Reporter.finishRequest(req, res)
|
|
146
180
|
}
|
|
147
181
|
|
|
148
|
-
function
|
|
149
|
-
|
|
182
|
+
function onPassportVerify ({ credentials, user }) {
|
|
183
|
+
const store = storage.getStore()
|
|
184
|
+
const rootSpan = store?.req && web.root(store.req)
|
|
150
185
|
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
|
|
186
|
+
if (!rootSpan) {
|
|
187
|
+
log.warn('No rootSpan found in onPassportVerify')
|
|
188
|
+
return
|
|
154
189
|
}
|
|
155
190
|
|
|
156
|
-
|
|
157
|
-
if (!rootSpan) return
|
|
158
|
-
|
|
159
|
-
const results = waf.run({
|
|
160
|
-
persistent: {
|
|
161
|
-
[addresses.HTTP_INCOMING_BODY]: body
|
|
162
|
-
}
|
|
163
|
-
}, req)
|
|
164
|
-
|
|
165
|
-
handleResults(results, req, res, rootSpan, abortController)
|
|
191
|
+
passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
|
|
166
192
|
}
|
|
167
193
|
|
|
168
194
|
function onRequestQueryParsed ({ req, res, query, abortController }) {
|
|
@@ -185,15 +211,15 @@ function onRequestQueryParsed ({ req, res, query, abortController }) {
|
|
|
185
211
|
handleResults(results, req, res, rootSpan, abortController)
|
|
186
212
|
}
|
|
187
213
|
|
|
188
|
-
function
|
|
189
|
-
if (!cookies || typeof cookies !== 'object') return
|
|
190
|
-
|
|
214
|
+
function onRequestProcessParams ({ req, res, abortController, params }) {
|
|
191
215
|
const rootSpan = web.root(req)
|
|
192
216
|
if (!rootSpan) return
|
|
193
217
|
|
|
218
|
+
if (!params || typeof params !== 'object' || !Object.keys(params).length) return
|
|
219
|
+
|
|
194
220
|
const results = waf.run({
|
|
195
221
|
persistent: {
|
|
196
|
-
[addresses.
|
|
222
|
+
[addresses.HTTP_INCOMING_PARAMS]: params
|
|
197
223
|
}
|
|
198
224
|
}, req)
|
|
199
225
|
|
|
@@ -212,20 +238,6 @@ function onResponseBody ({ req, body }) {
|
|
|
212
238
|
}, req)
|
|
213
239
|
}
|
|
214
240
|
|
|
215
|
-
function onPassportVerify ({ credentials, user }) {
|
|
216
|
-
const store = storage.getStore()
|
|
217
|
-
const rootSpan = store?.req && web.root(store.req)
|
|
218
|
-
|
|
219
|
-
if (!rootSpan) {
|
|
220
|
-
log.warn('No rootSpan found in onPassportVerify')
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const responseAnalyzedSet = new WeakSet()
|
|
228
|
-
|
|
229
241
|
function onResponseWriteHead ({ req, res, abortController, statusCode, responseHeaders }) {
|
|
230
242
|
// avoid "write after end" error
|
|
231
243
|
if (isBlocked(res)) {
|
|
@@ -287,12 +299,15 @@ function disable () {
|
|
|
287
299
|
|
|
288
300
|
// Channel#unsubscribe() is undefined for non active channels
|
|
289
301
|
if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
|
|
302
|
+
if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
|
|
290
303
|
if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
|
|
291
304
|
if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
|
|
305
|
+
if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
|
|
292
306
|
if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
|
|
293
|
-
if (
|
|
307
|
+
if (nextBodyParsed.hasSubscribers) nextBodyParsed.unsubscribe(onRequestBodyParsed)
|
|
308
|
+
if (nextQueryParsed.hasSubscribers) nextQueryParsed.unsubscribe(onRequestQueryParsed)
|
|
309
|
+
if (expressProcessParams.hasSubscribers) expressProcessParams.unsubscribe(onRequestProcessParams)
|
|
294
310
|
if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody)
|
|
295
|
-
if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
|
|
296
311
|
if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead)
|
|
297
312
|
if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(onResponseSetHeader)
|
|
298
313
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Plugin = require('../../plugins/plugin')
|
|
4
|
+
const { storage } = require('../../../../datadog-core')
|
|
5
|
+
const log = require('../../log')
|
|
6
|
+
|
|
7
|
+
const RASP_MODULE = 'rasp'
|
|
8
|
+
const IAST_MODULE = 'iast'
|
|
9
|
+
|
|
10
|
+
const enabledFor = {
|
|
11
|
+
[RASP_MODULE]: false,
|
|
12
|
+
[IAST_MODULE]: false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let fsPlugin
|
|
16
|
+
|
|
17
|
+
function enterWith (fsProps, store = storage.getStore()) {
|
|
18
|
+
if (store && !store.fs?.opExcluded) {
|
|
19
|
+
storage.enterWith({
|
|
20
|
+
...store,
|
|
21
|
+
fs: {
|
|
22
|
+
...store.fs,
|
|
23
|
+
...fsProps,
|
|
24
|
+
parentStore: store
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class AppsecFsPlugin extends Plugin {
|
|
31
|
+
enable () {
|
|
32
|
+
this.addSub('apm:fs:operation:start', this._onFsOperationStart)
|
|
33
|
+
this.addSub('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd)
|
|
34
|
+
this.addSub('tracing:datadog:express:response:render:start', this._onResponseRenderStart)
|
|
35
|
+
this.addSub('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd)
|
|
36
|
+
|
|
37
|
+
super.configure(true)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
disable () {
|
|
41
|
+
super.configure(false)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_onFsOperationStart () {
|
|
45
|
+
const store = storage.getStore()
|
|
46
|
+
if (store) {
|
|
47
|
+
enterWith({ root: store.fs?.root === undefined }, store)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_onResponseRenderStart () {
|
|
52
|
+
enterWith({ opExcluded: true })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_onFsOperationFinishOrRenderEnd () {
|
|
56
|
+
const store = storage.getStore()
|
|
57
|
+
if (store?.fs?.parentStore) {
|
|
58
|
+
storage.enterWith(store.fs.parentStore)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function enable (mod) {
|
|
64
|
+
if (enabledFor[mod] !== false) return
|
|
65
|
+
|
|
66
|
+
enabledFor[mod] = true
|
|
67
|
+
|
|
68
|
+
if (!fsPlugin) {
|
|
69
|
+
fsPlugin = new AppsecFsPlugin()
|
|
70
|
+
fsPlugin.enable()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
log.info(`Enabled AppsecFsPlugin for ${mod}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function disable (mod) {
|
|
77
|
+
if (!mod || !enabledFor[mod]) return
|
|
78
|
+
|
|
79
|
+
enabledFor[mod] = false
|
|
80
|
+
|
|
81
|
+
const allDisabled = Object.values(enabledFor).every(val => val === false)
|
|
82
|
+
if (allDisabled) {
|
|
83
|
+
fsPlugin?.disable()
|
|
84
|
+
|
|
85
|
+
fsPlugin = undefined
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
log.info(`Disabled AppsecFsPlugin for ${mod}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
enable,
|
|
93
|
+
disable,
|
|
94
|
+
|
|
95
|
+
AppsecFsPlugin,
|
|
96
|
+
|
|
97
|
+
RASP_MODULE,
|
|
98
|
+
IAST_MODULE
|
|
99
|
+
}
|