@appland/scanner 1.66.0 → 1.69.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.
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const userInteraction_1 = __importDefault(require("../userInteraction"));
39
+ const initial_1 = __importDefault(require("./initial"));
40
+ function hitBreakpoint(context) {
41
+ var _a, _b;
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ const choices = {
44
+ 'show hints': 'hint',
45
+ 'evaluate expression': 'eval',
46
+ 'add breakpoint': 'addBreakpoint',
47
+ continue: 'scan',
48
+ quit: null,
49
+ };
50
+ userInteraction_1.default.progress(`In breakpoint: ${context.breakpoint}`);
51
+ if (context.progress.appMapFileName) {
52
+ const line = context.progress.appMapFileName;
53
+ const eventId = ((_a = context.progress.event) === null || _a === void 0 ? void 0 : _a.id) || ((_b = context.progress.scope) === null || _b === void 0 ? void 0 : _b.id);
54
+ userInteraction_1.default.progress([line, eventId].filter(Boolean).join(':'));
55
+ }
56
+ const { action: actionName } = yield userInteraction_1.default.prompt({
57
+ name: 'action',
58
+ type: 'list',
59
+ message: 'Choose action:',
60
+ choices: Object.keys(choices),
61
+ });
62
+ const action = choices[actionName];
63
+ if (!action)
64
+ return initial_1.default;
65
+ return (yield Promise.resolve().then(() => __importStar(require(`./${action}`)))).default;
66
+ });
67
+ }
68
+ exports.default = hitBreakpoint;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const userInteraction_1 = __importDefault(require("../userInteraction"));
39
+ function initial(_context) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ const choices = {
42
+ 'add breakpoint': 'addBreakpoint',
43
+ 'run scan': 'scan',
44
+ quit: null,
45
+ };
46
+ const { action: actionName } = yield userInteraction_1.default.prompt({
47
+ name: 'action',
48
+ type: 'list',
49
+ message: 'How would you like to proceed?:',
50
+ choices: Object.keys(choices),
51
+ });
52
+ const action = choices[actionName];
53
+ if (!action)
54
+ return;
55
+ return (yield Promise.resolve().then(() => __importStar(require(`./${action}`)))).default;
56
+ });
57
+ }
58
+ exports.default = initial;
@@ -0,0 +1,33 @@
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
+ const hitBreakpoint_1 = __importDefault(require("./hitBreakpoint"));
16
+ const initial_1 = __importDefault(require("./initial"));
17
+ function scan(context) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ return new Promise((resolve) => {
20
+ context.on('breakpoint', (breakpoint) => {
21
+ context.removeAllListeners();
22
+ context.breakpoint = breakpoint;
23
+ resolve(hitBreakpoint_1.default);
24
+ });
25
+ context.on('complete', () => {
26
+ context.removeAllListeners();
27
+ resolve(initial_1.default);
28
+ });
29
+ context.scan();
30
+ });
31
+ });
32
+ }
33
+ exports.default = scan;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,97 @@
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
+ exports.UserInteraction = void 0;
16
+ const inquirer_1 = __importDefault(require("inquirer"));
17
+ const ora_1 = __importDefault(require("ora"));
18
+ const boxen_1 = __importDefault(require("boxen"));
19
+ const util_1 = require("../../../rules/lib/util");
20
+ // KEG: Sorry, copied from packages/cli/src/cmds/userInteraction.ts
21
+ class UserInteraction {
22
+ constructor() {
23
+ this.spinner = (0, ora_1.default)();
24
+ }
25
+ prompt(questions, opts) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ const wasSpinning = this.spinner.isSpinning;
28
+ if (wasSpinning) {
29
+ this.spinner.stop();
30
+ this.spinner.clear();
31
+ }
32
+ const result = yield inquirer_1.default.prompt(questions);
33
+ if (wasSpinning && !(opts === null || opts === void 0 ? void 0 : opts.supressSpinner)) {
34
+ this.spinner.start();
35
+ }
36
+ return result;
37
+ });
38
+ }
39
+ continue(msg) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ yield inquirer_1.default.prompt({ type: 'input', name: 'confirm', message: msg });
42
+ });
43
+ }
44
+ confirm(msg) {
45
+ return __awaiter(this, void 0, void 0, function* () {
46
+ const { confirm } = yield inquirer_1.default.prompt({
47
+ type: 'confirm',
48
+ name: 'confirm',
49
+ message: msg,
50
+ });
51
+ return confirm;
52
+ });
53
+ }
54
+ progress(msg) {
55
+ console.log(msg);
56
+ }
57
+ success(msg, align = 'center') {
58
+ if (this.spinner.isSpinning) {
59
+ this.spinner.succeed();
60
+ }
61
+ if (msg) {
62
+ console.log((0, boxen_1.default)(msg, {
63
+ padding: 1,
64
+ margin: 1,
65
+ borderStyle: 'round',
66
+ textAlignment: align,
67
+ }));
68
+ }
69
+ }
70
+ error(msg) {
71
+ if (this.spinner.isSpinning) {
72
+ this.spinner.fail();
73
+ }
74
+ if (msg) {
75
+ console.error('');
76
+ console.error(msg);
77
+ }
78
+ }
79
+ warn(msg) {
80
+ console.error(msg);
81
+ }
82
+ get status() {
83
+ return this.spinner.text;
84
+ }
85
+ set status(value) {
86
+ if (this.spinner.isSpinning) {
87
+ this.spinner.succeed();
88
+ }
89
+ this.spinner.text = value;
90
+ if (!this.spinner.isSpinning && !(0, util_1.verbose)()) {
91
+ this.spinner.start();
92
+ }
93
+ }
94
+ }
95
+ exports.UserInteraction = UserInteraction;
96
+ const UI = new UserInteraction();
97
+ exports.default = UI;
@@ -83,7 +83,7 @@ function loadFromDir(ruleName) {
83
83
  if ((0, util_1.verbose)())
84
84
  console.log(`Loaded rule ${ruleName}: ${rule}`);
85
85
  try {
86
- options = yield Promise.resolve().then(() => __importStar(require(`../rules/${ruleName}/options`)));
86
+ options = (yield Promise.resolve().then(() => __importStar(require(`../rules/${ruleName}/options`)))).default;
87
87
  if ((0, util_1.verbose)())
88
88
  console.log(`Loaded rule ${ruleName} options: ${options}`);
89
89
  }
@@ -126,14 +126,27 @@ function* sqlStrings(event, appMapIndex, filter = () => true) {
126
126
  }
127
127
  }
