dd-trace 4.17.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE-3rdparty.csv +2 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/index.d.ts +9 -1
  8. package/package.json +8 -8
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/cucumber.js +5 -0
  12. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  14. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +39 -10
  16. package/packages/datadog-instrumentations/src/knex.js +24 -17
  17. package/packages/datadog-instrumentations/src/mocha.js +16 -1
  18. package/packages/datadog-instrumentations/src/next.js +58 -23
  19. package/packages/datadog-instrumentations/src/playwright.js +11 -6
  20. package/packages/datadog-instrumentations/src/restify.js +14 -1
  21. package/packages/datadog-plugin-http/src/client.js +2 -0
  22. package/packages/datadog-plugin-jest/src/index.js +11 -3
  23. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -6
  24. package/packages/datadog-plugin-kafkajs/src/producer.js +9 -6
  25. package/packages/datadog-plugin-mocha/src/index.js +7 -1
  26. package/packages/datadog-plugin-next/src/index.js +4 -3
  27. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  28. package/packages/dd-trace/src/appsec/channels.js +1 -1
  29. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  30. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  33. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  34. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +9 -2
  35. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  36. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  37. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  39. package/packages/dd-trace/src/appsec/iast/path-line.js +7 -2
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
  41. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  42. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +19 -0
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +2 -1
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  45. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  46. package/packages/dd-trace/src/appsec/recommended.json +272 -48
  47. package/packages/dd-trace/src/appsec/reporter.js +31 -34
  48. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +16 -4
  49. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  50. package/packages/dd-trace/src/config.js +35 -17
  51. package/packages/dd-trace/src/datastreams/processor.js +60 -15
  52. package/packages/dd-trace/src/format.js +6 -1
  53. package/packages/dd-trace/src/git_properties.js +16 -15
  54. package/packages/dd-trace/src/iitm.js +1 -1
  55. package/packages/dd-trace/src/log/channels.js +1 -1
  56. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  57. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  58. package/packages/dd-trace/src/opentracing/span.js +4 -0
  59. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  60. package/packages/dd-trace/src/plugin_manager.js +1 -1
  61. package/packages/dd-trace/src/plugins/database.js +1 -1
  62. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  63. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  64. package/packages/dd-trace/src/plugins/util/git.js +2 -1
  65. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  66. package/packages/dd-trace/src/plugins/util/test.js +29 -1
  67. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  68. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -14
  69. package/packages/dd-trace/src/profiling/config.js +23 -20
  70. package/packages/dd-trace/src/profiling/profilers/events.js +161 -0
  71. package/packages/dd-trace/src/profiling/profilers/shared.js +9 -0
  72. package/packages/dd-trace/src/profiling/profilers/wall.js +84 -47
  73. package/packages/dd-trace/src/ritm.js +1 -1
  74. package/packages/dd-trace/src/span_processor.js +4 -0
  75. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  76. package/packages/dd-trace/src/telemetry/index.js +5 -1
  77. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  78. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  79. package/packages/dd-trace/src/tracer.js +4 -2
  80. package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
  81. package/packages/diagnostics_channel/index.js +0 -3
  82. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -1,5 +1,6 @@
1
1
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
2
2
  const shimmer = require('../../datadog-shimmer')
3
+ const { parseAnnotations } = require('../../dd-trace/src/plugins/util/test')
3
4
 
4
5
  const testStartCh = channel('ci:playwright:test:start')
5
6
  const testFinishCh = channel('ci:playwright:test:finish')
@@ -103,7 +104,11 @@ function testBeginHandler (test) {
103
104
  })
104
105
  }
105
106
 
