@contrast/assess 1.29.0 → 1.30.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.
@@ -24,18 +24,30 @@ const { InstrumentationType: { RULE } } = require('../../constants');
24
24
  const { PATCH_TYPE: patchType } = require('../common');
25
25
 
26
26
  const SAFE_HASH_ALGORITHMS = new Set([
27
- 'RSA-SHA1-2',
28
- 'RSA-SHA224',
29
- 'RSA-SHA256',
30
- 'RSA-SHA384',
31
- 'RSA-SHA512',
27
+ // SHA224
28
+ 'rsa-sha224',
29
+ 'sha-224',
30
+ 'sha2-224',
32
31
  'sha224',
33
- 'sha224WithRSAEncryption',
32
+ 'sha224withrsaencryption',
33
+ // SHA256
34
+ 'rsa-sha256',
35
+ 'sha-256',
36
+ 'sha2-256',
34
37
  'sha256',
35
- 'sha256WithRSAEncryption',
38
+ 'sha256withrsaencryption',
39
+ // SHA384
40
+ 'rsa-sha384',
41
+ 'sha-384',
42
+ 'sha2-384',
36
43
  'sha384',
37
- 'sha384WithRSAEncryption',
38
- 'sha512'
44
+ 'sha384withrsaencryption',
45
+ // SHA512
46
+ 'rsa-sha512',
47
+ 'sha-512',
48
+ 'sha2-512',
49
+ 'sha512',
50
+ 'sha512withrsaencryption',
39
51
  ]);
40
52
  const SAFE_HASH_LIBS = ['etag', 'browserify', 'deps-sort'];
41
53
  const SAFE_CIPHER_ALGORITHM_PREFIXES = ['des-ede', 'id-aes', 'aes', 'rsa'];
