@appland/scanner 1.62.1 → 1.64.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 +23 -0
- package/built/algorithms/hash/hashV1.js +29 -0
- package/built/algorithms/hash/hashV2.js +107 -0
- package/built/check.js +1 -1
- package/built/report/summaryReport.js +4 -4
- package/built/ruleChecker.js +13 -28
- package/built/rules/authzBeforeAuthn.js +1 -0
- package/built/rules/jobNotCancelled.js +8 -5
- package/built/rules/lib/isCommand.js +13 -0
- package/built/rules/logoutWithoutSessionReset.js +1 -0
- package/built/rules/missingContentType.js +4 -2
- package/built/rules/nPlusOneQuery.js +2 -1
- package/built/rules/secretInLog.js +2 -1
- package/built/rules/slowFunctionCall.js +0 -1
- package/built/rules/updateInGetRequest.js +9 -1
- package/built/scope/commandScope.js +13 -0
- package/doc/rules/secret-in-log.md +1 -0
- package/doc/rules/slow-function-call.md +0 -1
- package/package.json +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
# [@appland/scanner-v1.64.0](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.63.0...@appland/scanner-v1.64.0) (2022-08-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Command scope falls back on root events ([3823a1f](https://github.com/applandinc/appmap-js/commit/3823a1f686212db49b87f2995baa2103a4e007d1))
|
|
7
|
+
|
|
8
|
+
# [@appland/scanner-v1.63.0](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.62.2...@appland/scanner-v1.63.0) (2022-07-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Include a partial stack in the finding hash ([7e82f8a](https://github.com/applandinc/appmap-js/commit/7e82f8a0b13a1d0927aad73be4ee126d2d4695dc))
|
|
14
|
+
* Populate hash_v2 on each finding ([04470b7](https://github.com/applandinc/appmap-js/commit/04470b7f11e764d79a22eb297d0e6882f6f89a3f))
|
|
15
|
+
* Summarize local report using hash_v2 ([ffbde39](https://github.com/applandinc/appmap-js/commit/ffbde393c17f1f1572eb7653bad796d90662b943))
|
|
16
|
+
|
|
17
|
+
# [@appland/scanner-v1.62.2](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.62.1...@appland/scanner-v1.62.2) (2022-07-25)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* Return proper result for job-not-cancelled ([f7ee5da](https://github.com/applandinc/appmap-js/commit/f7ee5da073849881c3c553f08fc2dd82bb8c7965))
|
|
23
|
+
|
|
1
24
|
# [@appland/scanner-v1.62.1](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.62.0...@appland/scanner-v1.62.1) (2022-07-13)
|
|
2
25
|
|
|
3
26
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const crypto_1 = require("crypto");
|
|
4
|
+
class HashV1 {
|
|
5
|
+
constructor(ruleId, findingEvent, relatedEvents) {
|
|
6
|
+
this.hash = (0, crypto_1.createHash)('sha256');
|
|
7
|
+
this.hash.update(findingEvent.hash);
|
|
8
|
+
this.hash.update(ruleId);
|
|
9
|
+
// Admittedly odd, this implementation matches the original hash algorithm.
|
|
10
|
+
const uniqueEvents = new Set();
|
|
11
|
+
const hashEvents = [];
|
|
12
|
+
relatedEvents.unshift(findingEvent);
|
|
13
|
+
relatedEvents.forEach((event) => {
|
|
14
|
+
if (uniqueEvents.has(event.id))
|
|
15
|
+
return;
|
|
16
|
+
uniqueEvents.add(event.id);
|
|
17
|
+
hashEvents.push(event);
|
|
18
|
+
});
|
|
19
|
+
// This part where the hashes go into a Set, and there is some kind of ordering as a side-
|
|
20
|
+
// effect, is particularly weird.
|
|
21
|
+
new Set(hashEvents.map((e) => e.hash)).forEach((eventHash) => {
|
|
22
|
+
this.hash.update(eventHash);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
digest() {
|
|
26
|
+
return this.hash.digest('hex');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.default = HashV1;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.captureStack = void 0;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const isCommand_1 = __importDefault(require("../../rules/lib/isCommand"));
|
|
9
|
+
const util_1 = require("../../rules/lib/util");
|
|
10
|
+
function hashEvent(entries, prefix, event) {
|
|
11
|
+
Object.keys(event.stableProperties)
|
|
12
|
+
.sort()
|
|
13
|
+
.forEach((key) => entries.push([[prefix, key].join('.'), event.stableProperties[key].toString()].join('=')));
|
|
14
|
+
}
|
|
15
|
+
const STACK_DEPTH = 3;
|
|
16
|
+
/**
|
|
17
|
+
* Captures stack entries from distinct packages. Ancestors of the event are traversed up to the
|
|
18
|
+
* command or root. Then, starting from the command or root, subsequent events which come from the
|
|
19
|
+
* same package as their preceding event are removed. Then the last N entries remaining in the
|
|
20
|
+
* stack are collected.
|
|
21
|
+
*
|
|
22
|
+
* @param event leaf event
|
|
23
|
+
* @param participatingEvents output collector
|
|
24
|
+
* @param depth number of events to include in the output
|
|
25
|
+
*/
|
|
26
|
+
function captureStack(event, depth = STACK_DEPTH) {
|
|
27
|
+
let ancestor = event.parent;
|
|
28
|
+
const stack = [];
|
|
29
|
+
while (ancestor) {
|
|
30
|
+
stack.push(ancestor);
|
|
31
|
+
ancestor = (0, isCommand_1.default)(ancestor) ? undefined : ancestor.parent;
|
|
32
|
+
}
|
|
33
|
+
const packageOf = (event) => {
|
|
34
|
+
if (!event)
|
|
35
|
+
return;
|
|
36
|
+
if (event.codeObject.type !== 'function')
|
|
37
|
+
return;
|
|
38
|
+
return event.codeObject.packageOf;
|
|
39
|
+
};
|
|
40
|
+
return stack
|
|
41
|
+
.filter((item, index) => item.codeObject.type !== 'function' || packageOf(stack[index + 1]) !== packageOf(item))
|
|
42
|
+
.slice(0, depth);
|
|
43
|
+
}
|
|
44
|
+
exports.captureStack = captureStack;
|
|
45
|
+
/**
|
|
46
|
+
* Builds a hash (digest) of a finding. The digest is constructed by first building a canonical
|
|
47
|
+
* string of the finding, of the form:
|
|
48
|
+
*
|
|
49
|
+
* ```
|
|
50
|
+
* [
|
|
51
|
+
* algorithmVersion=2
|
|
52
|
+
* rule=<rule-id>
|
|
53
|
+
* findingEvent.<property1>=value1
|
|
54
|
+
* ...
|
|
55
|
+
* findingEvent.<propertyN>=valueN
|
|
56
|
+
* participatingEvent.<eventName1>=value1
|
|
57
|
+
* ...
|
|
58
|
+
* participatingEvent.<eventName1>=valueN
|
|
59
|
+
* ...
|
|
60
|
+
* participatingEvent.<eventNameN>=value1
|
|
61
|
+
* ...
|
|
62
|
+
* participatingEvent.<eventNameN>=valueN
|
|
63
|
+
* stack[1]=value1
|
|
64
|
+
* ...
|
|
65
|
+
* stack[1]=valueN
|
|
66
|
+
* ...
|
|
67
|
+
* stack[3]=value1
|
|
68
|
+
* ...
|
|
69
|
+
* stack[3]=valueN
|
|
70
|
+
* ]
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Participating events are sorted by the event name. Properties of each event are sorted by
|
|
74
|
+
* the property name. Event properties are provided by `Event#stableProperties`.
|
|
75
|
+
*
|
|
76
|
+
* The partial stack included in the finding hash removes subsequent function calls from the
|
|
77
|
+
* same package.
|
|
78
|
+
*/
|
|
79
|
+
class HashV2 {
|
|
80
|
+
constructor(ruleId, findingEvent, participatingEvents) {
|
|
81
|
+
this.hashEntries = [];
|
|
82
|
+
this.hash = (0, crypto_1.createHash)('sha256');
|
|
83
|
+
const hashEntries = [
|
|
84
|
+
['algorithmVersion', '2'],
|
|
85
|
+
['rule', ruleId],
|
|
86
|
+
].map((e) => e.join('='));
|
|
87
|
+
this.hashEntries = hashEntries;
|
|
88
|
+
hashEvent(hashEntries, 'findingEvent', findingEvent);
|
|
89
|
+
Object.keys(participatingEvents)
|
|
90
|
+
.sort()
|
|
91
|
+
.forEach((key) => {
|
|
92
|
+
const event = participatingEvents[key];
|
|
93
|
+
hashEvent(hashEntries, `participatingEvent.${key}`, event);
|
|
94
|
+
});
|
|
95
|
+
captureStack(findingEvent).forEach((event, index) => hashEvent(hashEntries, `stack[${index + 1}]`, event));
|
|
96
|
+
if ((0, util_1.verbose)())
|
|
97
|
+
console.log(hashEntries);
|
|
98
|
+
hashEntries.forEach((e) => this.hash.update(e));
|
|
99
|
+
}
|
|
100
|
+
get canonicalString() {
|
|
101
|
+
return this.hashEntries.join('\n');
|
|
102
|
+
}
|
|
103
|
+
digest() {
|
|
104
|
+
return this.hash.digest('hex');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.default = HashV2;
|
package/built/check.js
CHANGED
|
@@ -10,8 +10,8 @@ function summarizeFindings(findings) {
|
|
|
10
10
|
let findingSummary = memo[finding.ruleId];
|
|
11
11
|
if (findingSummary) {
|
|
12
12
|
findingSummary.findingTotal += 1;
|
|
13
|
-
if (!findingSummary.findingHashes.has(finding.
|
|
14
|
-
findingSummary.findingHashes.add(finding.
|
|
13
|
+
if (!findingSummary.findingHashes.has(finding.hash_v2)) {
|
|
14
|
+
findingSummary.findingHashes.add(finding.hash_v2);
|
|
15
15
|
findingSummary.messages.push(finding.message);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -20,7 +20,7 @@ function summarizeFindings(findings) {
|
|
|
20
20
|
ruleId: finding.ruleId,
|
|
21
21
|
ruleTitle: finding.ruleTitle,
|
|
22
22
|
findingTotal: 1,
|
|
23
|
-
findingHashes: new Set([finding.
|
|
23
|
+
findingHashes: new Set([finding.hash_v2]),
|
|
24
24
|
messages: [finding.message],
|
|
25
25
|
};
|
|
26
26
|
memo[finding.ruleId] = findingSummary;
|
|
@@ -31,7 +31,7 @@ function summarizeFindings(findings) {
|
|
|
31
31
|
return Object.values(result);
|
|
32
32
|
}
|
|
33
33
|
function default_1(summary, colorize) {
|
|
34
|
-
const matchedStr = `${summary.summary.numFindings} ${(0, util_1.pluralize)('finding', summary.summary.numFindings)} (${new Set(summary.findings.map((finding) => finding.
|
|
34
|
+
const matchedStr = `${summary.summary.numFindings} ${(0, util_1.pluralize)('finding', summary.summary.numFindings)} (${new Set(summary.findings.map((finding) => finding.hash_v2)).size} unique)`;
|
|
35
35
|
const colouredMatchedStr = colorize ? chalk_1.default.stderr.magenta(matchedStr) : matchedStr;
|
|
36
36
|
console.log();
|
|
37
37
|
console.log(colouredMatchedStr);
|
package/built/ruleChecker.js
CHANGED
|
@@ -20,8 +20,9 @@ const httpClientRequestScope_1 = __importDefault(require("./scope/httpClientRequ
|
|
|
20
20
|
const commandScope_1 = __importDefault(require("./scope/commandScope"));
|
|
21
21
|
const sqlTransactionScope_1 = __importDefault(require("./scope/sqlTransactionScope"));
|
|
22
22
|
const checkInstance_1 = __importDefault(require("./checkInstance"));
|
|
23
|
-
const crypto_1 = require("crypto");
|
|
24
23
|
const eventUtil_1 = require("./eventUtil");
|
|
24
|
+
const hashV1_1 = __importDefault(require("./algorithms/hash/hashV1"));
|
|
25
|
+
const hashV2_1 = __importDefault(require("./algorithms/hash/hashV2"));
|
|
25
26
|
class RuleChecker {
|
|
26
27
|
constructor() {
|
|
27
28
|
this.scopes = {
|
|
@@ -34,14 +35,7 @@ class RuleChecker {
|
|
|
34
35
|
}
|
|
35
36
|
check(appMapFile, appMapIndex, check, findings) {
|
|
36
37
|
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
-
const
|
|
38
|
-
if (numScopesChecked === 0 && check.scope === 'command') {
|
|
39
|
-
yield this.checkScope(appMapFile, appMapIndex, check, 'root', findings);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
checkScope(appMapFile, appMapIndex, check, scope, findings) {
|
|
44
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
const scope = check.scope;
|
|
45
39
|
if ((0, util_1.verbose)()) {
|
|
46
40
|
console.warn(`Checking AppMap ${appMapIndex.appMap.name} with scope ${scope}`);
|
|
47
41
|
}
|
|
@@ -55,9 +49,7 @@ class RuleChecker {
|
|
|
55
49
|
yield events[i];
|
|
56
50
|
}
|
|
57
51
|
};
|
|
58
|
-
let numScopes = 0;
|
|
59
52
|
for (const scope of scopeIterator.scopes(callEvents())) {
|
|
60
|
-
numScopes += 1;
|
|
61
53
|
if ((0, util_1.verbose)()) {
|
|
62
54
|
console.warn(`Scope ${scope.scope}`);
|
|
63
55
|
}
|
|
@@ -74,7 +66,6 @@ class RuleChecker {
|
|
|
74
66
|
yield this.checkEvent(scope.scope, scope.scope, appMapFile, appMapIndex, checkInstance, findings);
|
|
75
67
|
}
|
|
76
68
|
}
|
|
77
|
-
return numScopes;
|
|
78
69
|
});
|
|
79
70
|
}
|
|
80
71
|
checkEvent(event, scope, appMapFile, appMapIndex, checkInstance, findings) {
|
|
@@ -109,23 +100,16 @@ class RuleChecker {
|
|
|
109
100
|
findingEvent.codeObject.location,
|
|
110
101
|
...findingEvent.ancestors().map((ancestor) => ancestor.codeObject.location),
|
|
111
102
|
].filter(Boolean);
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
hash.
|
|
103
|
+
const hashV1 = new hashV1_1.default(checkInstance.ruleId, findingEvent,
|
|
104
|
+
// findingEvent gets passed here as a relatedEvent, and if you look at HashV1 it
|
|
105
|
+
// gets added to the hash again. That's how it worked in V1 so it's here for compatibility.
|
|
106
|
+
additionalEvents || []);
|
|
107
|
+
const hashV2 = new hashV2_1.default(checkInstance.ruleId, findingEvent, participatingEvents);
|
|
115
108
|
const uniqueEvents = new Set();
|
|
116
109
|
const relatedEvents = [];
|
|
117
|
-
[findingEvent
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
uniqueEvents.add(event.id);
|
|
122
|
-
relatedEvents.push((0, eventUtil_1.cloneEvent)(event));
|
|
123
|
-
});
|
|
124
|
-
// Update event hash with unique hashes of related events
|
|
125
|
-
new Set(relatedEvents.map((e) => e.hash)).forEach((eventHash) => {
|
|
126
|
-
hash.update(eventHash);
|
|
127
|
-
});
|
|
128
|
-
Object.values(participatingEvents).forEach((event) => {
|
|
110
|
+
[findingEvent, ...(additionalEvents || []), ...Object.values(participatingEvents)]
|
|
111
|
+
.map(eventUtil_1.cloneEvent)
|
|
112
|
+
.forEach((event) => {
|
|
129
113
|
if (uniqueEvents.has(event.id)) {
|
|
130
114
|
return;
|
|
131
115
|
}
|
|
@@ -138,7 +122,8 @@ class RuleChecker {
|
|
|
138
122
|
ruleId: checkInstance.ruleId,
|
|
139
123
|
ruleTitle: checkInstance.title,
|
|
140
124
|
event: (0, eventUtil_1.cloneEvent)(findingEvent),
|
|
141
|
-
hash:
|
|
125
|
+
hash: hashV1.digest(),
|
|
126
|
+
hash_v2: hashV2.digest(),
|
|
142
127
|
stack,
|
|
143
128
|
scope: (0, eventUtil_1.cloneEvent)(scope),
|
|
144
129
|
message: message || checkInstance.title,
|
|
@@ -18,11 +18,14 @@ 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
|
-
|
|
21
|
+
const result = {
|
|
22
|
+
event: event,
|
|
23
|
+
message: `${missing} jobs are scheduled but not cancelled in a 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];
|
|
26
29
|
}
|
|
27
30
|
return {
|
|
28
31
|
matcher,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function isCommand(event) {
|
|
4
|
+
let label;
|
|
5
|
+
if (event.labels.has('command'))
|
|
6
|
+
label = 'command';
|
|
7
|
+
else if (event.labels.has('job'))
|
|
8
|
+
label = 'job';
|
|
9
|
+
else if (event.httpServerRequest)
|
|
10
|
+
label = 'request';
|
|
11
|
+
return label;
|
|
12
|
+
}
|
|
13
|
+
exports.default = isCommand;
|
|
@@ -8,8 +8,10 @@ const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescripti
|
|
|
8
8
|
const isRedirect = (status) => [301, 302, 303, 307, 308].includes(status);
|
|
9
9
|
const hasContent = (status) => status !== 204;
|
|
10
10
|
function build() {
|
|
11
|
-
function matcher(
|
|
12
|
-
|
|
11
|
+
function matcher(event) {
|
|
12
|
+
if ((0, openapi_1.rpcRequestForEvent)(event).responseContentType === undefined) {
|
|
13
|
+
return `Missing HTTP content type in response to request: ${event.route}`;
|
|
14
|
+
}
|
|
13
15
|
}
|
|
14
16
|
function where(e) {
|
|
15
17
|
return (!!e.httpServerResponse &&
|
|
@@ -35,6 +35,7 @@ function build(options) {
|
|
|
35
35
|
const ancestor = eventsById[parseInt(ancestorId)];
|
|
36
36
|
const occurranceCount = events.length;
|
|
37
37
|
if (occurranceCount > options.warningLimit) {
|
|
38
|
+
const participatingEvents = { commonAncestor: ancestor };
|
|
38
39
|
const buildMatchResult = (level) => {
|
|
39
40
|
return {
|
|
40
41
|
level: level,
|
|
@@ -43,7 +44,7 @@ function build(options) {
|
|
|
43
44
|
groupMessage: sql,
|
|
44
45
|
occurranceCount: occurranceCount,
|
|
45
46
|
relatedEvents: events.map((e) => e.event),
|
|
46
|
-
participatingEvents
|
|
47
|
+
participatingEvents,
|
|
47
48
|
};
|
|
48
49
|
};
|
|
49
50
|
if (occurranceCount >= options.errorLimit) {
|
|
@@ -68,7 +68,7 @@ const findInLog = (event) => {
|
|
|
68
68
|
if (matches.length > 0) {
|
|
69
69
|
return matches.map((match) => {
|
|
70
70
|
const { pattern, value } = match;
|
|
71
|
-
const participatingEvents = {
|
|
71
|
+
const participatingEvents = {};
|
|
72
72
|
if (match.generatorEvent) {
|
|
73
73
|
participatingEvents.generatorEvent = match.generatorEvent;
|
|
74
74
|
}
|
|
@@ -101,6 +101,7 @@ exports.default = {
|
|
|
101
101
|
id: 'secret-in-log',
|
|
102
102
|
title: 'Secret in log',
|
|
103
103
|
labels: [Secret, Log],
|
|
104
|
+
scope: 'root',
|
|
104
105
|
impactDomain: 'Security',
|
|
105
106
|
enumerateScope: true,
|
|
106
107
|
references: {
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const util_1 = require("./lib/util");
|
|
7
7
|
const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
|
|
8
|
+
const assert_1 = __importDefault(require("assert"));
|
|
8
9
|
class Options {
|
|
9
10
|
constructor(queryInclude = [/\binsert\b/i, /\bupdate\b/i], queryExclude = []) {
|
|
10
11
|
this._queryInclude = queryInclude;
|
|
@@ -38,7 +39,14 @@ function build(options = new Options()) {
|
|
|
38
39
|
!options.queryExclude.some((pattern) => e.sqlQuery.match(pattern)) &&
|
|
39
40
|
!e.ancestors().some((ancestor) => ancestor.codeObject.labels.has(Audit)) &&
|
|
40
41
|
hasHttpServerRequest()) {
|
|
41
|
-
|
|
42
|
+
(0, assert_1.default)(httpServerRequest, 'HTTP server request is undefined');
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
event: e,
|
|
46
|
+
message: `Data update performed in HTTP request ${httpServerRequest.route}: ${e.sqlQuery}`,
|
|
47
|
+
participatingEvents: { request: httpServerRequest },
|
|
48
|
+
},
|
|
49
|
+
];
|
|
42
50
|
}
|
|
43
51
|
},
|
|
44
52
|
where: (e) => !!e.sqlQuery,
|
|
@@ -21,15 +21,28 @@ const Command = 'command.perform';
|
|
|
21
21
|
const Job = 'job.perform';
|
|
22
22
|
class CommandScope extends scopeIterator_1.default {
|
|
23
23
|
*scopes(events) {
|
|
24
|
+
let found = false;
|
|
25
|
+
const roots = [];
|
|
24
26
|
for (const event of events) {
|
|
27
|
+
if (event.isCall() && !event.parent) {
|
|
28
|
+
roots.push(event);
|
|
29
|
+
}
|
|
25
30
|
if (event.isCall() &&
|
|
26
31
|
(event.codeObject.labels.has(Command) ||
|
|
27
32
|
event.codeObject.labels.has(Job) ||
|
|
28
33
|
event.httpServerRequest)) {
|
|
34
|
+
found = true;
|
|
29
35
|
yield new ScopeImpl(event);
|
|
30
36
|
this.advanceToReturnEvent(event, events);
|
|
31
37
|
}
|
|
32
38
|
}
|
|
39
|
+
// If no true command is found, yield all root events.
|
|
40
|
+
if (!found) {
|
|
41
|
+
for (let index = 0; index < roots.length; index++) {
|
|
42
|
+
const event = roots[index];
|
|
43
|
+
yield new ScopeImpl(event);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
exports.default = CommandScope;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appland/scanner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.64.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"bin": "built/cli.js",
|
|
6
6
|
"files": [
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"@appland/sql-parser": "^1.5.0",
|
|
62
62
|
"@types/cli-progress": "^3.9.2",
|
|
63
63
|
"ajv": "^8.8.2",
|
|
64
|
-
"ansi-escapes": "^5.0.0",
|
|
65
64
|
"applicationinsights": "^2.1.4",
|
|
66
65
|
"async": "^3.2.3",
|
|
67
66
|
"chalk": "^4.1.2",
|
|
@@ -86,16 +85,20 @@
|
|
|
86
85
|
},
|
|
87
86
|
"pkg": {
|
|
88
87
|
"targets": [
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
88
|
+
"node16-linux-x64",
|
|
89
|
+
"node16-win-x64",
|
|
90
|
+
"node16-macos-x64",
|
|
91
|
+
"node16-macos-arm64"
|
|
92
92
|
],
|
|
93
93
|
"scripts": [
|
|
94
|
-
"built/scanner/*.js"
|
|
94
|
+
"built/scanner/*.js",
|
|
95
|
+
"built/rules/**/*.js"
|
|
95
96
|
],
|
|
96
97
|
"assets": [
|
|
97
98
|
"built/sampleConfig/*.yml",
|
|
98
|
-
"built/**/*.json"
|
|
99
|
+
"built/**/*.json",
|
|
100
|
+
"package.json",
|
|
101
|
+
"doc/**/*.md"
|
|
99
102
|
],
|
|
100
103
|
"outputPath": "dist"
|
|
101
104
|
}
|