@contrast/protect 1.65.0 → 1.67.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.
@@ -18,6 +18,7 @@
18
18
  const {
19
19
  BLOCKING_MODES,
20
20
  isString,
21
+ InputType,
21
22
  Rule: { UNTRUSTED_DESERIALIZATION }
22
23
  } = require('@contrast/common');
23
24
 
@@ -25,6 +26,7 @@ const NODE_SERIALIZE_RCE_TOKEN = '_$$ND_FUNC$$_';
25
26
 
26
27
  module.exports = function(core) {
27
28
  const {
29
+ protect,
28
30
  protect: {
29
31
  hardening,
30
32
  throwSecurityException,
@@ -40,32 +42,47 @@ module.exports = function(core) {
40
42
  return results;
41
43
  }
42
44
 
43
- hardening.handleUntrustedDeserialization = function(sourceContext, sinkContext) {
44
- const ruleId = UNTRUSTED_DESERIALIZATION;
45
+ function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
46
+ const { stacktraceOpts } = sinkContext;
47
+ captureStacktrace(sinkContext, stacktraceOpts);
48
+
45
49
  const mode = sourceContext.policy[ruleId];
46
- const { name, value, stacktraceOpts } = sinkContext;
50
+ getResults(sourceContext, ruleId).push(result);
47
51
 
48
- if (mode === 'off') return;
52
+ let blockInfo;
53
+ if (BLOCKING_MODES.includes(mode)) {
54
+ result.blocked = true;
55
+ blockInfo = [mode, ruleId];
56
+ sourceContext.securityException = blockInfo;
57
+ }
49
58
 
50
- if (name === 'node-serialize.unserialize') {
51
- if (!isString(value) || !value.indexOf(NODE_SERIALIZE_RCE_TOKEN)) return;
59
+ protect.reportFinding({ findings, result, sinkContext });
52
60
 
53
- const blocked = BLOCKING_MODES.includes(mode);
54
- const results = getResults(sourceContext, ruleId);
61
+ if (blockInfo) throwSecurityException(sourceContext);
62
+ }
55
63
 
56
- captureStacktrace(sinkContext, stacktraceOpts);
57
- results.push({
58
- value: sinkContext.value,
59
- blocked,
60
- exploitMetadata: [{ deserializer: name, command: false }],
61
- sinkContext,
62
- });
64
+ hardening.handleUntrustedDeserialization = function (sourceContext, sinkContext) {
65
+ const ruleId = UNTRUSTED_DESERIALIZATION;
66
+ const mode = sourceContext.policy[ruleId];
67
+ const { name, value } = sinkContext;
63
68
 
64
- if (blocked) {
65
- sourceContext.securityException = [mode, ruleId];
66
- throwSecurityException(sourceContext);
67
- }
68
- }
69
+ if (
70
+ mode === 'off' ||
71
+ name !== 'node-serialize.unserialize' ||
72
+ !isString(value) ||
73
+ !value.indexOf(NODE_SERIALIZE_RCE_TOKEN)
74
+ ) return;
75
+
76
+ const result = {
77
+ value: sinkContext.value,
78
+ ruleId: UNTRUSTED_DESERIALIZATION,
79
+ blocked: false,
80
+ exploited: true,
81
+ inputType: InputType.UNKNOWN,
82
+ };
83
+ const findings = { deserializer: name, command: false };
84
+
85
+ handleFindings(sourceContext, sinkContext, ruleId, result, findings);
69
86
  };
70
87
 
71
88
  return hardening;
package/lib/index.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * way not consistent with the End User License Agreement.
14
14
  */
15
15
 
16
- import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode, Blocker } from '@contrast/common';
16
+ import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode, Blocker, ProtectFindingEventArg } from '@contrast/common';
17
17
  import { IncomingMessage, ServerResponse } from 'node:http';
18
18
  import * as http from 'node:http';
19
19
  import * as https from 'node:https';
@@ -60,7 +60,8 @@ export interface Protect {
60
60
  makeSourceContext: (req: IncomingMessage, res: ServerResponse) => ProtectRequestStore,
61
61
  throwSecurityException: (sourceContext: ProtectRequestStore) => void,
62
62
  policy: ProtectPolicy,
63
- getPolicy(): ProtectPolicy, // creates copy for request scope
63
+ getPolicy: () => ProtectPolicy, // creates copy for request scope
64
+ reportFindings: (e: ProtectFindingEventArg) => void,
64
65
  inputAnalysis: {
65
66
  handleConnect: (sourceContext: ProtectRequestStore, connectInputs: ConnectInputs) => undefined | [string, string],
66
67
  handleRequestEnd: (sourceContext: ProtectRequestStore) => void, //NYI
package/lib/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  const { isMainThread } = require('node:worker_threads');
19
- const { callChildComponentMethodsSync } = require('@contrast/common');
19
+ const { callChildComponentMethodsSync, Event } = require('@contrast/common');
20
20
  const { ConfigSource } = require('@contrast/config');
21
21
 
22
22
  module.exports = function(core) {
@@ -37,7 +37,7 @@ module.exports = function(core) {
37
37
  require('./hardening')(core);
38
38
  require('./semantic-analysis')(core);
39
39
 
40
- protect.install = function() {
40
+ protect.install = function install() {
41
41
  // only force instrumentation if assess is explicitly enabled in local config
42
42
  const forceInstrumentation =
43
43
  config.preinstrument &&
@@ -60,6 +60,13 @@ module.exports = function(core) {
60
60
  ctx.store.protect = protect.makeSourceContext(ctx);
61
61
  });
62
62
 
63
+ protect.reportFinding = function reportFinding(data) {
64
+ core.messages.emit(Event.PROTECT_FINDING, {
65
+ store: core.scopes.sources.getStore(),
66
+ ...data,
67
+ });
68
+ };
69
+
63
70
  return protect;
64
71
  };
65
72
 
@@ -100,6 +100,7 @@ module.exports = Core.makeComponent({
100
100
  factory(core) {
101
101
  const {
102
102
  logger,
103
+ protect,
103
104
  protect: {
104
105
  agentLib,
105
106
  inputAnalysis,
@@ -183,6 +184,10 @@ module.exports = Core.makeComponent({
183
184
  block = inputAnalysis.handleMethodTampering(sourceContext, connectInputs);
184
185
  }
185
186
 
187
+ if (block) {
188
+ core.protect.reportFinding(block[2]);
189
+ }
190
+
186
191
  return block;
187
192
  };
188
193
 
@@ -216,6 +221,7 @@ module.exports = Core.makeComponent({
216
221
  const block = commonObjectAnalyzer(sourceContext, queryParams, parameterInputTypes);
217
222
 
218
223
  if (block) {
224
+ core.protect.reportFinding(block[2]);
219
225
  core.protect.throwSecurityException(sourceContext);
220
226
  }
221
227
  };
@@ -284,6 +290,9 @@ module.exports = Core.makeComponent({
284
290
  const block = mergeFindings(sourceContext, urlParamsFindings);
285
291
 
286
292
  if (block) {
293
+ if (block[2]) {
294
+ core.protect.reportFinding(block[2]);
295
+ }
287
296
  core.protect.throwSecurityException(sourceContext);
288
297
  }
289
298
  };
@@ -312,9 +321,11 @@ module.exports = Core.makeComponent({
312
321
 
313
322
  const cookieFindings = agentLib.scoreRequestConnect(rulesMask, { cookies: cookiesArr }, preferWW);
314
323
 
324
+
315
325
  const block = mergeFindings(sourceContext, cookieFindings);
316
326
 
317
327
  if (block) {
328
+ protect.reportFinding(block[2]);
318
329
  core.protect.throwSecurityException(sourceContext);
319
330
  }
320
331
  };
@@ -356,6 +367,7 @@ module.exports = Core.makeComponent({
356
367
  sourceContext.bodyType = bodyType;
357
368
 
358
369
  if (block) {
370
+ protect.reportFinding(block[2]);
359
371
  core.protect.throwSecurityException(sourceContext);
360
372
  }
361
373
  };
@@ -402,6 +414,7 @@ module.exports = Core.makeComponent({
402
414
  const block = mergeFindings(sourceContext, unsafeFilenameFindings);
403
415
 
404
416
  if (block) {
417
+ core.protect.reportFinding(block[2]);
405
418
  core.protect.throwSecurityException(sourceContext);
406
419
  }
407
420
  };
@@ -423,10 +436,17 @@ module.exports = Core.makeComponent({
423
436
  if (!sourceContext.resultsMap[ruleId]) {
424
437
  sourceContext.resultsMap[ruleId] = [];
425
438
  }
426
- sourceContext.resultsMap[ruleId].push({
427
- name,
428
- uuid
429
- });
439
+
440
+ const result = {
441
+ key: name,
442
+ inputType: 'UNKNOWN',
443
+ ruleId: Rule.VIRTUAL_PATCH,
444
+ value: 'Virtual Patch',
445
+ blocked: true,
446
+ };
447
+ const eventArg = { result, findings: { uuid } };
448
+
449
+ protect.reportFinding(eventArg);
430
450
  sourceContext.securityException = ['block', ruleId];
431
451
  core.protect.throwSecurityException(sourceContext);
432
452
  }
@@ -453,7 +473,7 @@ module.exports = Core.makeComponent({
453
473
  if (!sourceContext || !ipDenylist.length) return;
454
474
 
455
475
  const { sourceInfo } = core.scopes.sources.getStore();
456
- const match = ipListAnalysis(sourceInfo.Ip, sourceInfo.rawHeaders, ipDenylist);
476
+ const match = ipListAnalysis(sourceInfo.ip, sourceInfo.rawHeaders, ipDenylist);
457
477
 
458
478
  if (match) {
459
479
  logger.info(match, 'Found a matching IP to an entry in ipDeny list');
@@ -461,10 +481,21 @@ module.exports = Core.makeComponent({
461
481
  sourceContext.resultsMap[ruleId] = [];
462
482
  }
463
483
 
464
- sourceContext.resultsMap[ruleId].push({
465
- ip: match.matchedIp,
466
- uuid: match.uuid,
467
- });
484
+ const eventArg = {
485
+ result: {
486
+ key: 'IP Address',
487
+ inputType: 'UNKNOWN',
488
+ ruleId: Rule.IP_DENYLIST,
489
+ value: sourceInfo.ip,
490
+ blocked: true,
491
+ },
492
+ findings: {
493
+ uuid: match.uuid,
494
+ ip: match.matchedIp,
495
+ },
496
+ };
497
+ protect.reportFinding(eventArg);
498
+
468
499
  return ['block', 'ip-denylist'];
469
500
  }
470
501
  };
@@ -481,14 +512,14 @@ module.exports = Core.makeComponent({
481
512
  key: 'method',
482
513
  value: method,
483
514
  blocked: false,
484
- exploitMetadata: null,
485
515
  };
486
516
 
487
517
  sourceContext.resultsMap[ruleId] = [result];
488
518
 
489
519
  if (BLOCKING_MODES.includes(mode)) {
520
+ result.exploited = true;
490
521
  result.blocked = true;
491
- return sourceContext.securityException = ['block', ruleId];
522
+ return sourceContext.securityException = ['block', ruleId, { result }];
492
523
  }
493
524
  }
494
525
  }
@@ -502,24 +533,24 @@ module.exports = Core.makeComponent({
502
533
  * @param {Object} sourceContext
503
534
  */
504
535
  inputAnalysis.handleRequestEnd = function handleRequestEnd(sourceContext) {
505
- {
506
- // check status code to verify method-tampering exploitation
507
- const mtResult = sourceContext.resultsMap[Rule.METHOD_TAMPERING]?.[0];
508
- if (mtResult) {
509
- const { statusCode } = sourceContext.resData;
510
- if (statusCode !== 405 || statusCode !== 501) {
511
- mtResult.exploitMetadata = [{ statusCode }];
512
- }
536
+ // check status code to verify method-tampering exploitation
537
+ const mtResult = sourceContext.resultsMap[Rule.METHOD_TAMPERING]?.[0];
538
+ if (mtResult) {
539
+ const { statusCode } = sourceContext.resData;
540
+ if (statusCode !== 405 || statusCode !== 501) {
541
+ mtResult.exploited = true;
542
+ protect.reportFindings({ result: mtResult, finding: { statusCode } });
513
543
  }
514
544
  }
515
545
 
516
546
  if (!config.protect.probe_analysis.enable) return;
517
547
 
548
+ const probeReports = [];
549
+
518
550
  // Detecting probes
519
551
  const { resultsMap, policy: { rulesMask } } = sourceContext;
520
552
  const probesRules = [Rule.CMD_INJECTION, Rule.PATH_TRAVERSAL, Rule.SQL_INJECTION, Rule.XXE];
521
553
  const probes = {};
522
-
523
554
  const findingsForScoreRequest = {
524
555
  HeaderValue: {},
525
556
  ParameterValue: {},
@@ -532,7 +563,7 @@ module.exports = Core.makeComponent({
532
563
  resultsByRuleId.forEach(resultByRuleId => {
533
564
  const {
534
565
  ruleId,
535
- exploitMetadata,
566
+ exploited,
536
567
  score,
537
568
  value,
538
569
  key,
@@ -541,9 +572,10 @@ module.exports = Core.makeComponent({
541
572
 
542
573
  if (
543
574
  !isMonitorMode(ruleId, sourceContext) ||
544
- exploitMetadata.length > 0 ||
575
+ exploited === true || // todo: remove
545
576
  score >= 90 ||
546
- !probesRules.some((rule) => rule === ruleId)
577
+ !probesRules.some((rule) => rule === ruleId) ||
578
+ inputType == InputType.UNKNOWN
547
579
  ) {
548
580
  return;
549
581
  }
@@ -562,9 +594,7 @@ module.exports = Core.makeComponent({
562
594
  valueToResultByRuleId[value] = resultByRuleId;
563
595
  });
564
596
  });
565
-
566
597
  const { ParameterValue, HeaderValue, CookieValue } = findingsForScoreRequest;
567
-
568
598
  const results =
569
599
  agentLib.scoreRequestConnect(
570
600
  rulesMask,
@@ -579,16 +609,17 @@ module.exports = Core.makeComponent({
579
609
  ).resultsList || [];
580
610
 
581
611
  Object.entries(findingsForScoreAtom).forEach(([value, inputTypes]) => {
582
- Object.entries(inputTypes).forEach(([inputType, resultByRuleId]) =>
583
- (
584
- agentLib.scoreAtom(rulesMask, value, agentLib.InputType[inputType], {
585
- preferWorthWatching: false,
586
- }) || []
587
- ).forEach(result => {
612
+ Object.entries(inputTypes).forEach(([inputType, resultByRuleId]) => {
613
+ if (agentLib.InputType[inputType] == null) return;
614
+ const alibResult = agentLib.scoreAtom(rulesMask, value, agentLib.InputType[inputType], {
615
+ preferWorthWatching: false,
616
+ }) || [];
617
+ alibResult.forEach(result => {
588
618
  results.push({ value, ...result });
619
+ probeReports.push({ value, ...result });
589
620
  valueToResultByRuleId[value] = resultByRuleId;
590
- })
591
- );
621
+ });
622
+ });
592
623
  });
593
624
 
594
625
  results
@@ -613,7 +644,12 @@ module.exports = Core.makeComponent({
613
644
  }
614
645
 
615
646
  resultsMap[probe.ruleId].push(probe);
647
+ probeReports.push(probe);
616
648
  });
649
+
650
+ for (const result of probeReports) {
651
+ core.protect.reportFinding({ result });
652
+ }
617
653
  };
618
654
 
619
655
  /**
@@ -874,11 +910,11 @@ function normalizeFindings(policy, findings) {
874
910
  // what additional augmentations are needed?
875
911
  // the name/id might need to be mapped but keep the original so it's not lost
876
912
  r.mappedId = agentLibRuleTypeToName[r.ruleId] || r.ruleId;
877
- // this finding resulted in blocking, i.e., it is not a probe.
878
- r.blocked = false;
879
913
 
880
- // sink analysis will add findings here
881
- r.exploitMetadata = [];
914
+ // if we block this or the value is found in sink, we'll know not to check
915
+ // this result for probe analysis in handleRequestEnd().
916
+ r.blocked = false;
917
+ r.exploited = false;
882
918
 
883
919
  // apply exclusions here.
884
920
  //
@@ -893,7 +929,7 @@ function normalizeFindings(policy, findings) {
893
929
  const mode = policy[r.ruleId];
894
930
  if (r.score >= 90 && BLOCKING_MODES.includes(mode)) {
895
931
  r.blocked = true;
896
- findings.securityException = [mode, r.ruleId];
932
+ findings.securityException = [mode, r.ruleId, { result: r }];
897
933
  }
898
934
  }
899
935
  }
@@ -16,13 +16,12 @@
16
16
  'use strict';
17
17
 
18
18
  const onFinished = require('on-finished');
19
- const { Event, primordials: { StringPrototypeToLowerCase, ArrayPrototypeSlice } } = require('@contrast/common');
19
+ const { primordials: { StringPrototypeToLowerCase, ArrayPrototypeSlice } } = require('@contrast/common');
20
20
  const { patchType } = require('../constants');
21
21
 
22
22
  module.exports = function (core) {
23
23
  const {
24
24
  logger,
25
- messages,
26
25
  scopes: { sources },
27
26
  instrumentation: { instrument },
28
27
  protect: {
@@ -75,8 +74,8 @@ module.exports = function (core) {
75
74
 
76
75
  onFinished(res, (/* err, req */) => {
77
76
  resData.statusCode = res.statusCode;
77
+ // check for probes and method-tampering outcome
78
78
  inputAnalysis.handleRequestEnd(store.protect);
79
- messages.emit(Event.PROTECT, store);
80
79
  });
81
80
 
82
81
  const connectInputs = {
@@ -112,7 +111,7 @@ module.exports = function (core) {
112
111
  }
113
112
 
114
113
  function install() {
115
- ['http', 'https', 'spdy', 'http2'].forEach((moduleName) => {
114
+ ['http', 'https', 'http2'].forEach((moduleName) => {
116
115
  instrument({
117
116
  moduleName,
118
117
  patchObjects: [{
@@ -29,6 +29,7 @@ const {
29
29
 
30
30
  module.exports = function(core) {
31
31
  const {
32
+ protect,
32
33
  protect: {
33
34
  agentLib,
34
35
  inputTracing,
@@ -40,16 +41,21 @@ module.exports = function(core) {
40
41
  function handleFindings(sourceContext, sinkContext, ruleId, result, findings) {
41
42
  const { stacktraceOpts } = sinkContext;
42
43
  captureStacktrace(sinkContext, stacktraceOpts);
43
- result.exploitMetadata.push({ sinkContext, findings });
44
+ result.exploited = true;
44
45
 
45
46
  const mode = sourceContext.policy[ruleId];
47
+ const eventArg = { findings, result, sinkContext };
46
48
 
49
+ let blockInfo;
47
50
  if (BLOCKING_MODES.includes(mode)) {
48
51
  result.blocked = true;
49
- const blockInfo = [mode, ruleId];
52
+ blockInfo = [mode, ruleId, eventArg];
50
53
  sourceContext.securityException = blockInfo;
51
- throwSecurityException(sourceContext);
52
54
  }
55
+
56
+ protect.reportFinding(eventArg);
57
+
58
+ if (blockInfo) throwSecurityException(sourceContext);
53
59
  }
54
60
 
55
61
  inputTracing.handlePathTraversal = function(sourceContext, sinkContext) {
@@ -61,7 +67,6 @@ module.exports = function(core) {
61
67
  for (const result of results) {
62
68
  const idx = sinkContext.value.indexOf(result.value);
63
69
  const findings = idx !== -1 ? { path: sinkContext.value } : null;
64
-
65
70
  if (findings) {
66
71
  handleFindings(sourceContext, sinkContext, ruleId, result, findings);
67
72
  }
@@ -218,13 +223,7 @@ module.exports = function(core) {
218
223
  }
219
224
 
220
225
  if (stringFindings) {
221
- const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId };
222
-
223
- // don't modify ssjs-injection result items so use new exploit metadata array here
224
- if (nosqlInjectionResult.idsList?.some?.((id) => id.startsWith('SSJS'))) {
225
- nosqlInjectionResult.exploitMetadata = [];
226
- }
227
-
226
+ const nosqlInjectionResult = { ...result, ruleId, mappedId: ruleId, exploited: false };
228
227
  const nosqlInjectionResults = sourceContext.resultsMap[ruleId];
229
228
  const isAlreadyPresentInNosqlresults = result.idsList &&
230
229
  result.idsList.some(
@@ -312,12 +311,13 @@ module.exports = function(core) {
312
311
  const findings = idx !== -1 ? { value: sinkContext.value } : null;
313
312
 
314
313
  if (findings) {
315
- result.exploitMetadata.push({ sinkContext, findings });
314
+ result.exploited = true;
315
+ handleFindings(sourceContext, sinkContext, ruleId, result, findings);
316
+ break;
316
317
  }
317
318
  }
318
319
  };
319
320
 
320
-
321
321
  return inputTracing;
322
322
  };
323
323
 
@@ -36,7 +36,6 @@ module.exports = function(core) {
36
36
  require('./install/mysql')(core);
37
37
  require('./install/postgres')(core);
38
38
  require('./install/sequelize')(core);
39
- require('./install/spdy')(core);
40
39
  require('./install/sqlite3')(core);
41
40
  require('./install/vm')(core);
42
41
  // TODO: NODE-2360 (oracledb)
@@ -44,6 +44,7 @@ const getRuleResults = function(obj, prop) {
44
44
 
45
45
  module.exports = function(core) {
46
46
  const {
47
+ protect,
47
48
  protect: {
48
49
  agentLib,
49
50
  semanticAnalysis,
@@ -52,27 +53,32 @@ module.exports = function(core) {
52
53
  captureStacktrace,
53
54
  } = core;
54
55
 
55
- function handleResult(sourceContext, sinkContext, ruleId, mode, finding) {
56
+ function handleResult(sourceContext, sinkContext, ruleId, mode, findings) {
56
57
  const { value, stacktraceOpts } = sinkContext;
57
58
  captureStacktrace(sinkContext, stacktraceOpts);
58
59
 
59
60
  // shoehorn findings into agent-lib result data model
60
61
  const result = {
61
62
  blocked: false,
63
+ inputType: InputType.UNKNOWN,
62
64
  ruleId,
63
65
  value,
64
66
  mappedId: ruleId,
65
- exploitMetadata: [{ sinkContext, command: value }],
66
- ...finding
67
+ exploited: true,
67
68
  };
69
+
68
70
  getRuleResults(sourceContext.resultsMap, ruleId).push(result);
69
71
 
72
+ let blockInfo;
70
73
  if (BLOCKING_MODES.includes(mode)) {
71
74
  result.blocked = true;
72
- const blockInfo = [mode, ruleId];
75
+ blockInfo = [mode, ruleId];
73
76
  sourceContext.securityException = blockInfo;
74
- throwSecurityException(sourceContext);
75
77
  }
78
+
79
+ protect.reportFinding({ findings, result, sinkContext });
80
+
81
+ if (blockInfo) throwSecurityException(sourceContext);
76
82
  }
77
83
 
78
84
  /**
@@ -180,7 +186,7 @@ module.exports = function(core) {
180
186
  const finding = findBackdoorInjection(sourceContext, sinkContext.value);
181
187
 
182
188
  if (finding) {
183
- handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_COMMAND_BACKDOORS, mode, finding);
189
+ handleResult(sourceContext, sinkContext, Rule.CMD_INJECTION_COMMAND_BACKDOORS, mode);
184
190
  }
185
191
  };
186
192
 
@@ -190,9 +196,7 @@ module.exports = function(core) {
190
196
  if (mode == OFF) return;
191
197
 
192
198
  if (agentLib.isDangerousPath(sinkContext.value, true)) {
193
- handleResult(sourceContext, sinkContext, Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS, mode, {
194
- exploitMetadata: [{ sinkContext, path: sinkContext.value }]
195
- });
199
+ handleResult(sourceContext, sinkContext, Rule.PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS, mode);
196
200
  }
197
201
  };
198
202
 
@@ -202,9 +206,7 @@ module.exports = function(core) {
202
206
 
203
207
  const findings = findExternalEntities(sinkContext.value);
204
208
  if (findings.entities.length) {
205
- handleResult(sourceContext, sinkContext, Rule.XXE, mode, {
206
- exploitMetadata: [{ sinkContext, ...findings }],
207
- });
209
+ handleResult(sourceContext, sinkContext, Rule.XXE, mode, findings);
208
210
  }
209
211
  };
210
212
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.65.0",
3
+ "version": "1.67.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -14,23 +14,23 @@
14
14
  "types": "lib/index.d.ts",
15
15
  "engines": {
16
16
  "npm": ">=6.13.7 <7 || >= 8.3.1",
17
- "node": ">= 16.9.1"
17
+ "node": ">= 18.7.0"
18
18
  },
19
19
  "scripts": {
20
20
  "test": "bash ../scripts/test.sh"
21
21
  },
22
22
  "dependencies": {
23
23
  "@contrast/agent-lib": "^9.1.0",
24
- "@contrast/common": "1.35.0",
25
- "@contrast/config": "1.50.0",
26
- "@contrast/core": "1.55.0",
27
- "@contrast/dep-hooks": "1.24.0",
28
- "@contrast/esm-hooks": "2.29.0",
29
- "@contrast/instrumentation": "1.34.0",
30
- "@contrast/logger": "1.28.0",
31
- "@contrast/patcher": "1.27.0",
32
- "@contrast/rewriter": "1.31.0",
33
- "@contrast/scopes": "1.25.0",
24
+ "@contrast/common": "1.37.0",
25
+ "@contrast/config": "1.52.0",
26
+ "@contrast/core": "1.57.0",
27
+ "@contrast/dep-hooks": "1.26.0",
28
+ "@contrast/esm-hooks": "2.31.0",
29
+ "@contrast/instrumentation": "1.36.0",
30
+ "@contrast/logger": "1.30.0",
31
+ "@contrast/patcher": "1.29.0",
32
+ "@contrast/rewriter": "1.33.0",
33
+ "@contrast/scopes": "1.27.0",
34
34
  "async-hook-domain": "^4.0.1",
35
35
  "ipaddr.js": "^2.0.1",
36
36
  "on-finished": "^2.4.1",
@@ -1,63 +0,0 @@
1
- /*
2
- * Copyright: 2025 Contrast Security, Inc
3
- * Contact: support@contrastsecurity.com
4
- * License: Commercial
5
-
6
- * NOTICE: This Software and the patented inventions embodied within may only be
7
- * used as part of Contrast Security’s commercial offerings. Even though it is
8
- * made available through public repositories, use of this Software is subject to
9
- * the applicable End User Licensing Agreement found at
10
- * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
- * between Contrast Security and the End User. The Software may not be reverse
12
- * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
- * way not consistent with the End User License Agreement.
14
- */
15
-
16
- 'use strict';
17
-
18
- const { patchType } = require('../constants');
19
-
20
- module.exports = function(core) {
21
- const {
22
- scopes: { instrumentation },
23
- patcher,
24
- depHooks,
25
- protect,
26
- protect: { inputTracing }
27
- } = core;
28
-
29
- function install() {
30
- depHooks.resolve({ name: 'spdy', version: '<5' }, spdy => {
31
- const name = 'spdy.response.end';
32
- patcher.patch(spdy.response, 'end', {
33
- name,
34
- patchType,
35
- pre({ args, obj: response, hooked }) {
36
- if (instrumentation.isLocked()) return;
37
-
38
- const sourceContext = protect.getSourceContext();
39
-
40
- if (!sourceContext) return;
41
-
42
- const value = args[0]?.toString();
43
- if (!value) return;
44
-
45
- const sinkContext = {
46
- name,
47
- value,
48
- stacktraceOpts: { constructorOpt: hooked },
49
- };
50
-
51
- response.spdyStream.once('finish', () => response.emit('finish'));
52
- inputTracing.handleReflectedXss(sourceContext, sinkContext);
53
- }
54
- });
55
- });
56
- }
57
-
58
- const spdyInstrumentation = inputTracing.spdyInstrumentation = {
59
- install
60
- };
61
-
62
- return spdyInstrumentation;
63
- };