@appland/scanner 1.59.1 → 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 +21 -0
- package/built/analyzer/recordSecrets.js +1 -1
- package/built/check.js +2 -0
- package/built/checkInstance.js +3 -0
- package/built/cli/scan/watchScan.js +7 -2
- package/built/ruleChecker.js +17 -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,24 @@
|
|
|
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
|
+
|
|
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)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* pass down impactDomain from Rule to Finding ([8755041](https://github.com/applandinc/appmap-js/commit/875504183a8517fec316b5f188d269de053ceef8))
|
|
14
|
+
|
|
15
|
+
# [@appland/scanner-v1.59.2](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.59.1...@appland/scanner-v1.59.2) (2022-06-29)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* Use absolute paths when creating watchers ([afc81e0](https://github.com/applandinc/appmap-js/commit/afc81e03f1e5004288acda75c89bb61a1857c462))
|
|
21
|
+
|
|
1
22
|
# [@appland/scanner-v1.59.1](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.59.0...@appland/scanner-v1.59.1) (2022-06-15)
|
|
2
23
|
|
|
3
24
|
|
package/built/check.js
CHANGED
|
@@ -14,6 +14,8 @@ class Check {
|
|
|
14
14
|
this.excludeScope = [];
|
|
15
15
|
this.includeEvent = [];
|
|
16
16
|
this.excludeEvent = [];
|
|
17
|
+
//TODO: Create Default value for impact domain
|
|
18
|
+
this.impactDomain = rule.impactDomain;
|
|
17
19
|
}
|
|
18
20
|
filterScope(event, appMapIndex) {
|
|
19
21
|
if (this.includeScope.length > 0 && !this.includeScope.every((fn) => fn(event, appMapIndex))) {
|
package/built/checkInstance.js
CHANGED
|
@@ -44,6 +44,7 @@ const fs_1 = require("fs");
|
|
|
44
44
|
const util_1 = require("util");
|
|
45
45
|
const configurationProvider_1 = require("../../configuration/configurationProvider");
|
|
46
46
|
const assert_1 = __importDefault(require("assert"));
|
|
47
|
+
const path_1 = __importDefault(require("path"));
|
|
47
48
|
class Watcher {
|
|
48
49
|
constructor(options) {
|
|
49
50
|
this.options = options;
|
|
@@ -57,10 +58,14 @@ class Watcher {
|
|
|
57
58
|
this.configWatcher
|
|
58
59
|
.on('add', this.reloadConfig.bind(this))
|
|
59
60
|
.on('change', this.reloadConfig.bind(this));
|
|
60
|
-
|
|
61
|
+
// Chokidar struggles with relative paths. Make sure the watch pattern is absolute.
|
|
62
|
+
const watchPattern = path_1.default.resolve(this.options.appmapDir, '**', 'mtime');
|
|
63
|
+
this.appmapWatcher = chokidar.watch(watchPattern, {
|
|
61
64
|
ignoreInitial: true,
|
|
62
65
|
});
|
|
63
|
-
this.appmapWatcher
|
|
66
|
+
this.appmapWatcher
|
|
67
|
+
.on('add', (filePath) => this.scan(filePath))
|
|
68
|
+
.on('change', (filePath) => this.scan(filePath));
|
|
64
69
|
});
|
|
65
70
|
}
|
|
66
71
|
close() {
|
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,
|
|
@@ -137,6 +145,8 @@ class RuleChecker {
|
|
|
137
145
|
groupMessage,
|
|
138
146
|
occurranceCount,
|
|
139
147
|
relatedEvents: relatedEvents.sort((event) => event.id),
|
|
148
|
+
impactDomain: checkInstance.checkImpactDomain,
|
|
149
|
+
participatingEvents: Object.fromEntries(Object.entries(participatingEvents).map(([k, v]) => [k, (0, eventUtil_1.cloneEvent)(v)])),
|
|
140
150
|
};
|
|
141
151
|
};
|
|
142
152
|
const matchResult = yield checkInstance.ruleLogic.matcher(event, appMapIndex, checkInstance.filterEvent.bind(checkInstance));
|
|
@@ -145,21 +155,21 @@ class RuleChecker {
|
|
|
145
155
|
let finding;
|
|
146
156
|
if (checkInstance.ruleLogic.message) {
|
|
147
157
|
const message = checkInstance.ruleLogic.message(scope, event);
|
|
148
|
-
finding = buildFinding(event, message);
|
|
158
|
+
finding = buildFinding(event, {}, message);
|
|
149
159
|
}
|
|
150
160
|
else {
|
|
151
|
-
finding = buildFinding(event);
|
|
161
|
+
finding = buildFinding(event, {});
|
|
152
162
|
}
|
|
153
163
|
findings.push(finding);
|
|
154
164
|
}
|
|
155
165
|
else if (typeof matchResult === 'string') {
|
|
156
|
-
const finding = buildFinding(event, matchResult);
|
|
166
|
+
const finding = buildFinding(event, {}, matchResult);
|
|
157
167
|
finding.message = matchResult;
|
|
158
168
|
findings.push(finding);
|
|
159
169
|
}
|
|
160
170
|
else if (matchResult) {
|
|
161
171
|
matchResult.forEach((mr) => {
|
|
162
|
-
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);
|
|
163
173
|
findings.push(finding);
|
|
164
174
|
});
|
|
165
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() {
|