@@ -53,7 +65,6 @@ module.exports = function (core) {
53
65
  logger,
54
66
  patcher,
55
67
  assess: {
56
- inspect, // todo: remove
57
68
  eventFactory,
58
69
  cryptoAnalysis,
59
70
  getSourceContext,
@@ -68,16 +79,14 @@ module.exports = function (core) {
68
79
  patchType,
69
80
  post(data) {
70
81
  const [alg] = data.args;
82
+ if (!isString(alg) || !getSourceContext(RULE, Rule.CRYPTO_BAD_MAC)) return;
71
83
 
72
- if (
73
- !isString(alg) ||
74
- !getSourceContext(RULE, Rule.CRYPTO_BAD_MAC) ||
75
- SAFE_HASH_ALGORITHMS.has(alg)
76
- ) return;
84
+ const algLower = StringPrototypeToLowerCase.call(alg);
85
+ if (SAFE_HASH_ALGORITHMS.has(algLower)) return;
77
86
 
78
87
  const event = eventFactory.createCryptoAnalysisEvent({
79
88
  args: [{ tracked: false, value: alg }],
80
- context: `crypto.createHash(${inspect(alg)})`,
89
+ context: `crypto.createHash(${algLower})`,
81
90
  methodName: 'createHash',
82
91
  moduleName: 'crypto',
83
92
  name: 'crypto.createHash',
@@ -130,7 +139,7 @@ module.exports = function (core) {
130
139
 
131
140
  const event = eventFactory.createCryptoAnalysisEvent({
132
141
  args: [{ tracked: false, value: alg }],
133
- context: `crypto.${method}(${inspect(alg)})`,
142
+ context: `crypto.${method}(${algLower})`,
134
143
  methodName: method,
135
144
  moduleName: 'crypto',
136
145
  name: `crypto.${method}`,
@@ -38,7 +38,7 @@ module.exports = function (core) {
38
38
  } = core;
39
39
 
40
40
  /** @type {import('@contrast/rewriter').RewriteOpts} */
41
- const REWRITE_OPTS = { isModule: false, inject: false, wrap: false, trim: true };
41
+ const REWRITE_OPTS = { isModule: false, inject: false, wrap: false };
42
42
  const WRAPPER_PREFIX = ArrayPrototypeJoin.call([
43
43
  'function tempWrapper() {',
44
44
  'function __append(s) { if (s !== undefined && s !== null) __output += s }'
@@ -17,7 +17,7 @@
17
17
  const { patchType } = require('../../common');
18
18
 
19
19
  /** @type {import('@contrast/rewriter').RewriteOpts} */
20
- const REWRITE_OPTS = { isModule: false, inject: false, wrap: false, trim: true };
20
+ const REWRITE_OPTS = { isModule: false, inject: false, wrap: false };
21
21
 
22
22
  module.exports = function (core) {
23
23
  const store = { lock: true, name: 'assess:propagators:pug-compile' };
@@ -16,7 +16,8 @@
16
16
  'use strict';
17
17
 
18
18
  const { patchType } = require('../../common');
19
- const { isString } = require('@contrast/common');
19
+ const { isString, StringPrototypeConcat, StringPrototypeReplaceAll } = require('@contrast/common');
20
+ const { createAppendTags } = require('../../../tag-utils');
20
21
 
21
22
  module.exports = function(core) {
22
23
  const {
@@ -74,10 +75,9 @@ module.exports = function(core) {
74
75
  if (isString(params)) {
75
76
  params.split('&').forEach((query) => {
76
77
  const endIdx = query.indexOf('=');
77
- // this is pretty ugly because we don't want to create a propagation
78
- // event by splitting off the '?'. so we count on if it's there, we
79
- // skip it and if it's not there, we start at index 0.
80
- const key = query.substring(query.indexOf('?') + 1, endIdx);
78
+ // we don't want to create a propagation event by splitting off
79
+ // the '?'. so if there start at index 1 else 0.
80
+ const key = query.substring(query[0] === '?' ? 1 : 0, endIdx);
81
81
  const param = query.substring(endIdx + 1, query.length);
82
82
 
83
83
  const keyInfo = tracker.getData(key);
@@ -122,17 +122,76 @@ module.exports = function(core) {
122
122
  patchType,
123
123
  post(data) {
124
124
  const { obj: params } = data;
125
- let i = 0;
126
- let str = '';
127
- params.forEach((val, key) => {
128
- if (i === 0) {
129
- str = key.concat('=', val);
130
- } else {
131
- str = str.concat('&', key, '=', val);
125
+
126
+ // params.size doesn't exist until v18.16.0
127
+ const entries = [...params.entries()];
128
+ if (!entries.length) {
129
+ return;
130
+ }
131
+ let finalTags = {};
132
+ const history = [];
133
+ //
134
+ // local function to capture tags and history for entries in URLSearchParams
135
+ //
136
+ function createTagsAndHistoryIfNeeded(entry, base) {
137
+ // check key
138
+ let tracking = tracker.getData(entry[0]);
139
+ if (tracking) {
140
+ finalTags = createAppendTags(finalTags, tracking.tags, base + 1);
141
+ history.push(tracking);
142
+ }
143
+ // check value
144
+ tracking = tracker.getData(entry[1]);
145
+ if (tracking) {
146
+ // adjust tags to account for the 'key='
147
+ finalTags = createAppendTags(finalTags, tracking.tags, base + 2 + entry[0].length);
148
+ history.push(tracking);
149
+ }
150
+ }
151
+
152
+ // do the first one outside the loop to avoid the &. first base
153
+ // is -1 because there is no & to account for.
154
+ let result = StringPrototypeConcat.call(entries[0][0], '=', entries[0][1]);
155
+ createTagsAndHistoryIfNeeded(entries[0], -1);
156
+
157
+ // do remaining entries
158
+ let base = result.length;
159
+ for (let i = 1; i < entries.length; i++) {
160
+ result = StringPrototypeConcat.call(result, '&', entries[i][0], '=', entries[i][1]);
161
+ createTagsAndHistoryIfNeeded(entries[i], base);
162
+ base = result.length;
163
+ }
164
+
165
+ result = StringPrototypeReplaceAll.call(result, ' ', '+');
166
+
167
+ if (Object.keys(finalTags).length) {
168
+ const event = createPropagationEvent({
169
+ args: [],
170
+ context: `${inspect(params)}.toString()`,
171
+ moduleName: 'url',
172
+ methodName: 'URLSearchParams.toString',
173
+ history,
174
+ object: { value: 'URLSearchParams', tracked: false },
175
+ name: 'URLSearchParams.toString',
176
+ result: {
177
+ value: `${inspect(result)}`,
178
+ tracked: true
179
+ },
180
+ source: 'O',
181
+ stacktraceOpts: {
182
+ constructorOpt: data.hooked, // hide stack above this point
183
+ },
184
+ tags: finalTags,
185
+ target: 'R',
186
+ });
187
+
188
+ const { extern } = tracker.track(result, event);
189
+ if (extern) {
190
+ result = extern;
132
191
  }
133
- i++;
134
- });
135
- data.result = str;
192
+ }
193
+
194
+ data.result = result;
136
195
  }
137
196
  });
138
197
  }
@@ -77,11 +77,13 @@ module.exports = function(core) {
77
77
  WEAK_URL_ENCODED,
78
78
  ];
79
79
 
80
- const preHook = (name, method) => (data) => {
80
+ const preHook = (moduleName, responseName, method) => ({ args, obj: response, result, hooked, orig }) => {
81
+ const methodName = `${responseName + (moduleName !== 'spdy' ? '.prototype' : '')}.${method}`;
82
+ const name = `${moduleName}.${methodName}`;
81
83
  const sourceContext = getSourceContext(RULE, ruleId);
82
84
  if (!sourceContext) return;
83
85
 
84
- const payload = data.args[0];
86
+ const payload = args[0];
85
87
  if (!payload) return;
86
88
 
87
89
  const strInfo = tracker.getData(payload);
@@ -90,6 +92,7 @@ module.exports = function(core) {
90
92
  const { contentType } = sourceContext.responseData;
91
93
  if (contentType && isSafeContentType(contentType)) return;
92
94
 
95
+ if (moduleName === 'spdy') response.spdyStream.once('finish', () => response.emit('finish'));
93
96
  if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
94
97
  const event = createSinkEvent({
95
98
  args: [{
@@ -99,20 +102,20 @@ module.exports = function(core) {
99
102
  context: `res.${method}('${strInfo.value}')`,
100
103
  history: [strInfo],
101
104
  name,
102
- moduleName: 'http',
103
- methodName: `ServerResponse.prototype.${method}`,
105
+ moduleName,
106
+ methodName,
104
107
  object: {
105
108
  tracked: false,
106
- value: 'http.ServerResponse'
109
+ value: `${moduleName}.${responseName}`
107
110
  },
108
111
  result: {
109
- value: data.result,
112
+ value: result,
110
113
  tracked: false,
111
114
  },
112
115
  source: 'P0',
113
116
  stacktraceOpts: {
114
- constructorOpt: data.hooked,
115
- prependFrames: [data.orig]
117
+ constructorOpt: hooked,
118
+ prependFrames: [orig]
116
119
  },
117
120
  tags: strInfo.tags,
118
121
  });
@@ -139,21 +142,47 @@ module.exports = function(core) {
139
142
  http.install = function() {
140
143
  depHooks.resolve({ name: 'http' }, (http) => {
141
144
  {
142
- const name = 'http.ServerResponse.prototype.write';
143
145
  const method = 'write';
144
146
  patcher.patch(http.ServerResponse.prototype, method, {
145
- name,
147
+ name: 'http.ServerResponse.prototype.write',
146
148
  patchType,
147
- pre: preHook(name, method),
149
+ pre: preHook('http', 'ServerResponse', method),
148
150
  });
149
151
  }
150
152
  {
151
- const name = 'http.ServerResponse.prototype.end';
152
153
  const method = 'end';
153
154
  patcher.patch(http.ServerResponse.prototype, method, {
154
- name,
155
+ name: 'http.ServerResponse.prototype.end',
155
156
  patchType,
156
- pre: preHook(name, method),
157
+ pre: preHook('http', 'ServerResponse', method),
158
+ });
159
+ }
160
+ });
161
+ depHooks.resolve({ name: 'http2' }, (http2) => {
162
+ {
163
+ const method = 'write';
164
+ patcher.patch(http2.Http2ServerResponse.prototype, method, {
165
+ name: 'http2.Http2ServerResponse.prototype.write',
166
+ patchType,
167
+ pre: preHook('http2', 'Http2ServerResponse', method),
168
+ });
169
+ }
170
+ {
171
+ const method = 'end';
172
+ patcher.patch(http2.Http2ServerResponse.prototype, method, {
173
+ name: 'http2.Http2ServerResponse.prototype.end',
174
+ patchType,
175
+ pre: preHook('http2', 'Http2ServerResponse', method),
176
+ });
177
+ }
178
+ });
179
+ depHooks.resolve({ name: 'spdy', file: 'lib/spdy/response.js' }, (response) => {
180
+ {
181
+ const method = 'end';
182
+ patcher.patch(response, method, {
183
+ name: 'spdy.response.end',
184
+ patchType,
185
+ pre: preHook('spdy', 'response', method),
157
186
  });
158
187
  }
159
188
  });
@@ -56,15 +56,15 @@ module.exports = function(core) {
56
56
  }
57
57
  } = core;
58
58
 
59
- responseScanning.handleAutoCompleteMissing = function(sourceContext, { responseHeaders, responseBody }) {
59
+ responseScanning.handleAutoCompleteMissing = function(sourceContext, resHeaders, resBody) {
60
60
  if (
61
61
  !isEnabled(AUTOCOMPLETE_MISSING, sourceContext) ||
62
- !isHtmlContent(responseHeaders)
62
+ !isHtmlContent(resHeaders)
63
63
  ) {
64
64
  return;
65
65
  }
66
66
 
67
- const elements = getElements('form', responseBody);
67
+ const elements = getElements('form', resBody);
68
68
 
69
69
  for (let i = 0; i < elements.length; i++) {
70
70
  const autocomplete = getAttribute('autocomplete', elements[i]);
@@ -84,24 +84,24 @@ module.exports = function(core) {
84
84
  }
85
85
  };
86
86
 
87
- responseScanning.handleCacheControlsMissing = function(sourceContext, { responseHeaders, responseBody }) {
87
+ responseScanning.handleCacheControlsMissing = function(sourceContext, resHeaders, resBody) {
88
88
  const instructions = [];
89
89
 
90
90
  // de-dupe; this will be re-emitted for parseableBody handlers anyway
91
91
  if (
92
92
  !isEnabled(CACHE_CONTROLS_MISSING, sourceContext) ||
93
- (isParseableResponse(responseHeaders) && !responseBody)
93
+ (isParseableResponse(resHeaders) && !resBody)
94
94
  ) {
95
95
  return;
96
96
  }
97
- const cacheControlHeader = responseHeaders['cache-control'];
97
+ const cacheControlHeader = resHeaders['cache-control'];
98
98
 
99
99
  // save the Pragma Header
100
- if (responseHeaders['pragma']) {
100
+ if (resHeaders['pragma']) {
101
101
  instructions.push({
102
102
  type: 'Header',
103
103
  name: 'pragma',
104
- value: responseHeaders['pragma']
104
+ value: resHeaders['pragma']
105
105
  });
106
106
  }
107
107
 
@@ -124,7 +124,7 @@ module.exports = function(core) {
124
124
  }
125
125
 
126
126
  // we checked the headers now time to check the HTML content
127
- checkMetaTags({ body: responseBody, cacheControlHeader, instructions });
127
+ checkMetaTags({ body: resBody, cacheControlHeader, instructions });
128
128
 
129
129
  if (instructions.length) {
130
130
  reportFindings(sourceContext, {
@@ -136,11 +136,11 @@ module.exports = function(core) {
136
136
  }
137
137
  };
138
138
 
139
- responseScanning.handleClickJackingControlsMissing = function(sourceContext, { responseHeaders }) {
139
+ responseScanning.handleClickJackingControlsMissing = function(sourceContext, resHeaders) {
140
140
  if (!isEnabled(CLICKJACKING_CONTROL_MISSING, sourceContext)) return;
141
141
 
142
142
  // look for x-frame-options headers with deny or sameorigin
143
- const xFrameHeaders = responseHeaders['x-frame-options'];
143
+ const xFrameHeaders = resHeaders['x-frame-options'];
144
144
  let hasFrameBusting = false;
145
145
 
146
146
  if (xFrameHeaders) {
@@ -155,12 +155,12 @@ module.exports = function(core) {
155
155
  }
156
156
  };
157
157
 
158
- responseScanning.handleParameterPollution = function(sourceContext, { responseBody }) {
158
+ responseScanning.handleParameterPollution = function(sourceContext, resBody) {
159
159
  if (!isEnabled(PARAMETER_POLLUTION, sourceContext)) return;
160
160
 
161
161
  // look for form tag with missing action attribute.
162
162
  // ex: <form method="post">..
163
- const elements = getElements('form', responseBody);
163
+ const elements = getElements('form', resBody);
164
164
 
165
165
  for (let i = 0; i < elements.length; i++) {
166
166
  const action = getAttribute('action', elements[i]);
@@ -180,11 +180,11 @@ module.exports = function(core) {
180
180
  * Checks the response headers for the CSP. If found, and insecure, will return
181
181
  * the evidence for reporting.
182
182
  *
183
- * @param {Object} responseHeaders - HTTP headers object.
184
- * @returns {Object} - Evidence for insecure CSP header.
183
+ * @param {Object} resHeaders HTTP headers object.
184
+ * @returns {Object} Evidence for insecure CSP header.
185
185
  */
186
- responseScanning.handleCspHeader = function(sourceContext, { responseHeaders }) {
187
- const cspHeaders = getCspHeaders(responseHeaders);
186
+ responseScanning.handleCspHeader = function(sourceContext, resHeaders) {
187
+ const cspHeaders = getCspHeaders(resHeaders);
188
188
 
189
189
  // Don't report if not set; this report belongs to 'csp-header-missing'
190
190
  if (!cspHeaders && isEnabled(CSP_HEADER_MISSING, sourceContext)) {
@@ -213,10 +213,10 @@ module.exports = function(core) {
213
213
  }
214
214
  };
215
215
 
216
- responseScanning.handleHstsHeaderMissing = function(sourceContext, { responseHeaders }) {
216
+ responseScanning.handleHstsHeaderMissing = function(sourceContext, resHeaders) {
217
217
  if (!isEnabled(HSTS_HEADER_MISSING, sourceContext)) return;
218
218
 
219
- let header = responseHeaders['strict-transport-security'];
219
+ let header = resHeaders['strict-transport-security'];
220
220
  let maxAge;
221
221
 
222
222
  if (header) {
@@ -245,11 +245,11 @@ module.exports = function(core) {
245
245
  }
246
246
  };
247
247
 
248
- responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, { responseHeaders }) {
248
+ responseScanning.handleXContentTypeHeaderMissing = function(sourceContext, resHeaders) {
249
249
  if (!isEnabled(XCONTENTTYPE_HEADER_MISSING, sourceContext)) return;
250
250
 
251
251
  const headerName = 'x-content-type-options';
252
- let header = responseHeaders[headerName];
252
+ let header = resHeaders[headerName];
253
253
 
254
254
  if (header) {
255
255
  header = StringPrototypeToLowerCase.call(header);
@@ -266,11 +266,11 @@ module.exports = function(core) {
266
266
  });
267
267
  };
268
268
 
269
- responseScanning.handleXPoweredByHeader = function(sourceContext, { responseHeaders }) {
269
+ responseScanning.handleXPoweredByHeader = function(sourceContext, resHeaders) {
270
270
  if (!isEnabled(X_POWERED_BY_HEADER, sourceContext)) return;
271
271
 
272
272
  const headerName = 'x-powered-by';
273
- let header = responseHeaders[headerName];
273
+ let header = resHeaders[headerName];
274
274
 
275
275
  if (header) {
276
276
  header = StringPrototypeToLowerCase.call(header);
@@ -284,7 +284,7 @@ module.exports = function(core) {
284
284
  }
285
285
  };
286
286
 
287
- responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext, { responseHeaders }) {
287
+ responseScanning.handleXxsProtectionHeaderDisabled = function(sourceContext, responseHeaders) {
288
288
  if (!isEnabled(XXSPROTECTION_HEADER_DISABLED, sourceContext)) return;
289
289
 
290
290
  const header = responseHeaders['x-xss-protection'];
@@ -67,25 +67,25 @@ module.exports = function(core) {
67
67
 
68
68
  const patchType = 'response-scanning';
69
69
 
70
- function writeHookChecks(sourceContext, evaluationContext) {
70
+ function writeHookChecks(sourceContext, resHeaders, resBody) {
71
71
  // Check only the rules concerning the response body
72
- handleAutoCompleteMissing(sourceContext, evaluationContext);
73
- handleCacheControlsMissing(sourceContext, evaluationContext);
74
- handleParameterPollution(sourceContext, evaluationContext);
75
- handleXxsProtectionHeaderDisabled(sourceContext, evaluationContext);
72
+ handleAutoCompleteMissing(sourceContext, resHeaders, resBody);
73
+ handleCacheControlsMissing(sourceContext, resHeaders, resBody);
74
+ handleParameterPollution(sourceContext, resBody);
75
+ handleXxsProtectionHeaderDisabled(sourceContext, resHeaders, resBody);
76
76
  }
77
77
 
78
- function endHookChecks(sourceContext, evaluationContext) {
78
+ function endHookChecks(sourceContext, resHeaders, resBody) {
79
79
  // Check all the response scanning rules
80
- handleAutoCompleteMissing(sourceContext, evaluationContext);
81
- handleCacheControlsMissing(sourceContext, evaluationContext);
82
- handleClickJackingControlsMissing(sourceContext, evaluationContext);
83
- handleParameterPollution(sourceContext, evaluationContext);
84
- handleCspHeader(sourceContext, evaluationContext);
85
- handleHstsHeaderMissing(sourceContext, evaluationContext);
86
- handleXPoweredByHeader(sourceContext, evaluationContext);
87
- handleXContentTypeHeaderMissing(sourceContext, evaluationContext);
88
- handleXxsProtectionHeaderDisabled(sourceContext, evaluationContext);
80
+ handleAutoCompleteMissing(sourceContext, resHeaders, resBody);
81
+ handleCacheControlsMissing(sourceContext, resHeaders, resBody);
82
+ handleClickJackingControlsMissing(sourceContext, resHeaders, resBody);
83
+ handleParameterPollution(sourceContext, resBody);
84
+ handleCspHeader(sourceContext, resHeaders);
85
+ handleHstsHeaderMissing(sourceContext, resHeaders);
86
+ handleXPoweredByHeader(sourceContext, resHeaders);
87
+ handleXContentTypeHeaderMissing(sourceContext, resHeaders);
88
+ handleXxsProtectionHeaderDisabled(sourceContext, resHeaders);
89
89
  }
90
90
 
91
91
  http.install = function() {
@@ -98,13 +98,7 @@ module.exports = function(core) {
98
98
  post(data) {
99
99
  const sourceContext = getSourceContext();
100
100
  if (!sourceContext) return;
101
-
102
- const evaluationContext = {
103
- responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
104
- responseHeaders: parseHeaders(data.result._header),
105
- };
106
-
107
- writeHookChecks(sourceContext, evaluationContext);
101
+ writeHookChecks(sourceContext, parseHeaders(data.result._header), StringPrototypeToLowerCase.call(data.args[0] || ''));
108
102
  }
109
103
  });
110
104
  }
@@ -116,20 +110,14 @@ module.exports = function(core) {
116
110
  post(data) {
117
111
  const sourceContext = getSourceContext();
118
112
  if (!sourceContext) return;
119
-
120
- const evaluationContext = {
121
- responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
122
- responseHeaders: parseHeaders(data.result._header),
123
- };
124
-
125
- endHookChecks(sourceContext, evaluationContext);
113
+ endHookChecks(sourceContext, parseHeaders(data.result._header), StringPrototypeToLowerCase.call(data.args[0] || ''));
126
114
  }
127
115
  });
128
116
  }
129
117
  });
130
118
 
131
119
  depHooks.resolve({ name: 'http2' }, (http2) => {
132
- // Patching the response object
120
+ // todo: NODE-3467
133
121
  {
134
122
  const name = 'http2.Http2ServerResponse.prototype.write';
135
123
  patcher.patch(http2.Http2ServerResponse.prototype, 'write', {
@@ -138,14 +126,8 @@ module.exports = function(core) {
138
126
  post(data) {
139
127
  const sourceContext = getSourceContext();
140
128
  if (!sourceContext) return;
141
-
142
129
  const headersSymbol = Object.getOwnPropertySymbols(data.obj).find(symbol => symbol.toString().includes('headers'));
143
- const evaluationContext = {
144
- responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
145
- responseHeaders: data.obj[headersSymbol],
146
- };
147
-
148
- writeHookChecks(sourceContext, evaluationContext);
130
+ writeHookChecks(sourceContext, data.obj[headersSymbol], StringPrototypeToLowerCase.call(data.args[0] || ''));
149
131
  }
150
132
  });
151
133
  }
@@ -159,20 +141,24 @@ module.exports = function(core) {
159
141
  if (!sourceContext) return;
160
142
 
161
143
  const headersSymbol = Object.getOwnPropertySymbols(data.result).find(symbol => symbol.toString().includes('headers'));
162
- const evaluationContext = {
163
- responseBody: StringPrototypeToLowerCase.call(data.args[0] || ''),
164
- responseHeaders: data.result[headersSymbol],
165
- };
166
-
167
- endHookChecks(sourceContext, evaluationContext);
144
+ endHookChecks(sourceContext, data.result[headersSymbol], StringPrototypeToLowerCase.call(data.args[0] || ''));
168
145
  }
169
146
  });
170
147
  }
148
+ });
171
149
 
172
- // todo: patching the stream object (NODE-3467)
150
+ depHooks.resolve({ name: 'spdy', file: 'lib/spdy/response.js' }, (response) => {
151
+ patcher.patch(response, 'end', {
152
+ name: 'spdy.response.end',
153
+ patchType: 'test',
154
+ post(data) {
155
+ const sourceContext = getSourceContext();
156
+ if (!sourceContext) return;
157
+ endHookChecks(sourceContext, data.obj.getHeaders?.(), StringPrototypeToLowerCase.call(data.args[0] || ''));
158
+ }
159
+ });
173
160
  });
174
161
  };
175
162
 
176
163
  return http;
177
164
  };
178
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "Contrast service providing framework-agnostic Assess support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -17,7 +17,7 @@
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/common": "1.21.1",
20
+ "@contrast/common": "1.21.3",
21
21
  "@contrast/distringuish": "^5.0.0",
22
22
  "@contrast/scopes": "1.4.1"
23
23
  }