blueprint-tsa 1.1.0 → 1.1.2
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/dist/commands/audit.js +8 -5
- package/dist/commands/bounce-check.js +2 -1
- package/dist/commands/command-utils.js +4 -1
- package/dist/commands/create-checker.d.ts +11 -0
- package/dist/commands/create-checker.js +70 -0
- package/dist/commands/drain-check.js +3 -1
- package/dist/commands/opcode-info.js +3 -1
- package/dist/commands/owner-hijack-check.js +3 -1
- package/dist/commands/replay-attack-check.js +2 -1
- package/dist/common/analyzer-wrapper.d.ts +2 -0
- package/dist/common/analyzer-wrapper.js +28 -20
- package/dist/common/build-utils.d.ts +1 -1
- package/dist/common/build-utils.js +7 -2
- package/dist/common/constants.d.ts +3 -3
- package/dist/common/constants.js +1 -1
- package/dist/common/opcode-extractor.d.ts +1 -0
- package/dist/common/opcode-extractor.js +1 -0
- package/dist/common/result-parsing.d.ts +1 -0
- package/dist/common/result-parsing.js +7 -1
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -89,7 +89,7 @@ function buildCheckResult(checkName, analyzer, passedMessage, failedMessage, con
|
|
|
89
89
|
}
|
|
90
90
|
return result;
|
|
91
91
|
}
|
|
92
|
-
async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcodes, verbose, legacyAnalysisArtifacts, iterationLimit, recursionLimit) {
|
|
92
|
+
async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcodes, verbose, legacyAnalysisArtifacts, iterationLimit, recursionLimit, interactive) {
|
|
93
93
|
// Calculate timeout per opcode
|
|
94
94
|
let opcodeTimeout = null;
|
|
95
95
|
if (timeout !== null && opcodes.length > 0) {
|
|
@@ -103,6 +103,7 @@ async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcod
|
|
|
103
103
|
contract: contractName,
|
|
104
104
|
iterationLimit,
|
|
105
105
|
recursionLimit,
|
|
106
|
+
interactive,
|
|
106
107
|
legacyAnalysisArtifacts,
|
|
107
108
|
};
|
|
108
109
|
const results = [];
|
|
@@ -118,13 +119,14 @@ async function runDrainCheck(contractName, contractPath, ui, commonArgs) {
|
|
|
118
119
|
const analyzer = await (0, drain_check_js_1.runDrainCheckAnalysis)(ui, contractPath, commonArgs, `${constants_js_1.DRAIN_CHECK_NAME} completed.`);
|
|
119
120
|
return buildCheckResult(constants_js_1.DRAIN_CHECK_NAME, analyzer, "No drain vulnerabilities detected", "Vulnerability found - contract may be vulnerable to drain attacks", contractName, commonArgs.timeout);
|
|
120
121
|
}
|
|
121
|
-
async function runReplayAttackCheck(contractName, contractPath, ui, timeout, verbose, iterationLimit, recursionLimit, legacyAnalysisArtifacts) {
|
|
122
|
+
async function runReplayAttackCheck(contractName, contractPath, ui, timeout, verbose, iterationLimit, recursionLimit, legacyAnalysisArtifacts, interactive) {
|
|
122
123
|
const analyzer = await (0, replay_attack_check_js_1.runReplayAttackCheckAnalysis)(ui, contractPath, {
|
|
123
124
|
timeout,
|
|
124
125
|
verbose,
|
|
125
126
|
contract: contractName,
|
|
126
127
|
iterationLimit,
|
|
127
128
|
recursionLimit,
|
|
129
|
+
interactive,
|
|
128
130
|
legacyAnalysisArtifacts,
|
|
129
131
|
}, null, `${constants_js_1.REPLAY_ATTACK_CHECK_NAME} completed.`);
|
|
130
132
|
return buildCheckResult(constants_js_1.REPLAY_ATTACK_CHECK_NAME, analyzer, "No replay attack vulnerabilities detected", "Vulnerability found - contract may be vulnerable to replay attacks", contractName, timeout);
|
|
@@ -222,7 +224,7 @@ const auditCommand = async (ui, parsedArgs) => {
|
|
|
222
224
|
const ownerMethod = parsedArgs["owner-method"];
|
|
223
225
|
const disableOpcodeExtraction = parsedArgs["disable-opcode-extraction"];
|
|
224
226
|
const verbose = parsedArgs.verbose;
|
|
225
|
-
await (0, build_utils_js_1.buildAllContracts)(ui);
|
|
227
|
+
await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
|
|
226
228
|
const contractPath = (0, paths_js_1.findCompiledContract)(contractName);
|
|
227
229
|
if (!(0, fs_1.existsSync)(contractPath)) {
|
|
228
230
|
ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contractName} not found`);
|
|
@@ -254,6 +256,7 @@ const auditCommand = async (ui, parsedArgs) => {
|
|
|
254
256
|
ui,
|
|
255
257
|
codePath: contractPath,
|
|
256
258
|
contractName,
|
|
259
|
+
interactive: parsedArgs.interactive,
|
|
257
260
|
});
|
|
258
261
|
}
|
|
259
262
|
// Calculate timeout if not provided
|
|
@@ -283,7 +286,7 @@ const auditCommand = async (ui, parsedArgs) => {
|
|
|
283
286
|
// Run opcode-info check
|
|
284
287
|
ui.write("");
|
|
285
288
|
ui.write(`${constants_js_1.Sym.WAIT} Step 1/${checkCount}: running opcode authorization analysis...`);
|
|
286
|
-
summary.opcodeInfo = await runOpcodeInfoCheck(contractName, contractPath, ui, effectiveTimeout, opcodes, verbose ?? false, parsedArgs[common_analyzer_args_js_1.VERBOSE_ANALYSIS_ARTIFACTS_OPTION], parsedArgs[common_analyzer_args_js_1.ITERATION_LIMIT_OPTION], parsedArgs[common_analyzer_args_js_1.RECURSION_LIMIT_OPTION]);
|
|
289
|
+
summary.opcodeInfo = await runOpcodeInfoCheck(contractName, contractPath, ui, effectiveTimeout, opcodes, verbose ?? false, parsedArgs[common_analyzer_args_js_1.VERBOSE_ANALYSIS_ARTIFACTS_OPTION], parsedArgs[common_analyzer_args_js_1.ITERATION_LIMIT_OPTION], parsedArgs[common_analyzer_args_js_1.RECURSION_LIMIT_OPTION], parsedArgs.interactive);
|
|
287
290
|
// Run drain-check
|
|
288
291
|
ui.write("");
|
|
289
292
|
ui.write(`${constants_js_1.Sym.WAIT} Step 2/${checkCount}: running drain check...`);
|
|
@@ -302,7 +305,7 @@ const auditCommand = async (ui, parsedArgs) => {
|
|
|
302
305
|
// Run replay-attack-check
|
|
303
306
|
ui.write("");
|
|
304
307
|
ui.write(`${constants_js_1.Sym.WAIT} Step 3/${checkCount}: running replay attack check...`);
|
|
305
|
-
const replayResult = await runReplayAttackCheck(contractName, contractPath, ui, effectiveTimeout, verbose ?? false, commonArgs.iterationLimit, commonArgs.recursionLimit, commonArgs.legacyAnalysisArtifacts ?? false);
|
|
308
|
+
const replayResult = await runReplayAttackCheck(contractName, contractPath, ui, effectiveTimeout, verbose ?? false, commonArgs.iterationLimit, commonArgs.recursionLimit, commonArgs.legacyAnalysisArtifacts ?? false, parsedArgs.interactive);
|
|
306
309
|
summary.checks.push(replayResult);
|
|
307
310
|
// Run bounce-check
|
|
308
311
|
ui.write("");
|
|
@@ -63,6 +63,7 @@ const runBounceCheckAnalysis = async (ui, contractPath, commonArgs, completionMe
|
|
|
63
63
|
checkerCell,
|
|
64
64
|
properties,
|
|
65
65
|
codePath: contractPath,
|
|
66
|
+
interactive: commonArgs.interactive ?? true,
|
|
66
67
|
legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
|
|
67
68
|
});
|
|
68
69
|
const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
|
|
@@ -94,7 +95,7 @@ const runBounceCheckAnalysis = async (ui, contractPath, commonArgs, completionMe
|
|
|
94
95
|
exports.runBounceCheckAnalysis = runBounceCheckAnalysis;
|
|
95
96
|
const bounceCheckCommand = async (ui, parsedArgs) => {
|
|
96
97
|
const contractName = parsedArgs.contract;
|
|
97
|
-
await (0, build_utils_js_1.buildAllContracts)(ui);
|
|
98
|
+
await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
|
|
98
99
|
const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
|
|
99
100
|
const { opcodes, timeout } = await (0, command_utils_js_1.resolveOpcodesAndTimeout)(ui, contractName, contractPath, {
|
|
100
101
|
disableOpcodeExtraction: parsedArgs["disable-opcode-extraction"],
|
|
@@ -33,7 +33,9 @@ const resolveBuiltContract = (ui, contractName) => {
|
|
|
33
33
|
exports.resolveBuiltContract = resolveBuiltContract;
|
|
34
34
|
const hasExplicitTimeout = (timeout) => timeout !== null && timeout !== undefined;
|
|
35
35
|
exports.hasExplicitTimeout = hasExplicitTimeout;
|
|
36
|
-
const isInteractiveEnabled = (interactive) => interactive !== false
|
|
36
|
+
const isInteractiveEnabled = (interactive) => interactive !== false &&
|
|
37
|
+
process.stdin.isTTY === true &&
|
|
38
|
+
process.stdout.isTTY === true;
|
|
37
39
|
exports.isInteractiveEnabled = isInteractiveEnabled;
|
|
38
40
|
const confirmOpcodeExtractionWait = async (ui, options) => {
|
|
39
41
|
if (!(0, exports.isInteractiveEnabled)(options.interactive)) {
|
|
@@ -89,6 +91,7 @@ const resolveOpcodesAndTimeout = async (ui, contractName, contractPath, opts) =>
|
|
|
89
91
|
ui,
|
|
90
92
|
codePath: contractPath,
|
|
91
93
|
contractName,
|
|
94
|
+
interactive: opts.interactive,
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
97
|
let timeout = opts.explicitTimeout ?? null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CommandModule } from "yargs";
|
|
2
|
+
import { CommandContext } from "../cli.js";
|
|
3
|
+
declare const SUPPORTED_LANGUAGES: readonly ["Tolk", "FunC"];
|
|
4
|
+
export type CheckerLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
5
|
+
export interface StoredCheckerDraft {
|
|
6
|
+
checkerLanguage: CheckerLanguage;
|
|
7
|
+
contractName: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createCreateCheckerCommand(context: CommandContext): CommandModule<object, object>;
|
|
10
|
+
export declare function executeCreateCheckerCommand(context: CommandContext): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
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.createCreateCheckerCommand = createCreateCheckerCommand;
|
|
7
|
+
exports.executeCreateCheckerCommand = executeCreateCheckerCommand;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const constants_js_1 = require("../common/constants.js");
|
|
11
|
+
const paths_js_1 = require("../common/paths.js");
|
|
12
|
+
const CREATE_CHECKER_COMMAND = "create-checker";
|
|
13
|
+
const CREATE_CHECKER_DESCRIPTION = "Collect information for a user-defined checker template";
|
|
14
|
+
const CHECKERS_DIRECTORY_NAME = "checkers";
|
|
15
|
+
const CHECKER_CONFIG_FILE_EXTENSION = ".json";
|
|
16
|
+
const TOLK_LANGUAGE = "Tolk";
|
|
17
|
+
const FUNC_LANGUAGE = "FunC";
|
|
18
|
+
const SUPPORTED_LANGUAGES = [TOLK_LANGUAGE, FUNC_LANGUAGE];
|
|
19
|
+
const CHOOSE_CHECKER_LANGUAGE_MESSAGE = "Choose checker language";
|
|
20
|
+
const EMPTY_VALUE_MESSAGE = "Value cannot be empty. Please try again.";
|
|
21
|
+
function createCreateCheckerCommand(context) {
|
|
22
|
+
return {
|
|
23
|
+
command: CREATE_CHECKER_COMMAND,
|
|
24
|
+
describe: CREATE_CHECKER_DESCRIPTION,
|
|
25
|
+
builder: {},
|
|
26
|
+
handler: async () => {
|
|
27
|
+
await executeCreateCheckerCommand(context);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function executeCreateCheckerCommand(context) {
|
|
32
|
+
const { ui } = context;
|
|
33
|
+
ui.write("Create custom checker draft");
|
|
34
|
+
ui.write("");
|
|
35
|
+
const checkerName = await promptRequiredValue(ui, "Checker name");
|
|
36
|
+
const checkerLanguage = await promptCheckerLanguage(ui);
|
|
37
|
+
const contractName = await promptRequiredValue(ui, "Contract name to check");
|
|
38
|
+
const checkerDraft = {
|
|
39
|
+
checkerLanguage,
|
|
40
|
+
contractName,
|
|
41
|
+
};
|
|
42
|
+
const draftPath = persistCheckerDraft(checkerName, checkerDraft);
|
|
43
|
+
ui.write("");
|
|
44
|
+
ui.write(`${constants_js_1.Sym.OK} Checker draft stored successfully.`);
|
|
45
|
+
ui.write(`Name: ${checkerName}`);
|
|
46
|
+
ui.write(`Language: ${checkerDraft.checkerLanguage}`);
|
|
47
|
+
ui.write(`Contract: ${checkerDraft.contractName}`);
|
|
48
|
+
ui.write(`Saved to: ${draftPath}`);
|
|
49
|
+
}
|
|
50
|
+
async function promptRequiredValue(ui, label) {
|
|
51
|
+
while (true) {
|
|
52
|
+
const value = (await ui.input(label)).trim();
|
|
53
|
+
if (value.length > 0) {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
ui.write(`${constants_js_1.Sym.WARN} ${EMPTY_VALUE_MESSAGE}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function promptCheckerLanguage(ui) {
|
|
60
|
+
const languageChoices = [...SUPPORTED_LANGUAGES];
|
|
61
|
+
return await ui.choose(CHOOSE_CHECKER_LANGUAGE_MESSAGE, languageChoices, (language) => language);
|
|
62
|
+
}
|
|
63
|
+
function persistCheckerDraft(checkerName, draft) {
|
|
64
|
+
const checkersDirectory = path_1.default.join((0, paths_js_1.findTSAProjectDirectory)(), CHECKERS_DIRECTORY_NAME);
|
|
65
|
+
(0, fs_1.mkdirSync)(checkersDirectory, { recursive: true });
|
|
66
|
+
const fileName = `${checkerName}${CHECKER_CONFIG_FILE_EXTENSION}`;
|
|
67
|
+
const filePath = path_1.default.join(checkersDirectory, fileName);
|
|
68
|
+
(0, fs_1.writeFileSync)(filePath, `${JSON.stringify(draft, null, 2)}\n`);
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
@@ -50,6 +50,7 @@ const runDrainCheckAnalysis = async (ui, contractPath, commonArgs, completionMes
|
|
|
50
50
|
checkerCell: (0, core_1.beginCell)().endCell(),
|
|
51
51
|
properties,
|
|
52
52
|
codePath: contractPath,
|
|
53
|
+
interactive: commonArgs.interactive ?? true,
|
|
53
54
|
legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
|
|
54
55
|
});
|
|
55
56
|
const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
|
|
@@ -82,7 +83,7 @@ const runDrainCheckAnalysis = async (ui, contractPath, commonArgs, completionMes
|
|
|
82
83
|
exports.runDrainCheckAnalysis = runDrainCheckAnalysis;
|
|
83
84
|
const drainCheckCommand = async (ui, parsedArgs) => {
|
|
84
85
|
const contractName = parsedArgs.contract;
|
|
85
|
-
await (0, build_utils_js_1.buildAllContracts)(ui);
|
|
86
|
+
await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
|
|
86
87
|
const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
|
|
87
88
|
const { opcodes, timeout } = await (0, command_utils_js_1.resolveOpcodesAndTimeout)(ui, contractName, contractPath, {
|
|
88
89
|
disableOpcodeExtraction: parsedArgs["disable-opcode-extraction"],
|
|
@@ -145,6 +146,7 @@ const drainCheckConcrete = async (ui, config, completionMessage = "Analysis comp
|
|
|
145
146
|
checkerCell,
|
|
146
147
|
properties,
|
|
147
148
|
codePath: config.codePath,
|
|
149
|
+
interactive: true,
|
|
148
150
|
});
|
|
149
151
|
await analyzer.run(constants_js_1.DRAIN_CHECK_CONCRETE_FILENAME, (wrapper) => [
|
|
150
152
|
"custom-checker-compiled",
|
|
@@ -64,6 +64,7 @@ async function runOpcodeAuthorizationCheckAnalysis(opcode, contractPath, ui, com
|
|
|
64
64
|
checkerCell,
|
|
65
65
|
properties,
|
|
66
66
|
codePath: contractPath,
|
|
67
|
+
interactive: commonArgs.interactive ?? true,
|
|
67
68
|
legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
|
|
68
69
|
});
|
|
69
70
|
const sarifPath = (0, paths_js_1.getSarifReportPath)(analyzer.id);
|
|
@@ -152,7 +153,7 @@ function formatOpcodeInfo(infos) {
|
|
|
152
153
|
const opcodeInfoHandler = async (context, args) => {
|
|
153
154
|
const { ui } = context;
|
|
154
155
|
const { timeout, contract, verbose } = args;
|
|
155
|
-
await (0, build_utils_js_1.buildAllContracts)(ui);
|
|
156
|
+
await (0, build_utils_js_1.buildAllContracts)(ui, args.interactive);
|
|
156
157
|
const codePath = (0, paths_js_1.findCompiledContract)(contract);
|
|
157
158
|
if (!(0, fs_1.existsSync)(codePath)) {
|
|
158
159
|
ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contract} not found`);
|
|
@@ -167,6 +168,7 @@ const opcodeInfoHandler = async (context, args) => {
|
|
|
167
168
|
ui,
|
|
168
169
|
codePath,
|
|
169
170
|
contractName: contract,
|
|
171
|
+
interactive: args.interactive,
|
|
170
172
|
});
|
|
171
173
|
if (opcodes.length === 0) {
|
|
172
174
|
ui.write("");
|
|
@@ -61,6 +61,7 @@ const runOwnerHijackCheckAnalysis = async (ui, contractPath, methodId, commonArg
|
|
|
61
61
|
checkerCell,
|
|
62
62
|
properties,
|
|
63
63
|
codePath: contractPath,
|
|
64
|
+
interactive: commonArgs.interactive ?? true,
|
|
64
65
|
legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
|
|
65
66
|
});
|
|
66
67
|
const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
|
|
@@ -96,7 +97,7 @@ exports.runOwnerHijackCheckAnalysis = runOwnerHijackCheckAnalysis;
|
|
|
96
97
|
const ownerHijackCheckCommand = async (ui, parsedArgs) => {
|
|
97
98
|
const contractName = parsedArgs.contract;
|
|
98
99
|
const methodId = BigInt((0, core_1.getMethodId)(parsedArgs["method-name"]));
|
|
99
|
-
await (0, build_utils_js_1.buildAllContracts)(ui);
|
|
100
|
+
await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
|
|
100
101
|
const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
|
|
101
102
|
const { opcodes, timeout } = await (0, command_utils_js_1.resolveOpcodesAndTimeout)(ui, contractName, contractPath, {
|
|
102
103
|
disableOpcodeExtraction: parsedArgs["disable-opcode-extraction"],
|
|
@@ -171,6 +172,7 @@ const ownerHijackCheckConcrete = async (ui, config, concreteCheckerOptions, comp
|
|
|
171
172
|
checkerCell,
|
|
172
173
|
properties,
|
|
173
174
|
codePath: config.codePath,
|
|
175
|
+
interactive: true,
|
|
174
176
|
});
|
|
175
177
|
await analyzer.run(constants_js_1.OWNER_HIJACK_CHECK_CONCRETE_FILENAME, (wrapper) => [
|
|
176
178
|
"custom-checker-compiled",
|
|
@@ -74,6 +74,7 @@ const runReplayAttackCheckAnalysis = async (ui, contractPath, commonArgs, seqnoD
|
|
|
74
74
|
checkerCell,
|
|
75
75
|
properties,
|
|
76
76
|
codePath: contractPath,
|
|
77
|
+
interactive: commonArgs.interactive ?? true,
|
|
77
78
|
legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
|
|
78
79
|
});
|
|
79
80
|
const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
|
|
@@ -111,7 +112,7 @@ const resolveSeqnoData = (ui, seqnoMethodName, seqnoRestriction) => {
|
|
|
111
112
|
};
|
|
112
113
|
const replayAttackCheckCommand = async (ui, parsedArgs) => {
|
|
113
114
|
const contractName = parsedArgs.contract;
|
|
114
|
-
await (0, build_utils_js_1.buildAllContracts)(ui);
|
|
115
|
+
await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
|
|
115
116
|
const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
|
|
116
117
|
const timeout = parsedArgs.timeout ?? null;
|
|
117
118
|
const seqnoData = resolveSeqnoData(ui, parsedArgs["seqno-method-name"], parsedArgs["seqno-restriction"]);
|
|
@@ -10,6 +10,7 @@ export interface AnalyzerWrapperConfig {
|
|
|
10
10
|
checkerCell: Cell;
|
|
11
11
|
properties: TreeProperty[];
|
|
12
12
|
codePath: string;
|
|
13
|
+
interactive: boolean;
|
|
13
14
|
legacyAnalysisArtifacts?: boolean;
|
|
14
15
|
expectsSarifReport?: boolean;
|
|
15
16
|
}
|
|
@@ -27,6 +28,7 @@ export declare class AnalyzerWrapper {
|
|
|
27
28
|
private tempCheckerCellPath;
|
|
28
29
|
private progressTimer;
|
|
29
30
|
id: string;
|
|
31
|
+
private isInteractiveEnabled;
|
|
30
32
|
constructor(config: AnalyzerWrapperConfig);
|
|
31
33
|
/**
|
|
32
34
|
* Prints analysis information to UI
|
|
@@ -15,10 +15,6 @@ const draw_js_1 = require("./draw.js");
|
|
|
15
15
|
const format_utils_js_1 = require("./format-utils.js");
|
|
16
16
|
const result_parsing_js_1 = require("./result-parsing.js");
|
|
17
17
|
const paths_js_1 = require("./paths.js");
|
|
18
|
-
/**
|
|
19
|
-
* Generic wrapper for checker-based vulnerability analysis
|
|
20
|
-
* Handles common logic for compiling, running, and cleaning up checker analysis
|
|
21
|
-
*/
|
|
22
18
|
const ANALYZER_RUNNING_MESSAGE = "Running TSA analysis...";
|
|
23
19
|
const ANALYZER_SUCCESS_LOG_PREFIX = "TSA run log saved to:";
|
|
24
20
|
const ANALYZER_NON_EMPTY_LOG_MESSAGE = "TSA produced additional log output. Check the log file for details.";
|
|
@@ -38,6 +34,11 @@ const MESSAGE_BODY_INPUT_KEY = "messageBody";
|
|
|
38
34
|
const CONTRACT_DATA_INPUT_KEY = "contractData";
|
|
39
35
|
const INDENT_SIZE = 2;
|
|
40
36
|
class AnalyzerWrapper {
|
|
37
|
+
isInteractiveEnabled() {
|
|
38
|
+
return (this.config.interactive !== false &&
|
|
39
|
+
process.stdin.isTTY === true &&
|
|
40
|
+
process.stdout.isTTY === true);
|
|
41
|
+
}
|
|
41
42
|
constructor(config) {
|
|
42
43
|
this.tempBocPath = null;
|
|
43
44
|
this.tempCheckerCellPath = null;
|
|
@@ -74,7 +75,9 @@ class AnalyzerWrapper {
|
|
|
74
75
|
if (this.config.checkerPath == null) {
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
|
-
this.
|
|
78
|
+
if (this.isInteractiveEnabled()) {
|
|
79
|
+
this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} Compiling checker...`);
|
|
80
|
+
}
|
|
78
81
|
try {
|
|
79
82
|
const bocCode = await (0, build_utils_js_1.compileFuncFileToBase64Boc)(this.config.checkerPath, checkerFilename);
|
|
80
83
|
const bocBuffer = Buffer.from(bocCode, "base64");
|
|
@@ -82,7 +85,9 @@ class AnalyzerWrapper {
|
|
|
82
85
|
(0, fs_1.writeFileSync)(this.tempBocPath, bocBuffer);
|
|
83
86
|
}
|
|
84
87
|
catch (error) {
|
|
85
|
-
this.
|
|
88
|
+
if (this.isInteractiveEnabled()) {
|
|
89
|
+
this.config.ui.clearActionPrompt();
|
|
90
|
+
}
|
|
86
91
|
this.config.ui.write(`\n${constants_js_1.Sym.ERR} Compilation failed: ${error.message}`);
|
|
87
92
|
process.exit(1);
|
|
88
93
|
}
|
|
@@ -140,6 +145,9 @@ class AnalyzerWrapper {
|
|
|
140
145
|
}
|
|
141
146
|
startProgressBar(timeoutSeconds) {
|
|
142
147
|
this.stopProgressBar();
|
|
148
|
+
if (!this.isInteractiveEnabled()) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
143
151
|
if (timeoutSeconds === null) {
|
|
144
152
|
this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} ${ANALYZER_RUNNING_MESSAGE}`);
|
|
145
153
|
return;
|
|
@@ -161,7 +169,7 @@ class AnalyzerWrapper {
|
|
|
161
169
|
}
|
|
162
170
|
async showCompletedProgressBar(timeoutSeconds) {
|
|
163
171
|
this.stopProgressBar();
|
|
164
|
-
if (timeoutSeconds === null) {
|
|
172
|
+
if (!this.isInteractiveEnabled() || timeoutSeconds === null) {
|
|
165
173
|
return;
|
|
166
174
|
}
|
|
167
175
|
this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} ${ANALYZER_RUNNING_MESSAGE} ${this.formatProgressBar(1)}`);
|
|
@@ -229,6 +237,7 @@ class AnalyzerWrapper {
|
|
|
229
237
|
const reportDir = path_1.default.dirname((0, paths_js_1.getSummaryPath)(this.id));
|
|
230
238
|
const sarifPath = (0, paths_js_1.getSarifReportPath)(this.id);
|
|
231
239
|
const exploitExecutionIndex = (0, result_parsing_js_1.findExploitExecutionIndex)(sarifPath);
|
|
240
|
+
const shouldRemoveSarifReport = (0, result_parsing_js_1.isSarifResultsEmpty)(sarifPath) || (0, result_parsing_js_1.isSingleNonFailingResult)(sarifPath);
|
|
232
241
|
const executionDirs = (0, fs_1.readdirSync)(reportDir)
|
|
233
242
|
.filter((entry) => entry.startsWith(EXECUTION_DIRECTORY_PREFIX))
|
|
234
243
|
.map((entry) => path_1.default.join(reportDir, entry))
|
|
@@ -267,6 +276,10 @@ class AnalyzerWrapper {
|
|
|
267
276
|
(0, fs_1.rmSync)(extraContractDataDir, { recursive: true, force: true });
|
|
268
277
|
}
|
|
269
278
|
}
|
|
279
|
+
if (shouldRemoveSarifReport && (0, fs_1.existsSync)(sarifPath)) {
|
|
280
|
+
(0, fs_1.unlinkSync)(sarifPath);
|
|
281
|
+
}
|
|
282
|
+
this.removeDirectoryIfEmpty(reportDir);
|
|
270
283
|
}
|
|
271
284
|
/**
|
|
272
285
|
* Runs the checker analysis with custom analyzer arguments
|
|
@@ -287,6 +300,7 @@ class AnalyzerWrapper {
|
|
|
287
300
|
const timeoutValue = timeoutIndex >= 0 ? (analyzerArgs[timeoutIndex + 1] ?? null) : null;
|
|
288
301
|
const timeoutSeconds = timeoutValue !== null ? Number.parseInt(timeoutValue, 10) : null;
|
|
289
302
|
const analyzer = await analyzer_js_1.Analyzer.create();
|
|
303
|
+
(0, paths_js_1.getReportDirectory)(this.id); // report directory should be created here
|
|
290
304
|
const logPath = (0, paths_js_1.getTsaRunLogPath)(this.id);
|
|
291
305
|
this.startProgressBar(Number.isFinite(timeoutSeconds) ? timeoutSeconds : null);
|
|
292
306
|
const result = await analyzer.run(analyzerArgs, logPath);
|
|
@@ -302,16 +316,7 @@ class AnalyzerWrapper {
|
|
|
302
316
|
if (!(0, fs_1.existsSync)(sarifPath)) {
|
|
303
317
|
throw new Error(MISSING_SARIF_ERROR_MESSAGE);
|
|
304
318
|
}
|
|
305
|
-
if (!this.usesVerboseAnalysisArtifacts()
|
|
306
|
-
(0, result_parsing_js_1.isSarifResultsEmpty)(sarifPath)) {
|
|
307
|
-
if (!hasLogOutput) {
|
|
308
|
-
(0, fs_1.rmSync)((0, paths_js_1.getReportDirectory)(this.id), {
|
|
309
|
-
recursive: true,
|
|
310
|
-
force: true,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
319
|
+
if (!this.usesVerboseAnalysisArtifacts()) {
|
|
315
320
|
this.normalizeExportedInputs();
|
|
316
321
|
}
|
|
317
322
|
}
|
|
@@ -379,12 +384,15 @@ class AnalyzerWrapper {
|
|
|
379
384
|
return;
|
|
380
385
|
}
|
|
381
386
|
const summaryPath = (0, paths_js_1.getSummaryPath)(this.id);
|
|
382
|
-
const
|
|
387
|
+
const relativeSummaryPath = path_1.default.relative(process.cwd(), summaryPath);
|
|
388
|
+
const relativeTypedInputPath = path_1.default.relative(process.cwd(), this.getResolvedTypedInputPath(vulnerability.executionIndex));
|
|
389
|
+
const relativeSarifPath = path_1.default.relative(process.cwd(), sarifPath);
|
|
390
|
+
const typedInputLine = `Typed input: ${relativeTypedInputPath}`;
|
|
383
391
|
const reportLines = [
|
|
384
392
|
`${constants_js_1.Sym.WARN} Vulnerability found!`,
|
|
385
|
-
`Summary path: ${
|
|
393
|
+
`Summary path: ${relativeSummaryPath}`,
|
|
386
394
|
typedInputLine,
|
|
387
|
-
`SARIF with full information: ${
|
|
395
|
+
`SARIF with full information: ${relativeSarifPath}`,
|
|
388
396
|
];
|
|
389
397
|
if (descriptionUrl) {
|
|
390
398
|
reportLines.push("");
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { UIProvider } from "@ton/blueprint";
|
|
2
|
-
export declare const buildAllContracts: (ui: UIProvider) => Promise<void>;
|
|
2
|
+
export declare const buildAllContracts: (ui: UIProvider, interactive: boolean) => Promise<void>;
|
|
3
3
|
export declare const compileFuncFileToBase64Boc: (filePath: string, fileName: string) => Promise<string>;
|
|
@@ -9,8 +9,13 @@ const fs_1 = require("fs");
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const func_js_1 = require("@ton-community/func-js");
|
|
11
11
|
const constants_js_1 = require("./constants.js");
|
|
12
|
-
const buildAllContracts = async (ui) => {
|
|
13
|
-
|
|
12
|
+
const buildAllContracts = async (ui, interactive) => {
|
|
13
|
+
const isInteractiveTerminal = interactive &&
|
|
14
|
+
process.stdin.isTTY === true &&
|
|
15
|
+
process.stdout.isTTY === true;
|
|
16
|
+
if (isInteractiveTerminal) {
|
|
17
|
+
ui.setActionPrompt(`${constants_js_1.Sym.WAIT} Compiling contracts...`);
|
|
18
|
+
}
|
|
14
19
|
try {
|
|
15
20
|
await (0, blueprint_1.buildAll)(ui);
|
|
16
21
|
}
|
|
@@ -4,9 +4,9 @@ export declare class Sym {
|
|
|
4
4
|
static ERR: string;
|
|
5
5
|
static WAIT: string;
|
|
6
6
|
}
|
|
7
|
-
export declare const TSA_VERSION = "v0.5.
|
|
8
|
-
export declare const TSA_NAME = "tsa-cli-v0.5.
|
|
9
|
-
export declare const TSA_URL = "https://github.com/espritoxyz/tsa/releases/download/v0.5.
|
|
7
|
+
export declare const TSA_VERSION = "v0.5.4";
|
|
8
|
+
export declare const TSA_NAME = "tsa-cli-v0.5.4.jar";
|
|
9
|
+
export declare const TSA_URL = "https://github.com/espritoxyz/tsa/releases/download/v0.5.4/tsa-cli.jar";
|
|
10
10
|
export declare const DRAIN_CHECK_SYMBOLIC_FILENAME = "drain-check-symbolic.fc";
|
|
11
11
|
export declare const DRAIN_CHECK_CONCRETE_FILENAME = "drain-check-concrete.fc";
|
|
12
12
|
export declare const DRAIN_CHECK_ID = "drain-check";
|
package/dist/common/constants.js
CHANGED
|
@@ -8,7 +8,7 @@ Sym.OK = "✅";
|
|
|
8
8
|
Sym.WARN = "⚠️";
|
|
9
9
|
Sym.ERR = "❌";
|
|
10
10
|
Sym.WAIT = "⏳";
|
|
11
|
-
exports.TSA_VERSION = "v0.5.
|
|
11
|
+
exports.TSA_VERSION = "v0.5.4";
|
|
12
12
|
exports.TSA_NAME = `tsa-cli-${exports.TSA_VERSION}.jar`;
|
|
13
13
|
exports.TSA_URL = `https://github.com/espritoxyz/tsa/releases/download/${exports.TSA_VERSION}/tsa-cli.jar`;
|
|
14
14
|
exports.DRAIN_CHECK_SYMBOLIC_FILENAME = "drain-check-symbolic.fc";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const findExploitExecutionIndex: (sarifPath: string) => number | undefined;
|
|
2
2
|
export declare const findNonFailingExecution: (sarifPath: string) => number | undefined;
|
|
3
|
+
export declare const isSingleNonFailingResult: (sarifPath: string) => boolean;
|
|
3
4
|
export declare const isSarifResultsEmpty: (sarifPath: string) => boolean;
|
|
4
5
|
export declare const getMessageValue: (sarifPath: string, index: number) => bigint | null;
|
|
5
6
|
export declare const getInitialBalance: (sarifPath: string, index: number) => bigint;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInitialBalance = exports.getMessageValue = exports.isSarifResultsEmpty = exports.findNonFailingExecution = exports.findExploitExecutionIndex = void 0;
|
|
3
|
+
exports.getInitialBalance = exports.getMessageValue = exports.isSarifResultsEmpty = exports.isSingleNonFailingResult = exports.findNonFailingExecution = exports.findExploitExecutionIndex = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const constants_js_1 = require("./constants.js");
|
|
6
6
|
const getSarifResults = (sarifPath) => {
|
|
@@ -24,6 +24,12 @@ const findNonFailingExecution = (sarifPath) => {
|
|
|
24
24
|
return findExecutionByMessage(sarifPath, constants_js_1.EXPECTED_MESSAGE_NON_FAILING);
|
|
25
25
|
};
|
|
26
26
|
exports.findNonFailingExecution = findNonFailingExecution;
|
|
27
|
+
const isSingleNonFailingResult = (sarifPath) => {
|
|
28
|
+
const results = getSarifResults(sarifPath);
|
|
29
|
+
return (results.length === 1 &&
|
|
30
|
+
results[0]?.message?.text === constants_js_1.EXPECTED_MESSAGE_NON_FAILING);
|
|
31
|
+
};
|
|
32
|
+
exports.isSingleNonFailingResult = isSingleNonFailingResult;
|
|
27
33
|
const isSarifResultsEmpty = (sarifPath) => {
|
|
28
34
|
return getSarifResults(sarifPath).length === 0;
|
|
29
35
|
};
|