@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 +7 -0
- package/built/analyzer/recordSecrets.js +1 -1
- package/built/ruleChecker.js +16 -7
- package/built/rules/circularDependency.js +2 -1
- package/built/rules/illegalPackageDependency.js +5 -2
- package/built/rules/insecureCompare.js +8 -2
- package/built/rules/jobNotCancelled.js +5 -8
- package/built/rules/nPlusOneQuery.js +1 -0
- package/built/rules/queryFromInvalidPackage.js +6 -3
- package/built/rules/secretInLog.js +27 -12
- package/package.json +1 -1
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
|
|
package/built/ruleChecker.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
22
|
-
event:
|
|
23
|
-
message:
|
|
24
|
-
|
|
25
|
-
|
|
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,
|
|
@@ -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 (!
|
|
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 ${
|
|
26
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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() {
|