@contrast/agent 4.12.0 → 4.13.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 (41) hide show
  1. package/bin/VERSION +1 -1
  2. package/bin/linux/contrast-service +0 -0
  3. package/bin/mac/contrast-service +0 -0
  4. package/bin/windows/contrast-service.exe +0 -0
  5. package/esm.mjs +1 -32
  6. package/lib/assess/models/base-event.js +1 -1
  7. package/lib/assess/sinks/dynamo.js +65 -30
  8. package/lib/assess/static/read-findings-from-cache.js +40 -0
  9. package/lib/assess/technologies/index.js +12 -13
  10. package/lib/cli-rewriter/index.js +65 -6
  11. package/lib/contrast.js +1 -2
  12. package/lib/core/config/options.js +6 -0
  13. package/lib/core/config/util.js +15 -33
  14. package/lib/core/exclusions/input.js +6 -1
  15. package/lib/core/express/index.js +2 -4
  16. package/lib/hooks/http.js +81 -81
  17. package/lib/hooks/require.js +1 -0
  18. package/lib/instrumentation.js +17 -0
  19. package/lib/protect/errors/handler-async-errors.js +66 -0
  20. package/lib/protect/input-analysis.js +7 -13
  21. package/lib/protect/listeners.js +27 -23
  22. package/lib/protect/rules/base-scanner/index.js +2 -2
  23. package/lib/protect/rules/bot-blocker/bot-blocker-rule.js +4 -2
  24. package/lib/protect/rules/cmd-injection/cmdinjection-rule.js +57 -2
  25. package/lib/protect/rules/cmd-injection-semantic-chained-commands/cmd-injection-semantic-chained-commands-rule.js +31 -2
  26. package/lib/protect/rules/cmd-injection-semantic-dangerous-paths/cmd-injection-semantic-dangerous-paths-rule.js +32 -2
  27. package/lib/protect/rules/index.js +42 -21
  28. package/lib/protect/rules/ip-denylist/ip-denylist-rule.js +2 -2
  29. package/lib/protect/rules/nosqli/nosql-injection-rule.js +103 -38
  30. package/lib/protect/rules/path-traversal/path-traversal-rule.js +3 -0
  31. package/lib/protect/rules/rule-factory.js +6 -6
  32. package/lib/protect/rules/signatures/signature.js +3 -0
  33. package/lib/protect/rules/sqli/sql-injection-rule.js +98 -5
  34. package/lib/protect/rules/sqli/sql-scanner/labels.json +0 -3
  35. package/lib/protect/rules/xss/reflected-xss-rule.js +3 -3
  36. package/lib/protect/sample-aggregator.js +65 -57
  37. package/lib/protect/service.js +709 -104
  38. package/lib/reporter/models/app-activity/sample.js +6 -0
  39. package/lib/reporter/ts-reporter.js +1 -1
  40. package/lib/util/get-file-type.js +47 -0
  41. package/package.json +5 -3
@@ -20,7 +20,7 @@ Copyright: 2022 Contrast Security, Inc
20
20
 
21
21
  const _ = require('lodash');
22
22
 
23
- const POINTS_PATH = 'sample.assessment.results.points';
23
+ const { IMPORTANCE } = require('../constants');
24
24
 
25
25
  /**
26
26
  * Compares two findings and returns the one which is
@@ -38,31 +38,26 @@ const rankEffectiveness = (a, b) => {
38
38
  return null;
39
39
  }
40
40
 
41
- /* Otherwise, we want the one that is effective. */
42
- return _.find([a, b], (f) => f.sample.effective);
41
+ // effective is a boolean, so there is no "higher" effectiveness.
42
+ return a.sample.effective ? a : b;
43
43
  };
44
44
 
45
45
  /**
46
- * Compares two findings and returns the one which is
47
- * ranked higher in terms of assessment score. If their
48
- * assessment score is the same, <code>null</code> is
49
- * returned, signifying that a ranking order cannot be
50
- * determined for this criteria.
46
+ * Compares two findings and returns the one which has the highest
47
+ * score. If their scores are the same, return the first finding.
51
48
  * @param {object} a First finding
52
49
  * @param {object} b Second finding
53
- * @returns {object | null}
50
+ * @returns {object}
54
51
  */
55
- const rankPoints = (a, b) => {
56
- const p1 = _.get(a, POINTS_PATH);
57
- const p2 = _.get(b, POINTS_PATH);
52
+ const POINTS_PATH = 'sample.assessment.results.points';
58
53
 
59
- /* If both findings share point values, we can't rank them. */
60
- if (p1 === p2) {
61
- return null;
62
- }
54
+ function rankPoints(a, b) {
55
+ const pA = _.get(a, POINTS_PATH);
56
+ const pB = _.get(b, POINTS_PATH);
63
57
 
64
- return p1 < p2 ? b : a;
65
- };
58
+ // return a if a is greater than or equal to b
59
+ return pA < pB ? b : a;
60
+ }
66
61
 
67
62
  /**
68
63
  * Ranks two findings against effectiveness and score.
@@ -72,64 +67,77 @@ const rankPoints = (a, b) => {
72
67
  * @param {object} b Second finding
73
68
  * @returns {object} The highest-ranked finding
74
69
  */