128
128
  exports.sqlStrings = sqlStrings;
129
- function countJoins(ast) {
129
+ function countJoins(ast, filterTable) {
130
130
  if (!ast)
131
131
  return 0;
132
132
  let joins = 0;
133
133
  (0, visit_1.visit)(ast, {
134
134
  'map.join': (node) => {
135
135
  var _a;
136
- joins += ((_a = node.map) !== null && _a !== void 0 ? _a : []).length;
136
+ const map = (_a = node.map) !== null && _a !== void 0 ? _a : [];
137
+ const tableCount = filterTable
138
+ ? map.filter((entry) => {
139
+ const source = entry === null || entry === void 0 ? void 0 : entry.source;
140
+ if (!source)
141
+ return true;
142
+ if (source.type !== 'identifier')
143
+ return true;
144
+ if (source.variant !== 'table')
145
+ return true;
146
+ return filterTable(source);
147
+ }).length
148
+ : map.length;
149
+ joins += tableCount;
137
150
  },
138
151
  });
139
152
  return joins;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -24,7 +24,8 @@ const eventUtil_1 = require("./eventUtil");
24
24
  const hashV1_1 = __importDefault(require("./algorithms/hash/hashV1"));
25
25
  const hashV2_1 = __importDefault(require("./algorithms/hash/hashV2"));
26
26
  class RuleChecker {
27
- constructor() {
27
+ constructor(progress) {
28
+ this.progress = progress;
28
29
  this.scopes = {
29
30
  root: new rootScope_1.default(),
30
31
  command: new commandScope_1.default(),
@@ -33,15 +34,14 @@ class RuleChecker {
33
34
  transaction: new sqlTransactionScope_1.default(),
34
35
  };
35
36
  }
36
- check(appMapFile, appMapIndex, check, findings) {
37
+ check(appMapFileName, appMapIndex, check, findings) {
37
38
  return __awaiter(this, void 0, void 0, function* () {
38
- const scope = check.scope;
39
39
  if ((0, util_1.verbose)()) {
40
- console.warn(`Checking AppMap ${appMapIndex.appMap.name} with scope ${scope}`);
40
+ console.warn(`Checking AppMap ${appMapIndex.appMap.name} with scope ${check.scope}`);
41
41
  }
42
- const scopeIterator = this.scopes[scope];
42
+ const scopeIterator = this.scopes[check.scope];
43
43
  if (!scopeIterator) {
44
- throw new errors_1.AbortError(`Invalid scope name "${scope}"`);
44
+ throw new errors_1.AbortError(`Invalid scope name "${check.scope}"`);
45
45
  }
46
46
  const callEvents = function* () {
47
47
  const events = appMapIndex.appMap.events;
@@ -53,22 +53,28 @@ class RuleChecker {
53
53
  if ((0, util_1.verbose)()) {
54
54
  console.warn(`Scope ${scope.scope}`);
55
55
  }
56
+ if (this.progress)
57
+ yield this.progress.filterScope(check.scope, scope.scope);
56
58
  const checkInstance = new checkInstance_1.default(check);
57
59
  if (!check.filterScope(scope.scope, appMapIndex)) {
58
60
  continue;
59
61
  }
62
+ if (this.progress)
63
+ yield this.progress.enterScope(scope.scope);
60
64
  if (checkInstance.enumerateScope) {
61
65
  for (const event of scope.events()) {
62
- yield this.checkEvent(event, scope.scope, appMapFile, appMapIndex, checkInstance, findings);
66
+ yield this.checkEvent(event, scope.scope, appMapFileName, appMapIndex, checkInstance, findings);
63
67
  }
64
68
  }
65
69
  else {
66
- yield this.checkEvent(scope.scope, scope.scope, appMapFile, appMapIndex, checkInstance, findings);
70
+ yield this.checkEvent(scope.scope, scope.scope, appMapFileName, appMapIndex, checkInstance, findings);
67
71
  }
72
+ if (this.progress)
73
+ yield this.progress.leaveScope();
68
74
  }
69
75
  });
70
76
  }
71
- checkEvent(event, scope, appMapFile, appMapIndex, checkInstance, findings) {
77
+ checkEvent(event, scope, appMapFileName, appMapIndex, checkInstance, findings) {
72
78
  return __awaiter(this, void 0, void 0, function* () {
73
79
  if (!event.isCall()) {
74
80
  return;
@@ -82,6 +88,8 @@ class RuleChecker {
82
88
  }
83
89
  return;
84
90
  }
91
+ if (this.progress)
92
+ yield this.progress.filterEvent(event);
85
93
  if (!checkInstance.filterEvent(event, appMapIndex)) {
86
94
  return;
87
95
  }
@@ -117,7 +125,7 @@ class RuleChecker {
117
125
  relatedEvents.push((0, eventUtil_1.cloneEvent)(event));
118
126
  });
119
127
  return {
120
- appMapFile,
128
+ appMapFile: appMapFileName,
121
129
  checkId: checkInstance.checkId,
122
130
  ruleId: checkInstance.ruleId,
123
131
  ruleTitle: checkInstance.title,
@@ -134,7 +142,11 @@ class RuleChecker {
134
142
  participatingEvents: Object.fromEntries(Object.entries(participatingEvents).map(([k, v]) => [k, (0, eventUtil_1.cloneEvent)(v)])),
135
143
  };
136
144
  };
145
+ if (this.progress)
146
+ yield this.progress.matchEvent(event, appMapIndex);
137
147
  const matchResult = yield checkInstance.ruleLogic.matcher(event, appMapIndex, checkInstance.filterEvent.bind(checkInstance));
148
+ if (this.progress)
149
+ yield this.progress.matchResult(event, matchResult);
138
150
  const numFindings = findings.length;
139
151
  if (matchResult === true) {
140
152
  let finding;
@@ -3,46 +3,62 @@ 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
- const models_1 = require("@appland/models");
7
6
  const url_1 = require("url");
8
7
  const parseRuleDescription_1 = __importDefault(require("./lib/parseRuleDescription"));
9
- const precedingEvents_1 = __importDefault(require("./lib/precedingEvents"));
10
- const sanitizesData_1 = __importDefault(require("./lib/sanitizesData"));
11
- function allArgumentsSanitized(rootEvent, event) {
12
- return (event.parameters || [])
13
- .filter((parameter) => parameter.object_id)
14
- .every((parameter) => {
15
- for (const candidate of (0, precedingEvents_1.default)(rootEvent, event)) {
16
- if ((0, sanitizesData_1.default)(candidate.event, parameter.object_id, DeserializeSanitize)) {
17
- return true;
18
- }
19
- }
20
- return false;
21
- });
8
+ const analyzeDataFlow_1 = __importDefault(require("./lib/analyzeDataFlow"));
9
+ function valueHistory(value) {
10
+ const events = [];
11
+ const queue = [value];
12
+ for (;;) {
13
+ const current = queue.shift();
14
+ if (!current)
15
+ break;
16
+ const { origin, parents } = current;
17
+ if (!events.includes(origin))
18
+ events.push(origin);
19
+ queue.push(...parents);
20
+ }
21
+ return events;
22
+ }
23
+ function wasSanitized(value) {
24
+ return valueHistory(value).some(({ labels }) => labels.has(DeserializeSanitize));
22
25
  }
23
- function build() {
24
- function matcher(rootEvent) {
25
- for (const event of new models_1.EventNavigator(rootEvent).descendants()) {
26
- // events: //*[@authorization && truthy?(returnValue) && not(preceding::*[@authentication]) && not(descendant::*[@authentication])]
27
- if (event.event.labels.has(DeserializeUnsafe) &&
28
- !event.event.ancestors().find((ancestor) => ancestor.labels.has(DeserializeSafe))) {
29
- if (allArgumentsSanitized(rootEvent, event.event)) {
30
- return;
31
- }
32
- else {
33
- return [
34
- {
35
- event: event.event,
36
- message: `${event.event} deserializes untrusted data`,
37
- },
38
- ];
39
- }
26
+ function formatHistories(values) {
27
+ const histories = values.map(valueHistory);
28
+ return Object.fromEntries(histories.flatMap((history, input) => history.map((event, idx) => [`origin[${input}][${idx}]`, event])));
29
+ }
30
+ function label(name) {
31
+ return ({ labels }) => labels.has(name);
32
+ }
33
+ function matcher(startEvent) {
34
+ const flow = (0, analyzeDataFlow_1.default)([...(startEvent.message || [])], startEvent);
35
+ const results = [];
36
+ const sanitizedValues = new Set();
37
+ for (const [event, values] of flow) {
38
+ if (event.labels.has(DeserializeSanitize)) {
39
+ for (const v of values)
40
+ sanitizedValues.add(v);
41
+ continue;
42
+ }
43
+ if (!event.labels.has(DeserializeUnsafe))
44
+ continue;
45
+ const unsanitized = new Set(values.filter((v) => !(wasSanitized(v) || sanitizedValues.has(v))));
46
+ // remove any that have been passed into a safe deserialization function
47
+ for (const ancestor of event.ancestors().filter(label(DeserializeSafe))) {
48
+ for (const v of flow.get(ancestor) || []) {
49
+ unsanitized.delete(v);
40
50
  }
41
51
  }
52
+ const remaining = [...unsanitized];
53
+ if (remaining.length === 0)
54
+ continue;
55
+ results.push({
56
+ event: event,
57
+ message: `deserializes untrusted data: ${remaining.map(({ value: { value } }) => value)}`,
58
+ participatingEvents: formatHistories(remaining),
59
+ });
42
60
  }
43
- return {
44
- matcher,
45
- };
61
+ return results;
46
62
  }
47
63
  const DeserializeUnsafe = 'deserialize.unsafe';
48
64
  const DeserializeSafe = 'deserialize.safe';
@@ -53,11 +69,12 @@ exports.default = {
53
69
  labels: [DeserializeUnsafe, DeserializeSafe, DeserializeSanitize],
54
70
  impactDomain: 'Security',
55
71
  enumerateScope: false,
72
+ scope: 'http_server_request',
56
73
  references: {
57
74
  'CWE-502': new url_1.URL('https://cwe.mitre.org/data/definitions/502.html'),
58
75
  'Ruby Security': new url_1.URL('https://docs.ruby-lang.org/en/3.0/doc/security_rdoc.html'),
59
76
  },
60
77
  description: (0, parseRuleDescription_1.default)('deserializationOfUntrustedData'),
61
78
  url: 'https://appland.com/docs/analysis/rules-reference.html#deserialization-of-untrusted-data',
62
- build,
79
+ build: () => ({ matcher }),
63
80
  };
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function matches(template, value) {
4
+ if (template.object_id && template.object_id === value.object_id)
5
+ return true;
6
+ if (template.value && template.value === value.value)
7
+ return true;
8
+ return false;
9
+ }
10
+ class Matcher {
11
+ constructor(root, data) {
12
+ this.tracked = new Map();
13
+ for (const value of data)
14
+ this.add(value, root, []);
15
+ }
16
+ add(value, origin, parents) {
17
+ if (isPrimitive(value))
18
+ return;
19
+ this.tracked.set(value, { value, origin, parents });
20
+ }
21
+ match(value) {
22
+ if (isPrimitive(value))
23
+ return null;
24
+ for (const [probe, history] of this.tracked) {
25
+ if (matches(probe, value))
26
+ return history;
27
+ }
28
+ return null;
29
+ }
30
+ matches(values) {
31
+ return compact(values.map(this.match.bind(this)));
32
+ }
33
+ }
34
+ function isPrimitive(value) {
35
+ // we don't want to record any nulls,
36
+ // booleans or small strings and numbers
37
+ return !value.value || value.value.length < 6;
38
+ }
39
+ function isNotNullOrUndefined(x) {
40
+ return x !== undefined && x !== null;
41
+ }
42
+ function compact(x) {
43
+ return x.filter(isNotNullOrUndefined);
44
+ }
45
+ /**
46
+ * Tracks flow of data across the execution trace, identifying all function
47
+ * calls which have a tracked object as its receiver or one of the parameters.
48
+ * Any value such a function returns will also then become tracked.
49
+ * The origin chain of all values is recorded, so full provenience up to
50
+ * the starting set can be reconstructed.
51
+ * @param trackedData Initial data to track.
52
+ * @param startEvent The root event of the analysis.
53
+ * @returns Events which have a tracked piece of data as an input, each
54
+ * associated with the list of such inputs.
55
+ */
56
+ function analyzeDataFlow(trackedData, startEvent) {
57
+ const matcher = new Matcher(startEvent, trackedData);
58
+ const events = new Map([
59
+ [startEvent, matcher.matches(trackedData)],
60
+ ]);
61
+ startEvent.traverse({
62
+ onEnter(event) {
63
+ const inputs = compact([...(event.parameters || []), event.receiver]);
64
+ const matches = matcher.matches(inputs);
65
+ if (matches.length === 0)
66
+ return;
67
+ events.set(event, matches);
68
+ },
69
+ onExit({ callEvent, returnValue }) {
70
+ if (!returnValue)
71
+ return;
72
+ const parents = events.get(callEvent);
73
+ if (!parents)
74
+ return;
75
+ matcher.add(returnValue, callEvent, parents);
76
+ },
77
+ });
78
+ return events;
79
+ }
80
+ exports.default = analyzeDataFlow;
@@ -7,7 +7,10 @@ const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = require("path");
8
8
  const util_1 = require("./util");
9
9
  function parseRuleDescription(id) {
10
- const content = fs_1.default.readFileSync((0, path_1.join)(__dirname, `../../../doc/rules/${(0, util_1.dasherize)(id)}.md`), 'utf-8');
10
+ const docPath = (0, path_1.join)(__dirname, `../../../doc/rules/${(0, util_1.dasherize)(id)}.md`);
11
+ if (!fs_1.default.existsSync(docPath))
12
+ return `No doc exists for rule ${id}`;
13
+ const content = fs_1.default.readFileSync(docPath, 'utf-8');
11
14
  const propertiesContent = content.match(/---\n((?:.*\n)+)---\n((?:.*\n)+?)##?#?/);
12
15
  if (!propertiesContent) {
13
16
  // This is probably a new doc that doesn't have front matter yet.
@@ -1,7 +1,37 @@
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
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
2
14
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verbose = exports.toRegExpArray = exports.responseContentType = exports.toRegExp = exports.providesAuthentication = exports.pluralize = exports.dasherize = exports.camelize = exports.parseValue = exports.isRoot = exports.ideLink = exports.isTruthy = exports.isFalsey = exports.emptyValue = exports.capitalize = exports.appMapDir = void 0;
15
+ exports.verbose = exports.toRegExpArray = exports.responseContentType = exports.toRegExp = exports.providesAuthentication = exports.pluralize = exports.dasherize = exports.camelize = exports.parseValue = exports.isRoot = exports.ideLink = exports.isTruthy = exports.isFalsey = exports.emptyValue = exports.capitalize = exports.appMapDir = exports.collectAppMapFiles = void 0;
4
16
  const path_1 = require("path");
17
+ const util_1 = require("util");
18
+ const glob_1 = require("glob");
19
+ const assert_1 = __importDefault(require("assert"));
20
+ function collectAppMapFiles(appmapFile, appmapDir) {
21
+ return __awaiter(this, void 0, void 0, function* () {
22
+ let files = [];
23
+ if (appmapDir) {
24
+ const glob = (0, util_1.promisify)(glob_1.glob);
25
+ files = yield glob(`${appmapDir}/**/*.appmap.json`);
26
+ }
27
+ else {
28
+ (0, assert_1.default)(appmapFile, 'Either appmapDir or appmapFile is required');
29
+ files = typeof appmapFile === 'string' ? [appmapFile] : appmapFile;
30
+ }
31
+ return files;
32
+ });
33
+ }
34
+ exports.collectAppMapFiles = collectAppMapFiles;
5
35
  let isVerbose = false;
6
36
  function verbose(v = null) {
7
37
  if (v === true || v === false) {
@@ -71,7 +101,7 @@ exports.parseValue = parseValue;
71
101
  const isTruthy = (valueObj) => !isFalsey(valueObj);
72
102
  exports.isTruthy = isTruthy;
73
103
  function providesAuthentication(event, label) {
74
- return event.returnValue && event.labels.has(label) && isTruthy(event.returnValue);
104
+ return !!event.returnValue && event.labels.has(label) && isTruthy(event.returnValue);
75
105
  }
76
106
  exports.providesAuthentication = providesAuthentication;
77
107
  function ideLink(filePath, ide, eventId) {
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ title: 'Too many joins',
5
+ enumerateScope: false,
6
+ impactDomain: 'Performance',
7
+ references: {
8
+ 'CWE-1049': 'https://cwe.mitre.org/data/definitions/1049.html',
9
+ },
10
+ };