@appland/scanner 1.60.0 → 1.61.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [@appland/scanner-v1.61.0](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.60.0...@appland/scanner-v1.61.0) (2022-07-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add participating events to each finding ([f3e8033](https://github.com/applandinc/appmap-js/commit/f3e80332833ec3305ef530d89b12763781a8c85b))
7
+
1
8
  # [@appland/scanner-v1.60.0](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.59.2...@appland/scanner-v1.60.0) (2022-06-30)
2
9
 
3
10
 
@@ -14,7 +14,7 @@ function default_1(secrets, e) {
14
14
  if ((0, util_1.verbose)()) {
15
15
  console.warn(`Secret generated: ${secret}`);
16
16
  }
17
- secrets.add(secret);
17
+ secrets.push({ generatorEvent: e, value: secret });
18
18
  }
19
19
  }
20
20
  exports.default = default_1;
@@ -94,8 +94,9 @@ class RuleChecker {
94
94
  if (!checkInstance.filterEvent(event, appMapIndex)) {
95
95
  return;
96
96
  }
97
- const buildFinding = (matchEvent, message, groupMessage, occurranceCount,
98
- // matchEvent will be added to additionalEvents to create the relatedEvents array
97
+ const buildFinding = (matchEvent, participatingEvents, message, groupMessage, occurranceCount,
98
+ // matchEvent will be added to additionalEvents and participatingEvents.values
99
+ // to create the relatedEvents array
99
100
  additionalEvents) => {
100
101
  const findingEvent = matchEvent || event;
101
102
  // Fixes:
@@ -118,12 +119,19 @@ class RuleChecker {
118
119
  return;
119
120
  }
120
121
  uniqueEvents.add(event.id);
121
- relatedEvents.push(event);
122
+ relatedEvents.push((0, eventUtil_1.cloneEvent)(event));
122
123
  });
123
124
  // Update event hash with unique hashes of related events
124
125
  new Set(relatedEvents.map((e) => e.hash)).forEach((eventHash) => {
125
126
  hash.update(eventHash);
126
127
  });
128
+ Object.values(participatingEvents).forEach((event) => {
129
+ if (uniqueEvents.has(event.id)) {
130
+ return;
131
+ }
132
+ uniqueEvents.add(event.id);
133
+ relatedEvents.push((0, eventUtil_1.cloneEvent)(event));
134
+ });
127
135
  return {
128
136
  appMapFile,
129
137
  checkId: checkInstance.checkId,
@@ -138,6 +146,7 @@ class RuleChecker {
138
146
  occurranceCount,
139
147
  relatedEvents: relatedEvents.sort((event) => event.id),
140
148
  impactDomain: checkInstance.checkImpactDomain,
149
+ participatingEvents: Object.fromEntries(Object.entries(participatingEvents).map(([k, v]) => [k, (0, eventUtil_1.cloneEvent)(v)])),
141
150
  };
142
151
  };
143
152
  const matchResult = yield checkInstance.ruleLogic.matcher(event, appMapIndex, checkInstance.filterEvent.bind(checkInstance));
@@ -146,21 +155,21 @@ class RuleChecker {
146
155
  let finding;
147
156
  if (checkInstance.ruleLogic.message) {
148
157
  const message = checkInstance.ruleLogic.message(scope, event);
149
- finding = buildFinding(event, message);
158
+ finding = buildFinding(event, {}, message);
150
159
  }
151
160
  else {
152
- finding = buildFinding(event);
161
+ finding = buildFinding(event, {});
153
162
  }
154
163
  findings.push(finding);
155
164
  }
156
165
  else if (typeof matchResult === 'string') {
157
- const finding = buildFinding(event, matchResult);
166
+ const finding = buildFinding(event, {}, matchResult);
158
167
  finding.message = matchResult;
159
168
  findings.push(finding);
160
169
  }
161
170
  else if (matchResult) {
162
171
  matchResult.forEach((mr) => {
163
- const finding = buildFinding(mr.event, mr.message, mr.groupMessage, mr.occurranceCount, mr.relatedEvents);
172
+ const finding = buildFinding(mr.event, mr.participatingEvents || {}, mr.message, mr.groupMessage, mr.occurranceCount, mr.relatedEvents);
164
173
  findings.push(finding);
165
174
  });
166
175
  }
@@ -173,13 +173,14 @@ function build(options) {
173
173
  .map((cycle) => searchForCycle(cycle, ignoredPackages))
174
174
  .filter((path) => path)
175
175
  .map((path) => {
176
+ path = path;
176
177
  return {
177
178
  event: path[0],
178
179
  message: [
179
180
  'Cycle in package dependency graph',
180
181
  path.map((event) => event.codeObject.packageOf).join(' -> '),
181
182
  ].join(': '),
182
- relatedEvents: path,
183
+ participatingEvents: Object.fromEntries(path.map((event, index) => [`path[${index}]`, event])),
183
184
  };
184
185
  });
185
186
  }
@@ -19,18 +19,21 @@ function build(options) {
19
19
  return !!e.parent && !!e.parent.codeObject.packageOf && calleePattern(e.codeObject.packageOf);
20
20
  }
21
21
  function matcher(e) {
22
+ const parent = e.parent;
23
+ if (!parent)
24
+ return;
22
25
  const packageNamesStr = options.callerPackages
23
26
  .map((config) => config.equal || config.include || config.match)
24
27
  .map(String)
25
28
  .join(' or ');
26
- const parentPackage = e.parent.codeObject.packageOf;
29
+ const parentPackage = parent.codeObject.packageOf;
27
30
  if (!(e.codeObject.packageOf === parentPackage ||
28
31
  callerPatterns.some((pattern) => pattern(parentPackage)))) {
29
32
  return [
30
33
  {
31
34
  event: e,
32
35
  message: `Code object ${e.codeObject.id} was invoked from ${parentPackage}, not from ${packageNamesStr}`,
33
- relatedEvents: [e.parent],
36
+ participatingEvents: { parent },
34
37
  },
35
38
  ];
36
39
  }
@@ -8,7 +8,8 @@ const recordSecrets_1 = __importDefault(require("../analyzer/recordSecrets"));
8
8
  const secretsRegexes_1 = require("../analyzer/secretsRegexes");
9
9
  const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
10
10
  const BCRYPT_REGEXP = /^[$]2[abxy]?[$](?:0[4-9]|[12][0-9]|3[01])[$][./0-9a-zA-Z]{53}$/;
11
- const secrets = new Set();
11
+ const secrets = [];
12
+ const secretStrings = new Set();
12
13
  function stringEquals(e) {
13
14
  if (!e.parameters || !e.receiver || e.parameters.length !== 1) {
14
15
  return false;
@@ -18,7 +19,7 @@ function stringEquals(e) {
18
19
  return BCRYPT_REGEXP.test(str);
19
20
  }
20
21
  function isSecret(str) {
21
- return secrets.has(str) || (0, secretsRegexes_1.looksSecret)(str);
22
+ return secretStrings.has(str) || (0, secretsRegexes_1.looksSecret)(str);
22
23
  }
23
24
  // BCrypted strings are safe to compare using equals()
24
25
  return args.some(isSecret) && !args.some(isBcrypt);
@@ -26,7 +27,12 @@ function stringEquals(e) {
26
27
  function build() {
27
28
  function matcher(e) {
28
29
  if (e.codeObject.labels.has(Secret)) {
30
+ const numSecrets = secrets.length;
29
31
  (0, recordSecrets_1.default)(secrets, e);
32
+ for (let index = numSecrets; index < secrets.length; index++) {
33
+ const secret = secrets[index];
34
+ secretStrings.add(secret.value);
35
+ }
30
36
  }
31
37
  if (e.codeObject.labels.has(StringEquals)) {
32
38
  return stringEquals(e);
@@ -18,14 +18,11 @@ function build() {
18
18
  const missing = creationEvents.length - cancellationEvents.length;
19
19
  if (missing === 0)
20
20
  return;
21
- const result = {
22
- event: event,
23
- message: `${missing} jobs created but not cancelled in this rolled back transaction`,
24
- // if there's a mismatch and there are cancellations we can't tell
25
- // for sure which creations they match, so return everything
26
- relatedEvents: [...creationEvents, ...cancellationEvents],
27
- };
28
- return [result];
21
+ return creationEvents.map((jobCreationEvent) => ({
22
+ event: jobCreationEvent,
23
+ message: `Job created by ${jobCreationEvent.codeObject.prettyName} was not cancelled when the enclosing transaction rolled back`,
24
+ participatingEvents: { beginTransaction: event },
25
+ }));
29
26
  }
30
27
  return {
31
28
  matcher,
@@ -43,6 +43,7 @@ function build(options) {
43
43
  groupMessage: sql,
44
44
  occurranceCount: occurranceCount,
45
45
  relatedEvents: events.map((e) => e.event),
46
+ participatingEvents: { commonAncestor: ancestor },
46
47
  };
47
48
  };
48
49
  if (occurranceCount >= options.errorLimit) {
@@ -18,12 +18,15 @@ function build(options) {
18
18
  const allowedPackages = (0, matchPattern_1.buildFilters)(options.allowedPackages);
19
19
  const allowedQueries = (0, matchPattern_1.buildFilters)(options.allowedQueries);
20
20
  function matcher(e) {
21
- if (!allowedPackages.some((filter) => filter(e.parent.codeObject.packageOf))) {
21
+ if (!e.parent)
22
+ return;
23
+ const parent = e.parent;
24
+ if (!allowedPackages.some((filter) => filter(parent.codeObject.packageOf))) {
22
25
  return [
23
26
  {
24
27
  event: e,
25
- message: `${e.codeObject.id} is invoked from illegal package ${e.parent.codeObject.packageOf}`,
26
- relatedEvents: [e.parent],
28
+ message: `${e.codeObject.id} is invoked from illegal package ${parent.codeObject.packageOf}`,
29
+ participatingEvents: { parent: parent },
27
30
  },
28
31
  ];
29
32
  }
@@ -32,12 +32,19 @@ const recordSecrets_1 = __importDefault(require("../analyzer/recordSecrets"));
32
32
  const url_1 = require("url");
33
33
  const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
34
34
  class Match {
35
- constructor(pattern, value) {
35
+ constructor(pattern, value, generatorEvent) {
36
36
  this.pattern = pattern;
37
37
  this.value = value;
38
+ this.generatorEvent = generatorEvent;
39
+ }
40
+ static fromPattern(pattern, value) {
41
+ return new Match(pattern, value);
42
+ }
43
+ static fromSecret(secret, value) {
44
+ return new Match(secret.value, value, secret.generatorEvent);
38
45
  }
39
46
  }
40
- const secrets = new Set();
47
+ const secrets = [];
41
48
  const findInLog = (event) => {
42
49
  if (!event.parameters)
43
50
  return;
@@ -45,24 +52,32 @@ const findInLog = (event) => {
45
52
  for (const { value } of event.parameters) {
46
53
  if ((0, util_1.emptyValue)(value))
47
54
  continue;
48
- const patterns = [];
49
55
  if ((0, secretsRegexes_1.looksSecret)(value)) {
50
56
  // Only look for the exact matching regexes if it matches the catchall regex
51
- patterns.push(...Object.values(secretsRegexes_1.default)
57
+ matches.push(...Object.values(secretsRegexes_1.default)
52
58
  .flat()
53
- .filter((re) => re.test(value)));
59
+ .filter((re) => re.test(value))
60
+ .map((re) => Match.fromPattern(re, value)));
54
61
  }
55
62
  for (const secret of secrets) {
56
- if (value.includes(secret))
57
- patterns.push(secret);
63
+ if (value.includes(secret.value)) {
64
+ matches.push(Match.fromSecret(secret, value));
65
+ }
58
66
  }
59
- matches.push(...patterns.map((pattern) => new Match(pattern, value)));
60
67
  }
61
68
  if (matches.length > 0) {
62
- return matches.map((match) => ({
63
- event,
64
- message: `Log event contains secret data: ${match.value}`,
65
- }));
69
+ return matches.map((match) => {
70
+ const { pattern, value } = match;
71
+ const participatingEvents = { logEvent: event };
72
+ if (match.generatorEvent) {
73
+ participatingEvents.generatorEvent = match.generatorEvent;
74
+ }
75
+ return {
76
+ event,
77
+ message: `Log message contains secret ${match.generatorEvent ? match.generatorEvent.codeObject.prettyName || 'data' : 'data'} "${pattern}": ${value}`,
78
+ participatingEvents,
79
+ };
80
+ });
66
81
  }
67
82
  };
68
83
  function build() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appland/scanner",
3
- "version": "1.60.0",
3
+ "version": "1.61.0",
4
4
  "description": "",
5
5
  "bin": "built/cli.js",
6
6
  "files": [