@appland/scanner 1.65.0 → 1.68.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 +38 -0
- package/built/cli/scan/breakpoint.js +65 -0
- package/built/cli/scan/command.js +28 -18
- package/built/cli/scan/interactiveScan.js +34 -0
- package/built/cli/scan/singleScan.js +6 -14
- package/built/cli/scan/ui/interactiveProgess.js +147 -0
- package/built/cli/scan/ui/scanContext.js +67 -0
- package/built/cli/scan/ui/state/addBreakpoint.js +120 -0
- package/built/cli/scan/ui/state/eval.js +41 -0
- package/built/cli/scan/ui/state/hint.js +23 -0
- package/built/cli/scan/ui/state/hitBreakpoint.js +68 -0
- package/built/cli/scan/ui/state/initial.js +58 -0
- package/built/cli/scan/ui/state/scan.js +33 -0
- package/built/cli/scan/ui/state.js +2 -0
- package/built/cli/scan/ui/userInteraction.js +97 -0
- package/built/configuration/configurationProvider.js +1 -1
- package/built/database/index.js +15 -2
- package/built/progressReporter.js +2 -0
- package/built/ruleChecker.js +22 -10
- package/built/rules/deprecated-crypto-algorithm/metadata.js +14 -0
- package/built/rules/deprecated-crypto-algorithm/rule.js +44 -0
- package/built/rules/lib/parseRuleDescription.js +4 -1
- package/built/rules/lib/util.js +31 -1
- package/built/rules/too-many-joins/metadata.js +10 -0
- package/built/rules/too-many-joins/options.js +12 -0
- package/built/rules/{tooManyJoins.js → too-many-joins/rule.js} +14 -24
- package/built/sampleConfig/default.yml +1 -0
- package/doc/labels/crypto.decrypt.md +7 -0
- package/doc/labels/crypto.digest.md +7 -0
- package/doc/labels/crypto.encrypt.md +1 -0
- package/doc/rules/deprecated-crypto-algorithm.md +15 -0
- package/doc/rules/http-500.md +1 -1
- package/package.json +1 -1
|
@@ -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,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
|
}
|
package/built/database/index.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/built/ruleChecker.js
CHANGED
|
@@ -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(
|
|
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,
|
|
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,
|
|
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,
|
|
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;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.labels = void 0;
|
|
4
|
+
exports.labels = ['crypto.encrypt', 'crypto.decrypt', 'crypto.digest'];
|
|
5
|
+
exports.default = {
|
|
6
|
+
title: 'Deprecated cryptographic algorithm',
|
|
7
|
+
scope: 'root',
|
|
8
|
+
enumerateScope: true,
|
|
9
|
+
impactDomain: 'Security',
|
|
10
|
+
references: {
|
|
11
|
+
'A02:2021': 'https://owasp.org/Top10/A02_2021-Cryptographic_Failures/',
|
|
12
|
+
},
|
|
13
|
+
labels: exports.labels,
|
|
14
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deprecatedAlgorithms = void 0;
|
|
4
|
+
const metadata_1 = require("./metadata");
|
|
5
|
+
exports.deprecatedAlgorithms = [
|
|
6
|
+
/\bcbc\b/i,
|
|
7
|
+
/\becb\b/i,
|
|
8
|
+
/\b3?des\b/i,
|
|
9
|
+
/\brc\d\b/i,
|
|
10
|
+
/\bmd\d\b/i,
|
|
11
|
+
/\bsha-?1\b/i,
|
|
12
|
+
];
|
|
13
|
+
// Also:
|
|
14
|
+
// https://securitymusings.com/article/1587/algorithm-and-key-length-deprecation
|
|
15
|
+
// http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
|
|
16
|
+
// Password handling: As soon as you receive a password, hash it using scrypt or PBKDF2 and erase the plaintext password from memory.
|
|
17
|
+
// 1024-bit RSA or DSA
|
|
18
|
+
// 160-bit ECDSA (elliptic curves)
|
|
19
|
+
// 80/112-bit 2TDEA (two key triple DES)
|
|
20
|
+
// PKCS #1 v1.5
|
|
21
|
+
function matcher(event) {
|
|
22
|
+
if (!event.receiver)
|
|
23
|
+
return;
|
|
24
|
+
const receiverLabels = event.receiver.labels || [];
|
|
25
|
+
const deprecatedAlgorithm = receiverLabels
|
|
26
|
+
.filter((label) => label.startsWith('crypto.algorithm.'))
|
|
27
|
+
.map((label) => label.split('.').slice(2).join('.'))
|
|
28
|
+
.find((label) => exports.deprecatedAlgorithms.find((alg) => alg.test(label)));
|
|
29
|
+
if (deprecatedAlgorithm) {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
event,
|
|
33
|
+
message: `Deprecated crypto algorithm: ${deprecatedAlgorithm}`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function rule() {
|
|
39
|
+
return {
|
|
40
|
+
matcher,
|
|
41
|
+
where: (e) => !!metadata_1.labels.find((label) => e.labels.has(label)),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
exports.default = rule;
|
|
@@ -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
|
|
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.
|
package/built/rules/lib/util.js
CHANGED
|
@@ -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) {
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class Options {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.warningLimit = 5;
|
|
6
|
+
this.excludeTables = [
|
|
7
|
+
{ match: /^pg_/, ignoreCase: false },
|
|
8
|
+
{ match: /^information_schema$/, ignoreCase: true },
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.default = Options;
|
|
@@ -3,24 +3,26 @@ 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
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const assert_1 = __importDefault(require("assert"));
|
|
7
|
+
const database_1 = require("../../database");
|
|
8
|
+
const matchPattern_1 = require("../lib/matchPattern");
|
|
9
|
+
function rule(options) {
|
|
10
|
+
const excludeTables = (0, matchPattern_1.buildFilters)(options.excludeTables);
|
|
11
|
+
function filterTable(table) {
|
|
12
|
+
(0, assert_1.default)(table.type === 'identifier');
|
|
13
|
+
(0, assert_1.default)(table.variant === 'table');
|
|
14
|
+
return !excludeTables.find((filter) => filter(table.name));
|
|
12
15
|
}
|
|
13
|
-
|
|
14
|
-
// TODO: clean up (https://github.com/applandinc/scanner/issues/43)
|
|
15
|
-
function build(options = new Options()) {
|
|
16
|
-
const joinCount = {};
|
|
16
|
+
// TODO: clean up (https://github.com/applandinc/scanner/issues/43)
|
|
17
17
|
function matcher(command, appMapIndex, eventFilter) {
|
|
18
|
+
const joinCount = {};
|
|
18
19
|
for (const sqlEvent of (0, database_1.sqlStrings)(command, appMapIndex, eventFilter)) {
|
|
19
20
|
let occurrence = joinCount[sqlEvent.sql];
|
|
20
21
|
if (!occurrence) {
|
|
22
|
+
const sqlAST = appMapIndex.sqlAST(sqlEvent.event);
|
|
21
23
|
occurrence = {
|
|
22
24
|
count: 1,
|
|
23
|
-
joins: (0, database_1.countJoins)(
|
|
25
|
+
joins: (0, database_1.countJoins)(sqlAST, filterTable),
|
|
24
26
|
events: [sqlEvent.event],
|
|
25
27
|
};
|
|
26
28
|
joinCount[sqlEvent.sql] = occurrence;
|
|
@@ -46,16 +48,4 @@ function build(options = new Options()) {
|
|
|
46
48
|
matcher,
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
|
-
exports.default =
|
|
50
|
-
id: 'too-many-joins',
|
|
51
|
-
title: 'Too many joins',
|
|
52
|
-
impactDomain: 'Performance',
|
|
53
|
-
enumerateScope: false,
|
|
54
|
-
references: {
|
|
55
|
-
'CWE-1049': new url_1.URL('https://cwe.mitre.org/data/definitions/1049.html'),
|
|
56
|
-
},
|
|
57
|
-
description: (0, parseRuleDescription_1.default)('tooManyJoins'),
|
|
58
|
-
url: 'https://appland.com/docs/analysis/rules-reference.html#too-many-joins',
|
|
59
|
-
Options,
|
|
60
|
-
build,
|
|
61
|
-
};
|
|
51
|
+
exports.default = rule;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
rule: deprecated-crypto-algorithm
|
|
3
|
+
name: Deprecated crypto algorithm
|
|
4
|
+
title: Deprecated cryptographic algorithm
|
|
5
|
+
references:
|
|
6
|
+
A02:2021: https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
|
|
7
|
+
impactDomain: Security
|
|
8
|
+
labels:
|
|
9
|
+
- crypto.encrypt
|
|
10
|
+
- crypto.decrypt
|
|
11
|
+
- crypto.digest
|
|
12
|
+
scope: root
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
Ensure that cryptographic operations do not use deprecated algorithms.
|
package/doc/rules/http-500.md
CHANGED
|
@@ -8,7 +8,7 @@ impactDomain: Stability
|
|
|
8
8
|
scope: http_server_request
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
Identifies when an HTTP server
|
|
11
|
+
Identifies when an HTTP server request has returned a 500 status code. HTTP 500 status code
|
|
12
12
|
generally indicate an unanticipated problem in the backend that is not handled in a predictable way.
|
|
13
13
|
500 status codes are also hard for client code to handle, because they don't indicate any particular
|
|
14
14
|
problem or suggest a solution.
|