@appland/scanner 1.70.3 → 1.70.5

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,19 @@
1
+ # [@appland/scanner-v1.70.5](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.70.4...@appland/scanner-v1.70.5) (2022-09-27)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Ignore node_modules and .git when watching files ([a53e2a8](https://github.com/getappmap/appmap-js/commit/a53e2a8c0de25e0e7ae7c0515763a9f46d3bc307))
7
+ * More reliable AppMap change detection when scanning ([94a454b](https://github.com/getappmap/appmap-js/commit/94a454b14c3bea4c1c5e0cd81d75e7d919bc8e85))
8
+
9
+ # [@appland/scanner-v1.70.4](https://github.com/getappmap/appmap-js/compare/@appland/scanner-v1.70.3...@appland/scanner-v1.70.4) (2022-09-27)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Add excludeTables option for too-many-joins ([ad8b97d](https://github.com/getappmap/appmap-js/commit/ad8b97d4ca47ea7a6a0c9fc31560a42351ba0b94))
15
+ * Add missing JWT algorithm docs ([328f0a7](https://github.com/getappmap/appmap-js/commit/328f0a7e14e419a843c8deb4efd7b2cc56ddad8b))
16
+
1
17
  # [@appland/scanner-v1.70.3](https://github.com/applandinc/appmap-js/compare/@appland/scanner-v1.70.2...@appland/scanner-v1.70.3) (2022-09-21)
2
18
 
3
19
 
@@ -1,10 +1,50 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const models_1 = require("@appland/models");
7
+ const sha256_1 = __importDefault(require("crypto-js/sha256"));
8
+ const assert_1 = __importDefault(require("assert"));
3
9
  const crypto_1 = require("crypto");
10
+ // BEGIN COMMENT
11
+ // Moved here from packages/models, in order to retain backwards compatibility with Hash v1
12
+ // as the Event algoritms hash and identityHash are updated.
13
+ function qualifiedMethodId(event) {
14
+ const { definedClass, isStatic, methodId } = event;
15
+ (0, assert_1.default)(definedClass, 'Event.definedClass');
16
+ return `${definedClass}${isStatic ? '.' : '#'}${methodId}`;
17
+ }
18
+ // Returns a string suitable for durable identification of a call event
19
+ // that's independent of parameters and return values.
20
+ // For SQL queries, it's a JSON of abstract query AST.
21
+ // For HTTP queries, it's the method plus normalized path info.
22
+ // For function calls it's the qualified function id.
23
+ // TODO: This can be removed/deprecated when the current hash algorithm is removed.
24
+ function callEventToString(event) {
25
+ const { sqlQuery, route } = event;
26
+ if (sqlQuery)
27
+ return (0, models_1.abstractSqlAstJSON)(sqlQuery, event.sql.database_type);
28
+ if (route)
29
+ return route;
30
+ return qualifiedMethodId(event);
31
+ }
32
+ // Returns a short string (hash) suitable for durable identification
33
+ // of a call event that's independent of parameters and return values.
34
+ // For SQL queries, it considers the abstract query (ignoring differences in literal values).
35
+ // For HTTP queries, it considers the method plus normalized path info.
36
+ // For function calls it's the qualified function id.
37
+ // Non-call events will throw an error.
38
+ function hashEvent(event) {
39
+ if (event.event !== 'call')
40
+ throw new Error('tried to hash a non-call event');
41
+ return (0, sha256_1.default)(callEventToString(event)).toString();
42
+ }
43
+ // END COMMENT
4
44
  class HashV1 {
5
45
  constructor(ruleId, findingEvent, relatedEvents) {
6
46
  this.hash = (0, crypto_1.createHash)('sha256');
7
- this.hash.update(findingEvent.hash);
47
+ this.hash.update(hashEvent(findingEvent));
8
48
  this.hash.update(ruleId);
9
49
  // Admittedly odd, this implementation matches the original hash algorithm.
10
50
  const uniqueEvents = new Set();
@@ -18,7 +58,7 @@ class HashV1 {
18
58
  });
19
59
  // This part where the hashes go into a Set, and there is some kind of ordering as a side-
20
60
  // effect, is particularly weird.
21
- new Set(hashEvents.map((e) => e.hash)).forEach((eventHash) => {
61
+ new Set(hashEvents.map((e) => hashEvent(e))).forEach((eventHash) => {
22
62
  this.hash.update(eventHash);
23
63
  });
24
64
  }
@@ -61,21 +61,31 @@ class Watcher {
61
61
  // Chokidar struggles with relative paths. Make sure the watch pattern is absolute.
62
62
  const watchPattern = path_1.default.resolve(this.options.appmapDir, '**', 'mtime');
63
63
  this.appmapWatcher = chokidar.watch(watchPattern, {
64
+ ignoreInitial: true,
65
+ ignored: ['**/node_modules/**', '**/.git/**'],
66
+ });
67
+ this.appmapPoller = chokidar.watch(watchPattern, {
64
68
  ignoreInitial: false,
69
+ ignored: ['**/node_modules/**', '**/.git/**'],
70
+ usePolling: true,
71
+ interval: 1000,
72
+ persistent: false,
65
73
  });
66
- this.appmapWatcher
67
- .on('add', (filePath) => this.scan(filePath))
68
- .on('change', (filePath) => this.scan(filePath));
74
+ for (const ch of [this.appmapWatcher, this.appmapPoller]) {
75
+ ch.on('add', (filePath) => this.scan(filePath));
76
+ ch.on('change', (filePath) => this.scan(filePath));
77
+ }
69
78
  });
70
79
  }
71
80
  close() {
72
- if (!this.appmapWatcher)
73
- return;
74
- (0, assert_1.default)(this.configWatcher, `configWatcher should always be defined if appmapWatcher is defined`);
75
- this.appmapWatcher.close();
76
- this.configWatcher.close();
77
- this.appmapWatcher = undefined;
78
- this.configWatcher = undefined;
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ yield Promise.all(['appmapWatcher', 'appmapPoller', 'configWatcher'].map((k) => {
83
+ var _a;
84
+ const closer = (_a = this[k]) === null || _a === void 0 ? void 0 : _a.close();
85
+ this[k] = undefined;
86
+ return closer;
87
+ }));
88
+ });
79
89
  }
80
90
  scan(fileName) {
81
91
  return __awaiter(this, void 0, void 0, function* () {
@@ -173,12 +173,18 @@
173
173
  },
174
174
  "TooManyJoins.Options": {
175
175
  "type": "object",
176
- "additionalProperties": false,
177
176
  "properties": {
178
177
  "warningLimit": {
179
178
  "type": "number"
179
+ },
180
+ "excludeTables": {
181
+ "type": "array",
182
+ "items": {
183
+ "$ref": "https://appland.com/schemas/scanner/match-pattern-config.json"
184
+ }
180
185
  }
181
- }
186
+ },
187
+ "additionalProperties": false
182
188
  },
183
189
  "TooManyUpdates.Options": {
184
190
  "type": "object",
@@ -3,7 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Labels = void 0;
6
7
  const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
8
+ const url_1 = require("url");
9
+ var Labels;
10
+ (function (Labels) {
11
+ Labels["JwtEncode"] = "jwt.encode";
12
+ })(Labels = exports.Labels || (exports.Labels = {}));
7
13
  function getHeader(jwt) {
8
14
  try {
9
15
  const [header] = jwt.split('.');
@@ -28,7 +34,7 @@ class JwtAlgoritmNoneLogic {
28
34
  return matches;
29
35
  }
30
36
  where(event) {
31
- return event.labels.has('jwt.encode');
37
+ return event.labels.has(Labels.JwtEncode);
32
38
  }
33
39
  }
34
40
  class JwtAlgoritmNone {
@@ -39,6 +45,12 @@ class JwtAlgoritmNone {
39
45
  this.enumerateScope = true;
40
46
  this.description = (0, parseRuleDescription_1.default)('jwtAlgorithmNone');
41
47
  this.url = 'https://appland.com/docs/analysis/rules-reference.html#jwt-algorithm-none';
48
+ this.labels = [Labels.JwtEncode];
49
+ this.references = {
50
+ 'CWE-345': new url_1.URL('https://cwe.mitre.org/data/definitions/345.html'),
51
+ 'A02:2021': new url_1.URL('https://owasp.org/Top10/A02_2021-Cryptographic_Failures'),
52
+ 'RFC 7519': new url_1.URL('https://www.rfc-editor.org/rfc/rfc7519'),
53
+ };
42
54
  }
43
55
  build() {
44
56
  return new JwtAlgoritmNoneLogic();
@@ -76,6 +76,12 @@ class JwtUnverifiedSignature {
76
76
  this.enumerateScope = true;
77
77
  this.description = (0, parseRuleDescription_1.default)('jwtUnverifiedSignature');
78
78
  this.url = 'https://appland.com/docs/analysis/rules-reference.html#jwt-unverified-signature';
79
+ this.labels = [Labels.JwtDecode, Labels.SignatureVerify];
80
+ this.references = {
81
+ 'CWE-345': new URL('https://cwe.mitre.org/data/definitions/345.html'),
82
+ 'A02:2021': new URL('https://owasp.org/Top10/A02_2021-Cryptographic_Failures'),
83
+ 'RFC 7519': new URL('https://www.rfc-editor.org/rfc/rfc7519'),
84
+ };
79
85
  }
80
86
  build() {
81
87
  return new JwtUnverifiedSignatureLogic();
@@ -5,7 +5,7 @@ class Options {
5
5
  this.warningLimit = 5;
6
6
  this.excludeTables = [
7
7
  { match: /^pg_/, ignoreCase: false },
8
- { match: /^information_schema$/, ignoreCase: true },
8
+ { equal: 'information_schema', ignoreCase: false },
9
9
  ];
10
10
  }
11
11
  }
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: jwt.decode
3
+ rules:
4
+ - jwt-unverified-signature
5
+ ---
6
+
7
+ Performs decoding of a JWT token.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: jwt.encode
3
+ rules:
4
+ - jwt-algorithm-none
5
+ ---
6
+
7
+ Performs encoding of a JWT token.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: jwt.signature.verify
3
+ rules:
4
+ - jwt-unverified-signature
5
+ ---
6
+
7
+ Verifies the signature on a JWT token to ensure that it's authentic.
@@ -2,7 +2,13 @@
2
2
  rule: jwt-algorithm-none
3
3
  name: Jwt algorithm none
4
4
  title: JWT 'none' algorithm
5
+ references:
6
+ CWE-345: https://cwe.mitre.org/data/definitions/345.html
7
+ A02:2021: https://owasp.org/Top10/A02_2021-Cryptographic_Failures
8
+ RFC 7519: https://www.rfc-editor.org/rfc/rfc7519
5
9
  impactDomain: Security
10
+ labels:
11
+ - jwt.encode
6
12
  ---
7
13
 
8
14
  Finds usage of unsecured JWTs which use the `none` algorithm. When declaring this algorithm, there
@@ -2,7 +2,14 @@
2
2
  rule: jwt-unverified-signature
3
3
  name: Jwt unverified signature
4
4
  title: Unverified signature
5
+ references:
6
+ CWE-345: https://cwe.mitre.org/data/definitions/345.html
7
+ A02:2021: https://owasp.org/Top10/A02_2021-Cryptographic_Failures
8
+ RFC 7519: https://www.rfc-editor.org/rfc/rfc7519
5
9
  impactDomain: Security
10
+ labels:
11
+ - jwt.decode
12
+ - jwt.signature.verify
6
13
  ---
7
14
 
8
15
  Finds cases where a JWT is decoded but the signature is never verified. Without proper signature
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appland/scanner",
3
- "version": "1.70.3",
3
+ "version": "1.70.5",
4
4
  "description": "",
5
5
  "bin": "built/cli.js",
6
6
  "files": [
@@ -26,6 +26,7 @@
26
26
  "@semantic-release/changelog": "^6.0.1",
27
27
  "@semantic-release/git": "^10.0.1",
28
28
  "@types/async": "^3.2.12",
29
+ "@types/crypto-js": "^4.1.1",
29
30
  "@types/fs-extra": "^9.0.13",
30
31
  "@types/glob": "^7.2.0",
31
32
  "@types/jest": "^27.4.1",
@@ -69,6 +70,7 @@
69
70
  "chokidar": "applandinc/chokidar#fix/new-file-new-directory-race-on-linux",
70
71
  "cli-progress": "^3.11.0",
71
72
  "conf": "^10.0.2",
73
+ "crypto-js": "^4.0.0",
72
74
  "form-data": "^4.0.0",
73
75
  "glob": "7.2.3",
74
76
  "inquirer": "^8.1.2",