75
- const rankFindings = (a, b) => rankEffectiveness(a, b) || rankPoints(a, b) || a;
70
+ const rankFindings = (a, b) => rankEffectiveness(a, b) || rankPoints(a, b);
76
71
 
77
72
  /**
78
- * Takes a collection of findings and organizes them into
79
- * a map having keys being unique input types and paths,
80
- * and values being unique findings pertaining to each key.
73
+ * Takes a collection of findings and organizes them into a map having keys
74
+ * being unique input types and paths, and values being unique findings pertaining
75
+ * to each key.
81
76
  * @param {object} memo The map being created
82
77
  * @param {object} finding Current finding in iteratee
83
78
  * @returns {object} Map of ranked findings per type/path
84
79
  */
85
- const rankIntoMap = (memo, finding) => {
80
+ function rankIntoMap(memo, finding) {
86
81
  const type = _.get(finding, 'sample.input.type', '_');
87
82
  const path = _.get(finding, 'sample.input.documentPath', '_');
88
83
  const name = _.get(finding, 'sample.input.name', '_');
89
84
 
90
- /* This is what defines uniqueness in reporting */
85
+ // this string is the key to the map
91
86
  const memoPath = `${type}.${path}.${name}`;
92
- const existing = memo[memoPath];
93
87
 
94
- memo[memoPath] = !existing ? finding : rankFindings(existing, finding);
88
+ const existing = memo[memoPath];
89
+ // if it exists, rank it against the new finding
90
+ memo[memoPath] = existing ? rankFindings(existing, finding) : finding;
95
91
 
92
+ // memo returned despite having side effects because this function is
93
+ // called using reduce.
96
94
  return memo;
97
- };
95
+ }
98
96
 
99
97
  /**
100
- * Functions for sample aggregation and ranking.
101
- */
102
- /**
103
- * From a collection of findings, will return a filtered
104
- * collection without low-scoring samples. Low-scoring
105
- * samples are not confirmed attacks. BUT, we DO want to
106
- * report on findings if they were effective, always.
107
- * @param {object[]} findings
108
- * @returns {object[]}
109
- */
110
- const filterLowScoring = (findings = []) =>
111
- _.filter(
112
- findings,
113
- (finding) =>
114
- // Virtual Patch findings do not have inputs but always create a WW sample.
115
- // Make sure they were effective.
116
- (!finding.sample.input && finding.sample.effective) ||
117
- /* Don't include low-scoring ineffective attacks. */
118
- !(!finding.sample.effective && !finding.sample.confirmedAttack)
119
- );
120
-
121
- /**
122
- * Accepts a collection of findings and returns a new
123
- * collection whose samples are unique with respect to
124
- * input type and path of the finding per input type. Any
125
- * duplicate findings for the same type and path will be
126
- * filtered to only include the one with the highest
127
- * points/effectiveness ranking.
98
+ * Accepts a collection of findings and returns a new collection whose samples
99
+ * are unique with respect to input type and path of the finding per input type.
100
+ * Any duplicate findings for the same type and path will be filtered to only
101
+ * include the one with the highest points/effectiveness ranking.
128
102
  * @param {object[]} findings The findings to aggregate.
103
+ * @param {function} wwFilter filter to check if a sample should be saved.
129
104
  * @returns {object[]}
105
+ *
106
+ * effective, but not blocked, becomes PROBED.
107
+ *
130
108
  */
131
- const aggregate = (findings = []) =>
132
- _.values(_.reduce(filterLowScoring(findings), rankIntoMap, {}));
109
+ function aggregate(findings, wwFilter) {
110
+ // separate the findings into effective and ineffective
111
+ // but marked as worth-watching by agent-lib. if not in
112
+ // one of the two lists, the finding is not relevant.
113
+ const effective = [];
114
+ const wwFindings = [];
115
+ // and the rest were not agent-lib worth-watching or effective
116
+ for (const finding of findings) {
117
+ const { sample } = finding;
118
+ if (sample.confirmedAttack || sample.effective) {
119
+ effective.push(finding);
120
+ } else if (finding.rule.agentLibBit && sample.assessment.results.importance === IMPORTANCE.WORTH_WATCHING) {
121
+ //console.log(finding.rule.id, s.results)
122
+ wwFindings.push(finding);
123
+ }
124
+ }
125
+
126
+ // do we need to run any inputs through the full scoring (not the abbreviated
127
+ // worth-watching scoring)?
128
+ if (wwFindings.length) {
129
+ for (const finding of wwFindings) {
130
+ if (wwFilter(finding)) {
131
+ finding.sample.assessment.results.importance = IMPORTANCE.DEFINITE;
132
+ // put this in the "effective list", even though it wasn't effective, so it
133
+ // will be reported as PROBED.
134
+ effective.push(finding);
135
+ }
136
+ }
137
+ }
138
+
139
+ return _.values(effective.reduce(rankIntoMap, {}));
140
+ }
133
141
 
134
142
  module.exports = {
135
143
  aggregate