@appland/scanner 1.80.2 → 1.81.1

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,18 @@
1
+ # [@appland/scanner-v1.81.1](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.81.0...@appland/scanner-v1.81.1) (2023-08-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Specify node version requirements for user-facing packages ([ca55a25](https://github.com/getappmap/appmap-js/commit/ca55a256e5fb79b990eff87a753980de549a4b88))
7
+
8
+ # [@appland/scanner-v1.81.0](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.80.2...@appland/scanner-v1.81.0) (2023-08-09)
9
+
10
+
11
+ ### Features
12
+
13
+ * Move scanner logging behind verbose() flag ([2df8863](https://github.com/getappmap/appmap-js/commit/2df88632d4a97d4b65cadf83b9c11ec701d7e6fb))
14
+ * Refactor types into index.ts ([322596b](https://github.com/getappmap/appmap-js/commit/322596bbc8492dd032a4c47e62c5d730f4596e56))
15
+
1
16
  # [@appland/scanner-v1.80.2](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.80.1...@appland/scanner-v1.80.2) (2023-07-20)
2
17
 
3
18
 
package/built/index.js ADDED
@@ -0,0 +1,8 @@
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.scan = void 0;
7
+ var scan_1 = require("./scan");
8
+ Object.defineProperty(exports, "scan", { enumerable: true, get: function () { return __importDefault(scan_1).default; } });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -42,7 +42,7 @@ function build() {
42
42
  }
43
43
  const SecurityAuthentication = 'security.authentication';
44
44
  const SecurityAuthorization = 'security.authorization';
45
- exports.default = {
45
+ const RULE = {
46
46
  id: 'authz-before-authn',
47
47
  title: 'Authorization performed before authentication',
48
48
  labels: [SecurityAuthorization, SecurityAuthentication],
@@ -56,3 +56,4 @@ exports.default = {
56
56
  url: 'https://appland.com/docs/analysis/rules-reference.html#authz-before-authn',
57
57
  build,
58
58
  };
59
+ exports.default = RULE;
@@ -188,7 +188,7 @@ function build(options) {
188
188
  matcher,
189
189
  };
190
190
  }
191
- exports.default = {
191
+ const RULE = {
192
192
  id: 'circular-dependency',
193
193
  title: 'Circular package dependency',
194
194
  Options,
@@ -201,3 +201,4 @@ exports.default = {
201
201
  url: 'https://appland.com/docs/analysis/rules-reference.html#circular-dependency',
202
202
  build,
203
203
  };
204
+ exports.default = RULE;
@@ -63,7 +63,7 @@ function matcher(startEvent) {
63
63
  const DeserializeUnsafe = 'deserialize.unsafe';
64
64
  const DeserializeSafe = 'deserialize.safe';
65
65
  const DeserializeSanitize = 'deserialize.sanitize';
66
- exports.default = {
66
+ const RULE = {
67
67
  id: 'deserialization-of-untrusted-data',
68
68
  title: 'Deserialization of untrusted data',
69
69
  labels: [DeserializeUnsafe, DeserializeSafe, DeserializeSanitize],
@@ -78,3 +78,4 @@ exports.default = {
78
78
  url: 'https://appland.com/docs/analysis/rules-reference.html#deserialization-of-untrusted-data',
79
79
  build: () => ({ matcher }),
80
80
  };
81
+ exports.default = RULE;
@@ -46,7 +46,7 @@ function build() {
46
46
  const Exec = 'system.exec';
47
47
  const ExecSafe = 'system.exec.safe';
48
48
  const ExecSanitize = 'system.exec.sanitize';
49
- exports.default = {
49
+ const RULE = {
50
50
  id: 'exec-of-untrusted-command',
51
51
  title: 'Execution of untrusted system command',
52
52
  labels: [Exec, ExecSafe, ExecSanitize],
@@ -59,3 +59,4 @@ exports.default = {
59
59
  url: 'https://appland.com/docs/analysis/rules-reference.html#exec-of-untrusted-command',
60
60
  build,
61
61
  };
62
+ exports.default = RULE;
@@ -40,7 +40,7 @@ function build(options) {
40
40
  }
41
41
  return { where, matcher };
42
42
  }
43
- exports.default = {
43
+ const RULE = {
44
44
  id: 'illegal-package-dependency',
45
45
  title: 'Illegal use of code by a non-whitelisted package',
46
46
  // scope: //*[@command]
@@ -56,3 +56,4 @@ exports.default = {
56
56
  Options,
57
57
  build,
58
58
  };
59
+ exports.default = RULE;
@@ -57,7 +57,7 @@ function build(options) {
57
57
  where: (e) => !!e.httpClientRequest && !!e.httpClientRequest.url,
58
58
  };
59
59
  }
60
- exports.default = {
60
+ const RULE = {
61
61
  id: 'incompatible-http-client-request',
62
62
  title: 'Incompatible HTTP client request',
63
63
  // scope: //http_client_request
@@ -69,3 +69,4 @@ exports.default = {
69
69
  Options,
70
70
  build,
71
71
  };
72
+ exports.default = RULE;
@@ -48,7 +48,7 @@ function build() {
48
48
  }
49
49
  const Secret = 'secret';
50
50
  const StringEquals = 'string.equals';
51
- exports.default = {
51
+ const RULE = {
52
52
  id: 'insecure-compare',
53
53
  title: 'Insecure comparison of secrets',
54
54
  labels: [Secret, StringEquals],
@@ -61,3 +61,4 @@ exports.default = {
61
61
  url: 'https://appland.com/docs/analysis/rules-reference.html#insecure-compare',
62
62
  build,
63
63
  };
64
+ exports.default = RULE;
@@ -31,7 +31,7 @@ function build() {
31
31
  matcher,
32
32
  };
33
33
  }
34
- exports.default = {
34
+ const RULE = {
35
35
  id: 'job-not-cancelled',
36
36
  title: 'Job created in a rolled back transaction and not cancelled',
37
37
  scope: 'transaction',
@@ -45,3 +45,4 @@ exports.default = {
45
45
  url: 'https://appland.com/docs/analysis/rules-reference.html#job-not-cancelled',
46
46
  build,
47
47
  };
48
+ exports.default = RULE;
@@ -41,7 +41,7 @@ function build() {
41
41
  }
42
42
  const SecurityLogout = 'security.logout';
43
43
  const HTTPSessionClear = 'http.session.clear';
44
- exports.default = {
44
+ const RULE = {
45
45
  id: 'logout-without-session-reset',
46
46
  title: 'Logout without session reset',
47
47
  scope: 'http_server_request',
@@ -57,3 +57,4 @@ exports.default = {
57
57
  url: 'https://appland.com/docs/analysis/rules-reference.html#logout-without-session-reset',
58
58
  build,
59
59
  };
60
+ exports.default = RULE;
@@ -66,7 +66,7 @@ function build(options = new Options()) {
66
66
  }
67
67
  const AccessPublic = 'access.public';
68
68
  const SecurityAuthentication = 'security.authentication';
69
- exports.default = {
69
+ const RULE = {
70
70
  id: 'missing-authentication',
71
71
  title: 'Unauthenticated HTTP server request',
72
72
  scope: 'http_server_request',
@@ -81,3 +81,4 @@ exports.default = {
81
81
  Options,
82
82
  build,
83
83
  };
84
+ exports.default = RULE;
@@ -23,7 +23,7 @@ function build() {
23
23
  where,
24
24
  };
25
25
  }
26
- exports.default = {
26
+ const RULE = {
27
27
  id: 'missing-content-type',
28
28
  title: 'HTTP server request without a Content-Type header',
29
29
  scope: 'http_server_request',
@@ -33,3 +33,4 @@ exports.default = {
33
33
  url: 'https://appland.com/docs/analysis/rules-reference.html#missing-content-type',
34
34
  build,
35
35
  };
36
+ exports.default = RULE;
@@ -75,7 +75,7 @@ function build(options) {
75
75
  matcher,
76
76
  };
77
77
  }
78
- exports.default = {
78
+ const RULE = {
79
79
  id: 'n-plus-one-query',
80
80
  title: 'N plus 1 SQL query',
81
81
  scope: 'command',
@@ -89,3 +89,4 @@ exports.default = {
89
89
  url: 'https://appland.com/docs/analysis/rules-reference.html#n-plus-one-query',
90
90
  build,
91
91
  };
92
+ exports.default = RULE;
@@ -39,7 +39,7 @@ function build(options) {
39
39
  where,
40
40
  };
41
41
  }
42
- exports.default = {
42
+ const RULE = {
43
43
  id: 'query-from-invalid-package',
44
44
  title: 'Queries from invalid packages',
45
45
  Options,
@@ -52,3 +52,4 @@ exports.default = {
52
52
  url: 'https://appland.com/docs/analysis/rules-reference.html#query-from-invalid-package',
53
53
  build,
54
54
  };
55
+ exports.default = RULE;
@@ -33,7 +33,7 @@ function build(options = new Options()) {
33
33
  where,
34
34
  };
35
35
  }
36
- exports.default = {
36
+ const RULE = {
37
37
  id: 'query-from-view',
38
38
  title: 'Queries from view',
39
39
  Options,
@@ -46,3 +46,4 @@ exports.default = {
46
46
  url: 'https://appland.com/docs/analysis/rules-reference.html#query-from-view',
47
47
  build,
48
48
  };
49
+ exports.default = RULE;
@@ -21,7 +21,7 @@ function build(options = new Options()) {
21
21
  return (0, rpcWithoutProtection_1.rpcWithoutProtection)(descendants, options);
22
22
  }
23
23
  const RPCCircuitBreaker = 'rpc.circuit_breaker';
24
- exports.default = {
24
+ const RULE = {
25
25
  id: 'rpc-without-circuit-breaker',
26
26
  title: 'RPC without circuit breaker',
27
27
  Options,
@@ -32,3 +32,4 @@ exports.default = {
32
32
  url: 'https://appland.com/docs/analysis/rules-reference.html#rpc-without-circuit-breaker',
33
33
  build,
34
34
  };
35
+ exports.default = RULE;
@@ -24,7 +24,7 @@ function build() {
24
24
  where: (e) => e.isFunction && ['save', 'save!'].includes(e.methodId),
25
25
  };
26
26
  }
27
- exports.default = {
27
+ const RULE = {
28
28
  id: 'save-without-validation',
29
29
  title: 'Save without validation',
30
30
  enumerateScope: true,
@@ -36,3 +36,4 @@ exports.default = {
36
36
  url: 'https://appland.com/docs/analysis/rules-reference.html#save-without-validation',
37
37
  build,
38
38
  };
39
+ exports.default = RULE;
@@ -97,7 +97,7 @@ function build() {
97
97
  }
98
98
  const Secret = 'secret';
99
99
  const Log = 'log';
100
- exports.default = {
100
+ const RULE = {
101
101
  id: 'secret-in-log',
102
102
  title: 'Secret in log',
103
103
  labels: [Secret, Log],
@@ -111,3 +111,4 @@ exports.default = {
111
111
  url: 'https://appland.com/docs/analysis/rules-reference.html#secret-in-log',
112
112
  build,
113
113
  };
114
+ exports.default = RULE;
@@ -27,7 +27,7 @@ function build(options) {
27
27
  functionPatterns.some((pattern) => pattern(e.codeObject.id))),
28
28
  };
29
29
  }
30
- exports.default = {
30
+ const RULE = {
31
31
  id: 'slow-function-call',
32
32
  title: 'Slow function call',
33
33
  impactDomain: 'Performance',
@@ -37,3 +37,4 @@ exports.default = {
37
37
  Options,
38
38
  build,
39
39
  };
40
+ exports.default = RULE;
@@ -16,7 +16,7 @@ function build(options) {
16
16
  where: (e) => !!e.httpServerRequest && e.elapsedTime !== undefined,
17
17
  };
18
18
  }
19
- exports.default = {
19
+ const RULE = {
20
20
  id: 'slow-http-server-request',
21
21
  title: 'Slow HTTP server request',
22
22
  scope: 'http_server_request',
@@ -27,3 +27,4 @@ exports.default = {
27
27
  Options,
28
28
  build,
29
29
  };
30
+ exports.default = RULE;
@@ -15,7 +15,7 @@ function build(options = new Options()) {
15
15
  where: (e) => !!e.sqlQuery && !!e.elapsedTime,
16
16
  };
17
17
  }
18
- exports.default = {
18
+ const RULE = {
19
19
  id: 'slow-query',
20
20
  title: 'Slow SQL query',
21
21
  Options,
@@ -25,3 +25,4 @@ exports.default = {
25
25
  url: 'https://appland.com/docs/analysis/rules-reference.html#slow-query',
26
26
  build,
27
27
  };
28
+ exports.default = RULE;
@@ -57,7 +57,7 @@ function build(options) {
57
57
  matcher,
58
58
  };
59
59
  }
60
- exports.default = {
60
+ const RULE = {
61
61
  id: 'too-many-updates',
62
62
  title: 'Too many SQL and RPC updates performed in one command',
63
63
  scope: 'command',
@@ -71,3 +71,4 @@ exports.default = {
71
71
  Options,
72
72
  build,
73
73
  };
74
+ exports.default = RULE;
@@ -65,7 +65,7 @@ function build() {
65
65
  }
66
66
  // Example: ActiveRecord::Relation#records
67
67
  const DAOMaterialize = 'dao.materialize';
68
- exports.default = {
68
+ const RULE = {
69
69
  id: 'unbatched-materialized-query',
70
70
  title: 'Unbatched materialized SQL query',
71
71
  labels: [DAOMaterialize],
@@ -78,3 +78,4 @@ exports.default = {
78
78
  url: 'https://appland.com/docs/analysis/rules-reference.html#unbatched-materialized-query',
79
79
  build,
80
80
  };
81
+ exports.default = RULE;
@@ -53,7 +53,7 @@ function build(options = new Options()) {
53
53
  };
54
54
  }
55
55
  const Audit = 'audit';
56
- exports.default = {
56
+ const RULE = {
57
57
  id: 'update-in-get-request',
58
58
  title: 'Data update performed in GET or HEAD request',
59
59
  scope: 'http_server_request',
@@ -65,3 +65,4 @@ exports.default = {
65
65
  Options,
66
66
  build,
67
67
  };
68
+ exports.default = RULE;
package/built/scan.js ADDED
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ /* eslint-disable @typescript-eslint/no-empty-function */
16
+ /* eslint-disable @typescript-eslint/naming-convention */
17
+ /* eslint-disable @typescript-eslint/no-unused-vars */
18
+ const lru_cache_1 = __importDefault(require("lru-cache"));
19
+ const assert_1 = __importDefault(require("assert"));
20
+ const promises_1 = require("fs/promises");
21
+ const console_1 = require("console");
22
+ const models_1 = require("@appland/models");
23
+ const configurationProvider_1 = require("./configuration/configurationProvider");
24
+ const util_1 = require("./rules/lib/util");
25
+ const ruleChecker_1 = __importDefault(require("./ruleChecker"));
26
+ const appMapIndex_1 = __importDefault(require("./appMapIndex"));
27
+ const ConfigurationByFileName = new lru_cache_1.default({ max: 10 });
28
+ const ChecksByFileName = new lru_cache_1.default({ max: 10 });
29
+ class StatsProgressReporter {
30
+ constructor() {
31
+ this.parseTime = new Array();
32
+ this.elapsedByRuleId = new Map();
33
+ }
34
+ printSummary() {
35
+ if (!(0, util_1.verbose)())
36
+ return;
37
+ if (this.parseTime.length === 0)
38
+ return;
39
+ const keys = Array.from(this.elapsedByRuleId.keys()).sort();
40
+ console.warn(`Average parse time: ${this.parseTime.reduce((memo, val) => memo + val, 0) / this.parseTime.length}ms`);
41
+ for (const key of keys) {
42
+ const elapsed = this.elapsedByRuleId.get(key);
43
+ const average = elapsed.reduce((memo, val) => memo + val, 0) / elapsed.length;
44
+ console.warn(`Average check time for ${key}: ${average}ms`);
45
+ }
46
+ }
47
+ addParseTime(elapsed) {
48
+ this.parseTime.push(elapsed);
49
+ }
50
+ beginAppMap(appMapFileName, appMap) {
51
+ return __awaiter(this, void 0, void 0, function* () { });
52
+ }
53
+ beginCheck(check) {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ this.ruleId = check.rule.id;
56
+ this.checkStartTime = new Date();
57
+ });
58
+ }
59
+ filterScope(scopeName, scope) {
60
+ return __awaiter(this, void 0, void 0, function* () { });
61
+ }
62
+ enterScope(scope) {
63
+ return __awaiter(this, void 0, void 0, function* () { });
64
+ }
65
+ filterEvent(event) {
66
+ return __awaiter(this, void 0, void 0, function* () { });
67
+ }
68
+ matchResult(event, matchResult) {
69
+ return __awaiter(this, void 0, void 0, function* () { });
70
+ }
71
+ matchEvent(event, appMapIndex) {
72
+ return __awaiter(this, void 0, void 0, function* () { });
73
+ }
74
+ leaveScope() {
75
+ return __awaiter(this, void 0, void 0, function* () { });
76
+ }
77
+ endCheck() {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ (0, assert_1.default)(this.ruleId);
80
+ (0, assert_1.default)(this.checkStartTime);
81
+ const checkEndTime = new Date();
82
+ const elapsed = checkEndTime.getTime() - this.checkStartTime.getTime();
83
+ if (!this.elapsedByRuleId.has(this.ruleId))
84
+ this.elapsedByRuleId.set(this.ruleId, []);
85
+ this.elapsedByRuleId.get(this.ruleId).push(elapsed);
86
+ });
87
+ }
88
+ endAppMap() {
89
+ return __awaiter(this, void 0, void 0, function* () { });
90
+ }
91
+ }
92
+ const STATS_REPORTER = new StatsProgressReporter();
93
+ setInterval(() => STATS_REPORTER.printSummary(), 3000);
94
+ /**
95
+ * Perform all configured checks on a single AppMap file.
96
+ */
97
+ function scan(appmapFile, configurationFile) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ let configuration = ConfigurationByFileName.get(configurationFile);
100
+ if (!configuration) {
101
+ if ((0, util_1.verbose)())
102
+ (0, console_1.warn)(`Loading configuration from ${configurationFile}`);
103
+ configuration = yield (0, configurationProvider_1.parseConfigFile)(configurationFile);
104
+ ConfigurationByFileName.set(configurationFile, configuration);
105
+ }
106
+ let checkImpls = ChecksByFileName.get(configurationFile);
107
+ if (!checkImpls) {
108
+ if ((0, util_1.verbose)())
109
+ (0, console_1.warn)(`[scan] Loading checks from ${configurationFile}`);
110
+ checkImpls = yield (0, configurationProvider_1.loadConfig)(configuration);
111
+ ChecksByFileName.set(configurationFile, checkImpls);
112
+ }
113
+ const checker = new ruleChecker_1.default(STATS_REPORTER);
114
+ const findings = [];
115
+ const startTime = new Date();
116
+ const appMapData = yield (0, promises_1.readFile)(appmapFile, 'utf8');
117
+ const appMap = (0, models_1.buildAppMap)(appMapData).normalize().build();
118
+ const parseTime = new Date();
119
+ STATS_REPORTER.addParseTime(parseTime.getTime() - startTime.getTime());
120
+ if ((0, util_1.verbose)())
121
+ console.warn(`[scan] Event count: ${appMap.events.length}`);
122
+ if ((0, util_1.verbose)())
123
+ console.warn(`[scan] Parse time: ${parseTime.getTime() - startTime.getTime()}ms`);
124
+ const appMapIndex = new appMapIndex_1.default(appMap);
125
+ for (const check of checkImpls) {
126
+ yield STATS_REPORTER.beginCheck(check);
127
+ yield checker.check(appmapFile, appMapIndex, check, findings);
128
+ yield STATS_REPORTER.endCheck();
129
+ }
130
+ const scanTime = new Date();
131
+ if ((0, util_1.verbose)())
132
+ console.warn(`[scan] Scan time: ${scanTime.getTime() - parseTime.getTime()}ms`);
133
+ const checks = checkImpls.map((check) => ({
134
+ id: check.id,
135
+ scope: check.rule.scope || 'command',
136
+ impactDomain: check.rule.impactDomain || 'Stability',
137
+ rule: {
138
+ id: check.rule.id,
139
+ title: check.rule.title,
140
+ description: check.rule.description,
141
+ url: check.rule.url,
142
+ labels: check.rule.labels || [],
143
+ enumerateScope: check.rule.enumerateScope,
144
+ references: check.rule.references || {},
145
+ },
146
+ }));
147
+ return {
148
+ configuration,
149
+ appMapMetadata: { appmapFile: appMap.metadata },
150
+ findings,
151
+ checks,
152
+ };
153
+ });
154
+ }
155
+ exports.default = scan;
@@ -1,30 +1,31 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const fs_1 = require("fs");
4
- let SqlWarningFileName = 'sql_warning.txt';
5
- let messages = [];
6
- let writeMessage = (msg) => (messages ? messages.push(msg) : null);
7
- process.on('exit', () => {
8
- if (!messages)
9
- return;
10
- [...new Set(messages)].forEach((msg) => console.warn(msg));
11
- });
12
- function sqlWarning(error) {
13
- if (SqlWarningFileName) {
14
- (0, fs_1.open)(SqlWarningFileName, 'w', (err, fd) => {
15
- if (err || !fd)
16
- return;
17
- writeMessage = (msg) => {
18
- // eslint-disable-next-line @typescript-eslint/no-empty-function
19
- (0, fs_1.write)(fd, [msg, '\n'].join(''), () => { });
20
- };
21
- if (messages)
22
- messages.forEach(writeMessage);
23
- messages = null;
12
+ const promises_1 = require("fs/promises");
13
+ const SqlErrors = new Set();
14
+ const SqlParseErrorFileName = 'sql_warning.txt';
15
+ let SqlParseErrorFileOpened = false;
16
+ function writeErrorToFile(error) {
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ const flags = SqlParseErrorFileOpened ? 'a' : 'w';
19
+ SqlParseErrorFileOpened = true;
20
+ (0, promises_1.open)(SqlParseErrorFileName, flags).then((handle) => {
21
+ handle.write([error.toString(), ''].join('\n')).finally(handle.close.bind(handle));
24
22
  });
25
- // Try only once
26
- SqlWarningFileName = null;
23
+ });
24
+ }
25
+ function sqlWarning(parseError) {
26
+ if (!SqlErrors.has(parseError.sql)) {
27
+ writeErrorToFile(parseError);
28
+ SqlErrors.add(parseError.sql);
27
29
  }
28
- writeMessage(error.toString());
29
30
  }
30
31
  exports.default = sqlWarning;
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@appland/scanner",
3
- "version": "1.80.2",
3
+ "version": "1.81.1",
4
4
  "description": "Analyze AppMaps for code flaws",
5
5
  "bin": "built/cli.js",
6
+ "main": "built/index.js",
6
7
  "files": [
7
8
  "built",
8
9
  "doc",
9
10
  "src/types.d.ts"
10
11
  ],
11
- "types": "src/types.d.ts",
12
+ "types": "src/index.ts",
12
13
  "scripts": {
13
14
  "build": "node bin/preBuild.js && tsc -p tsconfig.build.json && yarn schema && yarn doc",
14
15
  "build-native": "yarn build && ./bin/build-native",
@@ -90,6 +91,9 @@
90
91
  "tar-stream": "^2.2.0",
91
92
  "yargs": "^17.1.1"
92
93
  },
94
+ "engines": {
95
+ "node": ">15"
96
+ },
93
97
  "publishConfig": {
94
98
  "access": "public"
95
99
  },
package/src/types.d.ts CHANGED
@@ -1,37 +1,5 @@
1
1
  import { AppMap, Event } from '@appland/models';
2
2
  import { SqliteParser } from '@appland/models/types/sqlite-parser';
3
- import { URL } from 'url';
4
-
5
- /**
6
- * Each Rule in the scanner library wants to consider some set of events as it decides whether the code should be flagged or not.
7
- * A Scope is a way of declaring how these "sets" are defined. A common scope is `http_server_request`. The rule will look at each HTTP
8
- * server request separately; what happens in one request is irrelevant to the next. For example, when looking for authz_before_authn, each HTTP
9
- * server request is considered separately.
10
- *
11
- * `http_server_request` is one example of a "command". Other types of commands are: CLI commands and background jobs. Each of these has a
12
- * defined beginning and end, and is logically completely separate from any other command.
13
- *
14
- * Some rules are relevant only to HTTP server requests - such as `http500`. Others are applicable to any kind of command - such as `nPlusOneQuery`.
15
- *
16
- * Finally, other rules simply want to look for a certain condition regardless of where it occurs. For example, Too many SQL joins will flag any
17
- * query anywhere in the AppMap, even if it's not part of a command. This rule uses the root scope, which yields a new scope for every root-level Event
18
- * (root-level = "has no parent").
19
- *
20
- * Ideally, AppMaps would not contain any events that are not part of a command - because without knowing the command, we don't really have any context
21
- * of what the code is trying to do. But, anticipating that this may sometimes happen, "root" scope is a good choice for a rule that may flag code
22
- * anywhere in the AppMap.
23
- */
24
- export type ScopeName =
25
- | 'root'
26
- | 'command'
27
- | 'http_client_request'
28
- | 'http_server_request'
29
- | 'transaction';
30
-
31
- /**
32
- * Indicates the aspect of software quality that is most relevant to a rule.
33
- */
34
- export type ImpactDomain = 'Security' | 'Performance' | 'Maintainability' | 'Stability';
35
3
 
36
4
  /**
37
5
  * Scope provides an Event at the root of the scope, and a Generator to iterate over its descendants.
@@ -93,34 +61,7 @@ interface AppMapIndex {
93
61
  */
94
62
  type Matcher = (e: Event, appMapIndex: AppMapIndex, eventFilter: EventFilter) => MatcherResult;
95
63
 
96
- /**
97
- * Finding is the full data structure that is created when a Rule matches an Event.
98
- *
99
- * The Rule only needs to return a boolean, string, or MatchResult. The scanner framework
100
- * adds the rest of the information to build the complete finding.
101
- */
102
- interface Finding {
103
- appMapFile: string;
104
- checkId: string;
105
- ruleId: string;
106
- ruleTitle: string;
107
- event: Event;
108
- hash: string; // Deprecated for local use. Still used to integrate local results with the server.
109
- hash_v2: string;
110
- scope: Event;
111
- message: string;
112
- stack: string[];
113
- groupMessage?: string;
114
- occurranceCount?: number;
115
- relatedEvents?: Event[];
116
- impactDomain?: ImpactDomain;
117
- // Map of events by functional role name; for example, logEvent, secret, scope, etc.
118
- participatingEvents?: Record<string, Event>;
119
- scopeModifiedDate?: Date;
120
- eventsModifiedDate?: Date;
121
- }
122
-
123
- interface RuleLogic {
64
+ export interface RuleLogic {
124
65
  // Tests an event in the scope see if it matches the rule conditions.
125
66
  matcher: Matcher;
126
67
  // When specified by the rule, only events which pass the where filter
@@ -129,35 +70,3 @@ interface RuleLogic {
129
70
  // When specified by the rule, provides a detailed message for a finding on a specific event.
130
71
  message?: (scope: Event, event: Event) => string;
131
72
  }
132
-
133
- interface Rule {
134
- // Unique id of the rule.
135
- id: string;
136
- // Simple text description of the rule.
137
- title: string;
138
- // Longer text description of the rule.
139
- description: string;
140
- // URL to the documentation.
141
- url?: string;
142
- // labels which the rule depends on.
143
- labels?: string[];
144
- // Specifies which sub-tree events will be identified as "root events of concern".
145
- // Events which are outside of any scope will not be processed by the rule.
146
- scope?: ScopeName;
147
- // Whether to pass all the events in the scope to the matcher. If false, only the scope event
148
- // is passed to the matcher, and the rule should traverse the scope as needed.
149
- enumerateScope: boolean;
150
- impactDomain?: ImpactDomain;
151
- references?: Record<string, URL>;
152
- // User-defined options for the rule.
153
- Options?: any; // FIXME
154
- // Function to instantiate the rule logic from configured options.
155
- build: (options: this['Options']) => RuleLogic;
156
- }
157
-
158
- export type Check = {
159
- id: string;
160
- scope: ScopeName;
161
- impactDomain: ImpactDomain;
162
- rule: Rule;
163
- };