106
- function testEndHandler (test, testStatus, error) {
107
+ function testEndHandler (test, annotations, testStatus, error) {
108
+ let annotationTags
109
+ if (annotations.length) {
110
+ annotationTags = parseAnnotations(annotations)
111
+ }
107
112
  const { _requireFile: testSuiteAbsolutePath, results, _type } = test
108
113
 
109
114
  if (_type === 'beforeAll' || _type === 'afterAll') {
@@ -113,7 +118,7 @@ function testEndHandler (test, testStatus, error) {
113
118
  const testResult = results[results.length - 1]
114
119
  const testAsyncResource = testToAr.get(test)
115
120
  testAsyncResource.runInAsyncScope(() => {
116
- testFinishCh.publish({ testStatus, steps: testResult.steps, error })
121
+ testFinishCh.publish({ testStatus, steps: testResult.steps, error, extraTags: annotationTags })
117
122
  })
118
123
 
119
124
  if (!testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
@@ -172,7 +177,7 @@ function dispatcherHook (dispatcherExport) {
172
177
  const { results } = test
173
178
  const testResult = results[results.length - 1]
174
179
 
175
- testEndHandler(test, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
180
+ testEndHandler(test, params.annotations, STATUS_TO_TEST_STATUS[testResult.status], testResult.error)
176
181
  }
177
182
  })
178
183
 
@@ -200,10 +205,10 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
200
205
  const test = getTestByTestId(dispatcher, testId)
201
206
  testBeginHandler(test)
202
207
  })
203
- worker.on('testEnd', ({ testId, status, errors }) => {
208
+ worker.on('testEnd', ({ testId, status, errors, annotations }) => {
204
209
  const test = getTestByTestId(dispatcher, testId)
205
210
 
206
- testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
211
+ testEndHandler(test, annotations, STATUS_TO_TEST_STATUS[status], errors && errors[0])
207
212
  })
208
213
 
209
214
  return worker
@@ -230,7 +235,7 @@ function runnerHook (runnerExport, playwrightVersion) {
230
235
  // because they were skipped
231
236
  tests.forEach(test => {
232
237
  testBeginHandler(test)
233
- testEndHandler(test, 'skip')
238
+ testEndHandler(test, [], 'skip')
234
239
  })
235
240
  })
236
241
 
@@ -50,7 +50,20 @@ function wrapFn (fn) {
50
50
  enterChannel.publish({ req, route })
51
51
 
52
52
  try {
53
- return fn.apply(this, arguments)
53
+ const result = fn.apply(this, arguments)
54
+ if (result && typeof result === 'object' && typeof result.then === 'function') {
55
+ return result.then(function () {
56
+ nextChannel.publish({ req })
57
+ finishChannel.publish({ req })
58
+ return arguments
59
+ }).catch(function (error) {
60
+ errorChannel.publish({ req, error })
61
+ nextChannel.publish({ req })
62
+ finishChannel.publish({ req })
63
+ throw error
64
+ })
65
+ }
66
+ return result
54
67
  } catch (error) {
55
68
  errorChannel.publish({ req, error })
56
69
  nextChannel.publish({ req })
@@ -76,6 +76,7 @@ class HttpClientPlugin extends ClientPlugin {
76
76
  }
77
77
 
78
78
  finish ({ req, res, span }) {
79
+ if (!span) return
79
80
  if (res) {
80
81
  const status = res.status || res.statusCode
81
82
 
@@ -98,6 +99,7 @@ class HttpClientPlugin extends ClientPlugin {
98
99
  }
99
100
 
100
101
  error ({ span, error }) {
102
+ if (!span) return
101
103
  if (error) {
102
104
  span.addTags({
103
105
  [ERROR_TYPE]: error.name,
@@ -54,11 +54,17 @@ class JestPlugin extends CiPlugin {
54
54
  testCodeCoverageLinesTotal,
55
55
  numSkippedSuites,
56
56
  hasUnskippableSuites,
57
- hasForcedToRunSuites
57
+ hasForcedToRunSuites,
58
+ error
58
59
  }) => {
59
60
  this.testSessionSpan.setTag(TEST_STATUS, status)
60
61
  this.testModuleSpan.setTag(TEST_STATUS, status)
61
62
 
63
+ if (error) {
64
+ this.testSessionSpan.setTag('error', error)
65
+ this.testModuleSpan.setTag('error', error)
66
+ }
67
+
62
68
  addIntelligentTestRunnerSpanTags(
63
69
  this.testSessionSpan,
64
70
  this.testModuleSpan,
@@ -150,9 +156,11 @@ class JestPlugin extends CiPlugin {
150
156
  })
151
157
  })
152
158
 
153
- this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
159
+ this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage, error }) => {
154
160
  this.testSuiteSpan.setTag(TEST_STATUS, status)
155
- if (errorMessage) {
161
+ if (error) {
162
+ this.testSuiteSpan.setTag('error', error)
163
+ } else if (errorMessage) {
156
164
  this.testSuiteSpan.setTag('error', new Error(errorMessage))
157
165
  }
158
166
  this.testSuiteSpan.finish()
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
3
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
5
 
5
6
  class KafkajsConsumerPlugin extends ConsumerPlugin {
@@ -7,13 +8,8 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
7
8
  static get operation () { return 'consume' }
8
9
 
9
10
  start ({ topic, partition, message, groupId }) {
10
- if (this.config.dsmEnabled) {
11
- this.tracer.decodeDataStreamsContext(message.headers['dd-pathway-ctx'])
12
- this.tracer
13
- .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'])
14
- }
15
11
  const childOf = extract(this.tracer, message.headers)
16
- this.startSpan({
12
+ const span = this.startSpan({
17
13
  childOf,
18
14
  resource: topic,
19
15
  type: 'worker',
@@ -26,6 +22,12 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
26
22
  'kafka.partition': partition
27
23
  }
28
24
  })
25
+ if (this.config.dsmEnabled) {
26
+ const payloadSize = getMessageSize(message)
27
+ this.tracer.decodeDataStreamsContext(message.headers[CONTEXT_PROPAGATION_KEY])
28
+ this.tracer
29
+ .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], span, payloadSize)
30
+ }
29
31
  }
30
32
  }
31
33
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
4
  const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
5
+ const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
6
+
5
7
  const BOOTSTRAP_SERVERS_KEY = 'messaging.kafka.bootstrap.servers'
6
8
 
7
9
  class KafkajsProducerPlugin extends ProducerPlugin {
@@ -11,11 +13,6 @@ class KafkajsProducerPlugin extends ProducerPlugin {
11
13
 
12
14
  start ({ topic, messages, bootstrapServers }) {
13
15
  let pathwayCtx
14
- if (this.config.dsmEnabled) {
15
- const dataStreamsContext = this.tracer
16
- .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'])
17
- pathwayCtx = encodePathwayContext(dataStreamsContext)
18
- }
19
16
  const span = this.startSpan({
20
17
  resource: topic,
21
18
  meta: {
@@ -31,8 +28,14 @@ class KafkajsProducerPlugin extends ProducerPlugin {
31
28
  }
32
29
  for (const message of messages) {
33
30
  if (typeof message === 'object') {
34
- if (this.config.dsmEnabled) message.headers['dd-pathway-ctx'] = pathwayCtx
35
31
  this.tracer.inject(span, 'text_map', message.headers)
32
+ if (this.config.dsmEnabled) {
33
+ const payloadSize = getMessageSize(message)
34
+ const dataStreamsContext = this.tracer
35
+ .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'], span, payloadSize)
36
+ pathwayCtx = encodePathwayContext(dataStreamsContext)
37
+ message.headers[CONTEXT_PROPAGATION_KEY] = pathwayCtx
38
+ }
36
39
  }
37
40
  }
38
41
  }
@@ -150,13 +150,19 @@ class MochaPlugin extends CiPlugin {
150
150
  testCodeCoverageLinesTotal,
151
151
  numSkippedSuites,
152
152
  hasForcedToRunSuites,
153
- hasUnskippableSuites
153
+ hasUnskippableSuites,
154
+ error
154
155
  }) => {
155
156
  if (this.testSessionSpan) {
156
157
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
157
158
  this.testSessionSpan.setTag(TEST_STATUS, status)
158
159
  this.testModuleSpan.setTag(TEST_STATUS, status)
159
160
 
161
+ if (error) {
162
+ this.testSessionSpan.setTag('error', error)
163
+ this.testModuleSpan.setTag('error', error)
164
+ }
165
+
160
166
  addIntelligentTestRunnerSpanTags(
161
167
  this.testSessionSpan,
162
168
  this.testModuleSpan,
@@ -43,7 +43,7 @@ class NextPlugin extends ServerPlugin {
43
43
  this.addError(error, span)
44
44
  }
45
45
 
46
- finish ({ req, res }) {
46
+ finish ({ req, res, nextRequest = {} }) {
47
47
  const store = storage.getStore()
48
48
 
49
49
  if (!store) return
@@ -52,7 +52,8 @@ class NextPlugin extends ServerPlugin {
52
52
  const error = span.context()._tags['error']
53
53
 
54
54
  if (!this.config.validateStatus(res.statusCode) && !error) {
55
- span.setTag('error', true)
55
+ span.setTag('error', req.error || nextRequest.error || true)
56
+ web.addError(req, req.error || nextRequest.error || true)
56
57
  }
57
58
 
58
59
  span.addTags({
@@ -64,7 +65,7 @@ class NextPlugin extends ServerPlugin {
64
65
  span.finish()
65
66
  }
66
67
 
67
- pageLoad ({ page, isAppPath }) {
68
+ pageLoad ({ page, isAppPath = false }) {
68
69
  const store = storage.getStore()
69
70
 
70
71
  if (!store) return
@@ -72,7 +72,7 @@ class PlaywrightPlugin extends CiPlugin {
72
72
 
73
73
  this.enter(span, store)
74
74
  })
75
- this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error }) => {
75
+ this.addSub('ci:playwright:test:finish', ({ testStatus, steps, error, extraTags }) => {
76
76
  const store = storage.getStore()
77
77
  const span = store && store.span
78
78
  if (!span) return
@@ -82,6 +82,9 @@ class PlaywrightPlugin extends CiPlugin {
82
82
  if (error) {
83
83
  span.setTag('error', error)
84
84
  }
85
+ if (extraTags) {
86
+ span.addTags(extraTags)
87
+ }
85
88
 
86
89
  steps.forEach(step => {
87
90
  const stepStartTime = step.startTime.getTime()
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
4
4
 
5
5
  // TODO: use TBD naming convention
6
6
  module.exports = {
@@ -2,6 +2,7 @@
2
2
 
3
3
  module.exports = {
4
4
  'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
5
+ 'HARCODED_SECRET_ANALYZER': require('./hardcoded-secret-analyzer'),
5
6
  'HSTS_HEADER_MISSING_ANALYZER': require('./hsts-header-missing-analyzer'),
6
7
  'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
7
8
  'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
@@ -0,0 +1,60 @@
1
+ 'use strict'
2
+
3
+ const Analyzer = require('./vulnerability-analyzer')
4
+ const { HARDCODED_SECRET } = require('../vulnerabilities')
5
+ const { getRelativePath } = require('../path-line')
6
+
7
+ const secretRules = require('./hardcoded-secrets-rules')
8
+
9
+ class HardcodedSecretAnalyzer extends Analyzer {
10
+ constructor () {
11
+ super(HARDCODED_SECRET)
12
+ }
13
+
14
+ onConfigure () {
15
+ this.addSub('datadog:secrets:result', (secrets) => { this.analyze(secrets) })
16
+ }
17
+
18
+ analyze (secrets) {
19
+ if (!secrets?.file || !secrets.literals) return
20
+
21
+ const matches = secrets.literals
22
+ .filter(literal => literal.value && literal.locations?.length)
23
+ .map(literal => {
24
+ const match = secretRules.find(rule => literal.value.match(rule.regex))
25
+
26
+ return match ? { locations: literal.locations, ruleId: match.id } : undefined
27
+ })
28
+ .filter(match => !!match)
29
+
30
+ if (matches.length) {
31
+ const file = getRelativePath(secrets.file)
32
+
33
+ matches.forEach(match => {
34
+ match.locations
35
+ .filter(location => location.line)
36
+ .forEach(location => this._report({
37
+ file,
38
+ line: location.line,
39
+ column: location.column,
40
+ data: match.ruleId
41
+ }))
42
+ })
43
+ }
44
+ }
45
+
46
+ _getEvidence (value) {
47
+ return { value: `${value.data}` }
48
+ }
49
+
50
+ _getLocation (value) {
51
+ return {
52
+ path: value.file,
53
+ line: value.line,
54
+ column: value.column,
55
+ isInternal: false
56
+ }
57
+ }
58
+ }
59
+
60
+ module.exports = new HardcodedSecretAnalyzer()
@@ -0,0 +1,269 @@
1
+ /* eslint-disable max-len */
2
+ 'use strict'
3
+
4
+ module.exports = [
5
+ {
6
+ 'id': 'adobe-client-secret',
7
+ 'regex': /\b((p8e-)[a-z0-9]{32})(?:['"\s\x60;]|$)/i
8
+ },
9
+ {
10
+ 'id': 'age-secret-key',
11
+ 'regex': /AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}/
12
+ },
13
+ {
14
+ 'id': 'alibaba-access-key-id',
15
+ 'regex': /\b((LTAI)[a-z0-9]{20})(?:['"\s\x60;]|$)/i
16
+ },
17
+ {
18
+ 'id': 'authress-service-client-access-key',
19
+ 'regex': /\b((?:sc|ext|scauth|authress)_[a-z0-9]{5,30}\.[a-z0-9]{4,6}\.acc[_-][a-z0-9-]{10,32}\.[a-z0-9+/_=-]{30,120})(?:['"\s\x60;]|$)/i
20
+ },
21
+ {
22
+ 'id': 'aws-access-token',
23
+ 'regex': /\b((A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16})(?:['"\s\x60;]|$)/
24
+ },
25
+ {
26
+ 'id': 'clojars-api-token',
27
+ 'regex': /(CLOJARS_)[a-z0-9]{60}/i
28
+ },
29
+ {
30
+ 'id': 'databricks-api-token',
31
+ 'regex': /\b(dapi[a-h0-9]{32})(?:['"\s\x60;]|$)/i
32
+ },
33
+ {
34
+ 'id': 'digitalocean-access-token',
35
+ 'regex': /\b(doo_v1_[a-f0-9]{64})(?:['"\s\x60;]|$)/i
36
+ },
37
+ {
38
+ 'id': 'digitalocean-pat',
39
+ 'regex': /\b(dop_v1_[a-f0-9]{64})(?:['"\s\x60;]|$)/i
40
+ },
41
+ {
42
+ 'id': 'digitalocean-refresh-token',
43
+ 'regex': /\b(dor_v1_[a-f0-9]{64})(?:['"\s\x60;]|$)/i
44
+ },
45
+ {
46
+ 'id': 'doppler-api-token',
47
+ 'regex': /(dp\.pt\.)[a-z0-9]{43}/i
48
+ },
49
+ {
50
+ 'id': 'duffel-api-token',
51
+ 'regex': /duffel_(test|live)_[a-z0-9_\-=]{43}/i
52
+ },
53
+ {
54
+ 'id': 'dynatrace-api-token',
55
+ 'regex': /dt0c01\.[a-z0-9]{24}\.[a-z0-9]{64}/i
56
+ },
57
+ {
58
+ 'id': 'easypost-api-token',
59
+ 'regex': /\bEZAK[a-z0-9]{54}/i
60
+ },
61
+ {
62
+ 'id': 'flutterwave-public-key',
63
+ 'regex': /FLWPUBK_TEST-[a-h0-9]{32}-X/i
64
+ },
65
+ {
66
+ 'id': 'frameio-api-token',
67
+ 'regex': /fio-u-[a-z0-9\-_=]{64}/i
68
+ },
69
+ {
70
+ 'id': 'gcp-api-key',
71
+ 'regex': /\b(AIza[0-9a-z\-_]{35})(?:['"\s\x60;]|$)/i
72
+ },
73
+ {
74
+ 'id': 'github-app-token',
75
+ 'regex': /(ghu|ghs)_[0-9a-zA-Z]{36}/
76
+ },
77
+ {
78
+ 'id': 'github-fine-grained-pat',
79
+ 'regex': /github_pat_[0-9a-zA-Z_]{82}/
80
+ },
81
+ {
82
+ 'id': 'github-oauth',
83
+ 'regex': /gho_[0-9a-zA-Z]{36}/
84
+ },
85
+ {
86
+ 'id': 'github-pat',
87
+ 'regex': /ghp_[0-9a-zA-Z]{36}/
88
+ },
89
+ {
90
+ 'id': 'gitlab-pat',
91
+ 'regex': /glpat-[0-9a-zA-Z\-_]{20}/
92
+ },
93
+ {
94
+ 'id': 'gitlab-ptt',
95
+ 'regex': /glptt-[0-9a-f]{40}/
96
+ },
97
+ {
98
+ 'id': 'gitlab-rrt',
99
+ 'regex': /GR1348941[0-9a-zA-Z\-_]{20}/
100
+ },
101
+ {
102
+ 'id': 'grafana-api-key',
103
+ 'regex': /\b(eyJrIjoi[a-z0-9]{70,400}={0,2})(?:['"\s\x60;]|$)/i
104
+ },
105
+ {
106
+ 'id': 'grafana-cloud-api-token',
107
+ 'regex': /\b(glc_[a-z0-9+/]{32,400}={0,2})(?:['"\s\x60;]|$)/i
108
+ },
109
+ {
110
+ 'id': 'grafana-service-account-token',
111
+ 'regex': /\b(glsa_[a-z0-9]{32}_[a-f0-9]{8})(?:['"\s\x60;]|$)/i
112
+ },
113
+ {
114
+ 'id': 'hashicorp-tf-api-token',
115
+ 'regex': /[a-z0-9]{14}\.atlasv1\.[a-z0-9\-_=]{60,70}/i
116
+ },
117
+ {
118
+ 'id': 'jwt',
119
+ 'regex': /\b(ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9/_-]{17,}\.(?:[a-zA-Z0-9/_-]{10,}={0,2})?)(?:['"\s\x60;]|$)/
120
+ },
121
+ {
122
+ 'id': 'linear-api-key',
123
+ 'regex': /lin_api_[a-z0-9]{40}/i
124
+ },
125
+ {
126
+ 'id': 'npm-access-token',
127
+ 'regex': /\b(npm_[a-z0-9]{36})(?:['"\s\x60;]|$)/i
128
+ },
129
+ {
130
+ 'id': 'openai-api-key',
131
+ 'regex': /\b(sk-[a-z0-9]{20}T3BlbkFJ[a-z0-9]{20})(?:['"\s\x60;]|$)/i
132
+ },
133
+ {
134
+ 'id': 'planetscale-api-token',
135
+ 'regex': /\b(pscale_tkn_[a-z0-9=\-_.]{32,64})(?:['"\s\x60;]|$)/i
136
+ },
137
+ {
138
+ 'id': 'planetscale-oauth-token',
139
+ 'regex': /\b(pscale_oauth_[a-z0-9=\-_.]{32,64})(?:['"\s\x60;]|$)/i
140
+ },
141
+ {
142
+ 'id': 'planetscale-password',
143
+ 'regex': /\b(pscale_pw_[a-z0-9=\-_.]{32,64})(?:['"\s\x60;]|$)/i
144
+ },
145
+ {
146
+ 'id': 'postman-api-token',
147
+ 'regex': /\b(PMAK-[a-f0-9]{24}-[a-f0-9]{34})(?:['"\s\x60;]|$)/i
148
+ },
149
+ {
150
+ 'id': 'prefect-api-token',
151
+ 'regex': /\b(pnu_[a-z0-9]{36})(?:['"\s\x60;]|$)/i
152
+ },
153
+ {
154
+ 'id': 'private-key',
155
+ 'regex': /-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY( BLOCK)?-----[\s\S]*KEY( BLOCK)?----/i
156
+ },
157
+ {
158
+ 'id': 'pulumi-api-token',
159
+ 'regex': /\b(pul-[a-f0-9]{40})(?:['"\s\x60;]|$)/i
160
+ },
161
+ {
162
+ 'id': 'pypi-upload-token',
163
+ 'regex': /pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}/
164
+ },
165
+ {
166
+ 'id': 'readme-api-token',
167
+ 'regex': /\b(rdme_[a-z0-9]{70})(?:['"\s\x60;]|$)/i
168
+ },
169
+ {
170
+ 'id': 'rubygems-api-token',
171
+ 'regex': /\b(rubygems_[a-f0-9]{48})(?:['"\s\x60;]|$)/i
172
+ },
173
+ {
174
+ 'id': 'scalingo-api-token',
175
+ 'regex': /tk-us-[a-zA-Z0-9-_]{48}/
176
+ },
177
+ {
178
+ 'id': 'sendgrid-api-token',
179
+ 'regex': /\b(SG\.[a-z0-9=_\-.]{66})(?:['"\s\x60;]|$)/i
180
+ },
181
+ {
182
+ 'id': 'sendinblue-api-token',
183
+ 'regex': /\b(xkeysib-[a-f0-9]{64}-[a-z0-9]{16})(?:['"\s\x60;]|$)/i
184
+ },
185
+ {
186
+ 'id': 'shippo-api-token',
187
+ 'regex': /\b(shippo_(live|test)_[a-f0-9]{40})(?:['"\s\x60;]|$)/i
188
+ },
189
+ {
190
+ 'id': 'shopify-access-token',
191
+ 'regex': /shpat_[a-fA-F0-9]{32}/
192
+ },
193
+ {
194
+ 'id': 'shopify-custom-access-token',
195
+ 'regex': /shpca_[a-fA-F0-9]{32}/
196
+ },
197
+ {
198
+ 'id': 'shopify-private-app-access-token',
199
+ 'regex': /shppa_[a-fA-F0-9]{32}/
200
+ },
201
+ {
202
+ 'id': 'shopify-shared-secret',
203
+ 'regex': /shpss_[a-fA-F0-9]{32}/
204
+ },
205
+ {
206
+ 'id': 'slack-app-token',
207
+ 'regex': /(xapp-\d-[A-Z0-9]+-\d+-[a-z0-9]+)/i
208
+ },
209
+ {
210
+ 'id': 'slack-bot-token',
211
+ 'regex': /(xoxb-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*)/
212
+ },
213
+ {
214
+ 'id': 'slack-config-access-token',
215
+ 'regex': /(xoxe.xox[bp]-\d-[A-Z0-9]{163,166})/i
216
+ },
217
+ {
218
+ 'id': 'slack-config-refresh-token',
219
+ 'regex': /(xoxe-\d-[A-Z0-9]{146})/i
220
+ },
221
+ {
222
+ 'id': 'slack-legacy-bot-token',
223
+ 'regex': /(xoxb-[0-9]{8,14}-[a-zA-Z0-9]{18,26})/
224
+ },
225
+ {
226
+ 'id': 'slack-legacy-token',
227
+ 'regex': /(xox[os]-\d+-\d+-\d+-[a-fA-F\d]+)/
228
+ },
229
+ {
230
+ 'id': 'slack-legacy-workspace-token',
231
+ 'regex': /(xox[ar]-(?:\d-)?[0-9a-zA-Z]{8,48})/
232
+ },
233
+ {
234
+ 'id': 'slack-user-token',
235
+ 'regex': /(xox[pe](?:-[0-9]{10,13}){3}-[a-zA-Z0-9-]{28,34})/
236
+ },
237
+ {
238
+ 'id': 'slack-webhook-url',
239
+ 'regex': /(https?:\/\/)?hooks.slack.com\/(services|workflows)\/[A-Za-z0-9+/]{43,46}/
240
+ },
241
+ {
242
+ 'id': 'square-access-token',
243
+ 'regex': /\b(sq0atp-[0-9a-z\-_]{22})(?:['"\s\x60;]|$)/i
244
+ },
245
+ {
246
+ 'id': 'square-secret',
247
+ 'regex': /\b(sq0csp-[0-9a-z\-_]{43})(?:['"\s\x60;]|$)/i
248
+ },
249
+ {
250
+ 'id': 'stripe-access-token',
251
+ 'regex': /(sk|pk)_(test|live)_[0-9a-z]{10,32}/i
252
+ },
253
+ {
254
+ 'id': 'telegram-bot-api-token',
255
+ 'regex': /(?:^|[^0-9])([0-9]{5,16}:A[a-z0-9_-]{34})(?:$|[^a-z0-9_-])/i
256
+ },
257
+ {
258
+ 'id': 'twilio-api-key',
259
+ 'regex': /SK[0-9a-fA-F]{32}/
260
+ },
261
+ {
262
+ 'id': 'vault-batch-token',
263
+ 'regex': /\b(hvb\.[a-z0-9_-]{138,212})(?:['"\s\x60;]|$)/i
264
+ },
265
+ {
266
+ 'id': 'vault-service-token',
267
+ 'regex': /\b(hvs\.[a-z0-9_-]{90,100})(?:['"\s\x60;]|$)/i
268
+ }
269
+ ]
@@ -10,8 +10,11 @@ class HstsHeaderMissingAnalyzer extends MissingHeaderAnalyzer {
10
10
  super(HSTS_HEADER_MISSING, HSTS_HEADER_NAME)
11
11
  }
12
12
  _isVulnerableFromRequestAndResponse (req, res) {
13
- const headerToCheck = res.getHeader(HSTS_HEADER_NAME)
14
- return !this._isHeaderValid(headerToCheck) && this._isHttpsProtocol(req)
13
+ const headerValues = this._getHeaderValues(res, HSTS_HEADER_NAME)
14
+ return this._isHttpsProtocol(req) && (
15
+ headerValues.length === 0 ||
16
+ headerValues.some(headerValue => !this._isHeaderValid(headerValue))
17
+ )
15
18
  }
16
19
 
17
20
  _isHeaderValid (headerValue) {
@@ -28,6 +28,15 @@ class MissingHeaderAnalyzer extends Analyzer {
28
28
  }, (data) => this.analyze(data))
29
29
  }
30
30
 
31
+ _getHeaderValues (res, headerName) {
32
+ const headerValue = res.getHeader(headerName)
33
+ if (Array.isArray(headerValue)) {
34
+ return headerValue
35
+ } else {
36
+ return headerValue ? [headerValue.toString()] : []
37
+ }
38
+ }
39
+
31
40
  _getLocation () {
32
41
  return undefined
33
42
  }
@@ -41,7 +50,14 @@ class MissingHeaderAnalyzer extends Analyzer {
41
50
  }
42
51
 
43
52
  _getEvidence ({ res }) {
44
- return { value: res.getHeader(this.headerName) }
53
+ const headerValues = this._getHeaderValues(res, this.headerName)
54
+ let value
55
+ if (headerValues.length === 1) {
56
+ value = headerValues[0]
57
+ } else if (headerValues.length > 0) {
58
+ value = JSON.stringify(headerValues)
59
+ }
60
+ return { value }
45
61
  }
46
62
 
47
63
  _isVulnerable ({ req, res }, context) {
@@ -56,9 +72,11 @@ class MissingHeaderAnalyzer extends Analyzer {
56
72
  }
57
73
 
58
74
  _isResponseHtml (res) {
59
- const contentType = res.getHeader('content-type')
60
- return contentType && HTML_CONTENT_TYPES.some(htmlContentType => {
61
- return htmlContentType === contentType || contentType.startsWith(htmlContentType + ';')
75
+ const contentTypes = this._getHeaderValues(res, 'content-type')
76
+ return contentTypes.some(contentType => {
77
+ return contentType && HTML_CONTENT_TYPES.some(htmlContentType => {
78
+ return htmlContentType === contentType || contentType.startsWith(htmlContentType + ';')
79
+ })
62
80
  })
63
81
  }
64
82
  }