blueprint-tsa 1.0.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/README.md +0 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +54 -0
- package/dist/commands/audit.d.ts +19 -0
- package/dist/commands/audit.js +296 -0
- package/dist/commands/bounce-check.d.ts +15 -0
- package/dist/commands/bounce-check.js +152 -0
- package/dist/commands/clean.d.ts +1 -0
- package/dist/commands/clean.js +20 -0
- package/dist/commands/drain-check.d.ts +32 -0
- package/dist/commands/drain-check.js +218 -0
- package/dist/commands/opcode-info.d.ts +23 -0
- package/dist/commands/opcode-info.js +176 -0
- package/dist/commands/owner-hijack-check.d.ts +20 -0
- package/dist/commands/owner-hijack-check.js +290 -0
- package/dist/commands/replay-attack-check.d.ts +20 -0
- package/dist/commands/replay-attack-check.js +149 -0
- package/dist/commands/reproduce.d.ts +3 -0
- package/dist/commands/reproduce.js +102 -0
- package/dist/common/analyzer-wrapper.d.ts +69 -0
- package/dist/common/analyzer-wrapper.js +198 -0
- package/dist/common/analyzer.d.ts +10 -0
- package/dist/common/analyzer.js +49 -0
- package/dist/common/build-utils.d.ts +3 -0
- package/dist/common/build-utils.js +68 -0
- package/dist/common/constants.d.ts +41 -0
- package/dist/common/constants.js +45 -0
- package/dist/common/draw.d.ts +7 -0
- package/dist/common/draw.js +33 -0
- package/dist/common/file-utils.d.ts +7 -0
- package/dist/common/file-utils.js +20 -0
- package/dist/common/format-utils.d.ts +13 -0
- package/dist/common/format-utils.js +30 -0
- package/dist/common/opcode-extractor.d.ts +7 -0
- package/dist/common/opcode-extractor.js +60 -0
- package/dist/common/paths.d.ts +19 -0
- package/dist/common/paths.js +139 -0
- package/dist/common/result-parsing.d.ts +4 -0
- package/dist/common/result-parsing.js +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +16 -0
- package/dist/install/architecture.d.ts +5 -0
- package/dist/install/architecture.js +51 -0
- package/dist/install/downloading.d.ts +1 -0
- package/dist/install/downloading.js +44 -0
- package/dist/install/java.d.ts +1 -0
- package/dist/install/java.js +89 -0
- package/dist/install/postinstall.d.ts +1 -0
- package/dist/install/postinstall.js +12 -0
- package/dist/install/tsa-jar.d.ts +1 -0
- package/dist/install/tsa-jar.js +23 -0
- package/dist/install/unzip.d.ts +1 -0
- package/dist/install/unzip.js +14 -0
- package/dist/reproduce/build-config.d.ts +3 -0
- package/dist/reproduce/build-config.js +24 -0
- package/dist/reproduce/concrete-analysis.d.ts +15 -0
- package/dist/reproduce/concrete-analysis.js +21 -0
- package/dist/reproduce/network.d.ts +19 -0
- package/dist/reproduce/network.js +70 -0
- package/dist/reproduce/reproduce-config.d.ts +30 -0
- package/dist/reproduce/reproduce-config.js +59 -0
- package/dist/reproduce/utils.d.ts +4 -0
- package/dist/reproduce/utils.js +34 -0
- package/dist/tsa.d.ts +2 -0
- package/dist/tsa.js +22 -0
- package/package.json +45 -0
package/README.md
ADDED
|
File without changes
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import yargs from "yargs";
|
|
2
|
+
import { Args, UIProvider } from "@ton/blueprint";
|
|
3
|
+
export interface CommandContext {
|
|
4
|
+
ui: UIProvider;
|
|
5
|
+
args: Args;
|
|
6
|
+
}
|
|
7
|
+
export interface CommandHandler {
|
|
8
|
+
(context: CommandContext, parsedArgs: yargs.ArgumentsCamelCase): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Main CLI router - parses subcommands and delegates to handlers
|
|
12
|
+
*/
|
|
13
|
+
export declare const createCLI: (context: CommandContext) => yargs.Argv<{}>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
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.createCLI = void 0;
|
|
7
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
8
|
+
const drain_check_js_1 = require("./commands/drain-check.js");
|
|
9
|
+
const clean_js_1 = require("./commands/clean.js");
|
|
10
|
+
const owner_hijack_check_js_1 = require("./commands/owner-hijack-check.js");
|
|
11
|
+
const reproduce_js_1 = require("./commands/reproduce.js");
|
|
12
|
+
const replay_attack_check_js_1 = require("./commands/replay-attack-check.js");
|
|
13
|
+
const opcode_info_js_1 = require("./commands/opcode-info.js");
|
|
14
|
+
const audit_js_1 = require("./commands/audit.js");
|
|
15
|
+
const bounce_check_js_1 = require("./commands/bounce-check.js");
|
|
16
|
+
/**
|
|
17
|
+
* Main CLI router - parses subcommands and delegates to handlers
|
|
18
|
+
*/
|
|
19
|
+
const createCLI = (context) => {
|
|
20
|
+
const { args, ui } = context;
|
|
21
|
+
const argv = args._.slice(1);
|
|
22
|
+
const drainCheckConfig = (0, drain_check_js_1.configureDrainCheckCommand)(context);
|
|
23
|
+
const replayAttackCheckConfig = (0, replay_attack_check_js_1.configureReplayAttackCheckCommand)(context);
|
|
24
|
+
const cleanConfig = (0, clean_js_1.configureCleanCommand)();
|
|
25
|
+
const reproduceCommand = (0, reproduce_js_1.configureReproduceCommand)(context);
|
|
26
|
+
const ownerHijackConfig = (0, owner_hijack_check_js_1.configureOwnerHijackCommand)(context);
|
|
27
|
+
const opcodeInfoConfig = (0, opcode_info_js_1.configureOpcodeInfoCommand)(context);
|
|
28
|
+
const auditConfig = (0, audit_js_1.configureAuditCommand)(context);
|
|
29
|
+
const bounceCheckConfig = (0, bounce_check_js_1.configureBounceCheckCommand)(context);
|
|
30
|
+
return (0, yargs_1.default)(argv)
|
|
31
|
+
.scriptName("tsa")
|
|
32
|
+
.command(drainCheckConfig.command, drainCheckConfig.description, drainCheckConfig.builder, drainCheckConfig.handler)
|
|
33
|
+
.command(replayAttackCheckConfig.command, replayAttackCheckConfig.description, replayAttackCheckConfig.builder, replayAttackCheckConfig.handler)
|
|
34
|
+
.command(cleanConfig.command, cleanConfig.description, cleanConfig.builder, cleanConfig.handler)
|
|
35
|
+
.command(ownerHijackConfig.command, ownerHijackConfig.description, ownerHijackConfig.builder, ownerHijackConfig.handler)
|
|
36
|
+
.command(reproduceCommand.command, reproduceCommand.description, reproduceCommand.builder, reproduceCommand.handler)
|
|
37
|
+
.command(opcodeInfoConfig.command, opcodeInfoConfig.description, opcodeInfoConfig.builder, opcodeInfoConfig.handler)
|
|
38
|
+
.command(auditConfig.command, auditConfig.description, auditConfig.builder, auditConfig.handler)
|
|
39
|
+
.command(bounceCheckConfig.command, bounceCheckConfig.description, bounceCheckConfig.builder, bounceCheckConfig.handler)
|
|
40
|
+
.demandCommand(1, "Please specify a subcommand")
|
|
41
|
+
.help()
|
|
42
|
+
.alias("help", "h")
|
|
43
|
+
.strict()
|
|
44
|
+
.fail(async (msg, err, yargs) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
ui.write(`\nError: ${msg}`);
|
|
49
|
+
ui.write("");
|
|
50
|
+
yargs.showHelp((s) => ui.write(s));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
exports.createCLI = createCLI;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Argv } from "yargs";
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import { CommandContext } from "../cli.js";
|
|
4
|
+
export declare const configureAuditCommand: (context: CommandContext) => {
|
|
5
|
+
command: string;
|
|
6
|
+
description: string;
|
|
7
|
+
builder: (yargs: Argv) => Argv<{
|
|
8
|
+
contract: string;
|
|
9
|
+
} & {
|
|
10
|
+
timeout: number | undefined;
|
|
11
|
+
} & {
|
|
12
|
+
"owner-method": string | undefined;
|
|
13
|
+
} & {
|
|
14
|
+
"disable-opcode-extraction": boolean | undefined;
|
|
15
|
+
} & {
|
|
16
|
+
verbose: boolean | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
handler: (argv: yargs.ArgumentsCamelCase) => Promise<void>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,296 @@
|
|
|
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.configureAuditCommand = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const constants_js_1 = require("../common/constants.js");
|
|
10
|
+
const build_utils_js_1 = require("../common/build-utils.js");
|
|
11
|
+
const paths_js_1 = require("../common/paths.js");
|
|
12
|
+
const format_utils_js_1 = require("../common/format-utils.js");
|
|
13
|
+
const opcode_extractor_js_1 = require("../common/opcode-extractor.js");
|
|
14
|
+
const core_1 = require("@ton/core");
|
|
15
|
+
const utils_js_1 = require("../reproduce/utils.js");
|
|
16
|
+
const drain_check_js_1 = require("./drain-check.js");
|
|
17
|
+
const replay_attack_check_js_1 = require("./replay-attack-check.js");
|
|
18
|
+
const owner_hijack_check_js_1 = require("./owner-hijack-check.js");
|
|
19
|
+
const bounce_check_js_1 = require("./bounce-check.js");
|
|
20
|
+
const opcode_info_js_1 = require("./opcode-info.js");
|
|
21
|
+
const ONE_MINUTE_SECONDS = 60;
|
|
22
|
+
function getCheckCommand(checkName, contractName, timeout, ownerMethod) {
|
|
23
|
+
let commandId;
|
|
24
|
+
if (checkName === constants_js_1.DRAIN_CHECK_NAME) {
|
|
25
|
+
commandId = constants_js_1.DRAIN_CHECK_ID;
|
|
26
|
+
}
|
|
27
|
+
else if (checkName === constants_js_1.REPLAY_ATTACK_CHECK_NAME) {
|
|
28
|
+
commandId = constants_js_1.REPLAY_ATTACK_CHECK_ID;
|
|
29
|
+
}
|
|
30
|
+
else if (checkName === constants_js_1.OWNER_HIJACK_CHECK_NAME) {
|
|
31
|
+
commandId = constants_js_1.OWNER_HIJACK_CHECK_ID;
|
|
32
|
+
}
|
|
33
|
+
else if (checkName === constants_js_1.BOUNCE_CHECK_NAME) {
|
|
34
|
+
commandId = constants_js_1.BOUNCE_CHECK_ID;
|
|
35
|
+
}
|
|
36
|
+
if (!commandId) {
|
|
37
|
+
throw new Error(`Unknown check: ${checkName}`);
|
|
38
|
+
}
|
|
39
|
+
let command = `yarn blueprint tsa ${commandId} -c ${contractName}`;
|
|
40
|
+
if (timeout !== null) {
|
|
41
|
+
command += ` -t ${timeout}`;
|
|
42
|
+
}
|
|
43
|
+
if (ownerMethod !== undefined && checkName === constants_js_1.OWNER_HIJACK_CHECK_NAME) {
|
|
44
|
+
command += ` -m ${ownerMethod}`;
|
|
45
|
+
}
|
|
46
|
+
return command;
|
|
47
|
+
}
|
|
48
|
+
function getCheckDescriptionUrl(checkName) {
|
|
49
|
+
if (checkName === constants_js_1.DRAIN_CHECK_NAME) {
|
|
50
|
+
return constants_js_1.DRAIN_DESCRIPTION_URL;
|
|
51
|
+
}
|
|
52
|
+
else if (checkName === constants_js_1.REPLAY_ATTACK_CHECK_NAME) {
|
|
53
|
+
return constants_js_1.REPLAY_DESCRIPTION_URL;
|
|
54
|
+
}
|
|
55
|
+
else if (checkName === constants_js_1.OWNER_HIJACK_CHECK_NAME) {
|
|
56
|
+
return constants_js_1.OWNER_HIJACK_DESCRIPTION_URL;
|
|
57
|
+
}
|
|
58
|
+
else if (checkName === constants_js_1.BOUNCE_CHECK_NAME) {
|
|
59
|
+
return constants_js_1.BOUNCE_DESCRIPTION_URL;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function buildCheckResult(checkName, analyzer, passedMessage, failedMessage, contractName, timeout, ownerMethod) {
|
|
64
|
+
const vulnerability = analyzer.vulnerabilityIsPresent();
|
|
65
|
+
const result = {
|
|
66
|
+
name: checkName,
|
|
67
|
+
passed: !vulnerability,
|
|
68
|
+
message: vulnerability ? failedMessage : passedMessage,
|
|
69
|
+
checkCommand: getCheckCommand(checkName, contractName, timeout, ownerMethod),
|
|
70
|
+
};
|
|
71
|
+
if (vulnerability) {
|
|
72
|
+
const vulnDesc = analyzer.getVulnerability();
|
|
73
|
+
if (vulnDesc) {
|
|
74
|
+
result.vulnerabilityPath = (0, paths_js_1.getInputsPath)(analyzer.id, vulnDesc.executionIndex);
|
|
75
|
+
result.analyzerId = analyzer.id;
|
|
76
|
+
}
|
|
77
|
+
result.descriptionUrl = getCheckDescriptionUrl(checkName);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcodes, verbose) {
|
|
82
|
+
// Calculate timeout per opcode
|
|
83
|
+
let opcodeTimeout = null;
|
|
84
|
+
if (timeout !== null && opcodes.length > 0) {
|
|
85
|
+
opcodeTimeout = Math.floor(timeout / opcodes.length);
|
|
86
|
+
ui.write("");
|
|
87
|
+
ui.write(`Opcode authorization check: using timeout of ${opcodeTimeout} seconds per opcode (${timeout} / ${opcodes.length})`);
|
|
88
|
+
}
|
|
89
|
+
const results = [];
|
|
90
|
+
for (const opcode of opcodes) {
|
|
91
|
+
const info = await (0, opcode_info_js_1.runOpcodeAuthorizationCheckAnalysis)(opcode, contractName, contractPath, ui, opcodeTimeout, `Authorization check for ${(0, format_utils_js_1.formatOpcodeHex)(opcode)} completed.`, verbose);
|
|
92
|
+
if (info !== null) {
|
|
93
|
+
results.push(info);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
async function runDrainCheck(contractName, contractPath, ui, timeout, opcodes, verbose) {
|
|
99
|
+
const analyzer = await (0, drain_check_js_1.runDrainCheckAnalysis)(contractName, contractPath, ui, timeout, opcodes, verbose, `${constants_js_1.DRAIN_CHECK_NAME} completed.`);
|
|
100
|
+
return buildCheckResult(constants_js_1.DRAIN_CHECK_NAME, analyzer, "No drain vulnerabilities detected", "Vulnerability found - contract may be vulnerable to drain attacks", contractName, timeout);
|
|
101
|
+
}
|
|
102
|
+
async function runReplayAttackCheck(contractName, contractPath, ui, timeout, verbose) {
|
|
103
|
+
const analyzer = await (0, replay_attack_check_js_1.runReplayAttackCheckAnalysis)(contractName, contractPath, ui, timeout, verbose, `${constants_js_1.REPLAY_ATTACK_CHECK_NAME} completed.`);
|
|
104
|
+
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);
|
|
105
|
+
}
|
|
106
|
+
async function runOwnerHijackCheck(contractName, contractPath, ui, timeout, methodName, opcodes, verbose) {
|
|
107
|
+
const methodId = BigInt((0, core_1.getMethodId)(methodName));
|
|
108
|
+
const analyzer = await (0, owner_hijack_check_js_1.runOwnerHijackCheckAnalysis)(contractName, contractPath, ui, timeout, methodId, opcodes, verbose, `${constants_js_1.OWNER_HIJACK_CHECK_NAME} completed.`);
|
|
109
|
+
return buildCheckResult(constants_js_1.OWNER_HIJACK_CHECK_NAME, analyzer, "No owner hijack vulnerabilities detected", "Vulnerability found - contract owner may be hijackable", contractName, timeout, methodName);
|
|
110
|
+
}
|
|
111
|
+
async function runBounceCheck(contractName, contractPath, ui, timeout, opcodes, verbose) {
|
|
112
|
+
const analyzer = await (0, bounce_check_js_1.runBounceCheckAnalysis)(contractName, contractPath, ui, timeout, opcodes, verbose, `${constants_js_1.BOUNCE_CHECK_NAME} completed.`);
|
|
113
|
+
return buildCheckResult(constants_js_1.BOUNCE_CHECK_NAME, analyzer, "No bounce message handling vulnerabilities detected", "Vulnerability found - contract may not handle bounced messages correctly", contractName, timeout);
|
|
114
|
+
}
|
|
115
|
+
function buildAuditReport(summary) {
|
|
116
|
+
const lines = [];
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push("═".repeat(60));
|
|
119
|
+
lines.push(` AUDIT SUMMARY: ${summary.contract}`);
|
|
120
|
+
lines.push("═".repeat(60));
|
|
121
|
+
lines.push("");
|
|
122
|
+
// Add opcode information
|
|
123
|
+
if (summary.opcodeInfo.length > 0) {
|
|
124
|
+
const opcodeInfoFormatted = (0, opcode_info_js_1.formatOpcodeInfo)(summary.opcodeInfo);
|
|
125
|
+
lines.push(opcodeInfoFormatted);
|
|
126
|
+
}
|
|
127
|
+
// Add check results
|
|
128
|
+
lines.push("Security Checks:");
|
|
129
|
+
lines.push("");
|
|
130
|
+
let allPassed = true;
|
|
131
|
+
for (const check of summary.checks) {
|
|
132
|
+
const status = check.passed ? constants_js_1.Sym.OK : constants_js_1.Sym.ERR;
|
|
133
|
+
lines.push(` ${status} ${check.name}: ${check.message}`);
|
|
134
|
+
if (check.vulnerabilityPath) {
|
|
135
|
+
const relativePath = path_1.default.relative(process.cwd(), check.vulnerabilityPath);
|
|
136
|
+
lines.push(` Path to reproducing input: ${relativePath}`);
|
|
137
|
+
}
|
|
138
|
+
if (check.descriptionUrl) {
|
|
139
|
+
lines.push(` Description: ${check.descriptionUrl}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push("");
|
|
142
|
+
if (!check.passed) {
|
|
143
|
+
allPassed = false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
lines.push("─".repeat(60));
|
|
147
|
+
if (allPassed) {
|
|
148
|
+
lines.push(` ${constants_js_1.Sym.OK} All checks passed!`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
lines.push(` ${constants_js_1.Sym.WARN} Some checks failed - review the results above`);
|
|
152
|
+
}
|
|
153
|
+
lines.push("─".repeat(60));
|
|
154
|
+
lines.push("");
|
|
155
|
+
let report = lines.join("\n");
|
|
156
|
+
// Add vulnerability instructions for checks with vulnerabilities
|
|
157
|
+
const vulnerabilityInstructions = [];
|
|
158
|
+
for (const check of summary.checks) {
|
|
159
|
+
if (check.analyzerId) {
|
|
160
|
+
const instructions = (0, utils_js_1.getReproductionInstructions)(check.analyzerId);
|
|
161
|
+
vulnerabilityInstructions.push("─".repeat(60));
|
|
162
|
+
vulnerabilityInstructions.push(` ${check.name}`);
|
|
163
|
+
vulnerabilityInstructions.push("─".repeat(60));
|
|
164
|
+
vulnerabilityInstructions.push("To run only this check, use:");
|
|
165
|
+
vulnerabilityInstructions.push(`> ${check.checkCommand}`);
|
|
166
|
+
vulnerabilityInstructions.push("");
|
|
167
|
+
if (instructions) {
|
|
168
|
+
vulnerabilityInstructions.push(instructions);
|
|
169
|
+
vulnerabilityInstructions.push("");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
report += "\n";
|
|
174
|
+
if (vulnerabilityInstructions.length > 0) {
|
|
175
|
+
report += vulnerabilityInstructions.join("\n");
|
|
176
|
+
}
|
|
177
|
+
return report;
|
|
178
|
+
}
|
|
179
|
+
function printAuditSummary(summary, ui) {
|
|
180
|
+
const report = buildAuditReport(summary);
|
|
181
|
+
ui.write(report);
|
|
182
|
+
}
|
|
183
|
+
function saveAuditReport(summary, ui) {
|
|
184
|
+
const report = buildAuditReport(summary);
|
|
185
|
+
const reportsDir = (0, paths_js_1.findTSAReportsDirectory)();
|
|
186
|
+
const reportId = (0, format_utils_js_1.generateReportId)();
|
|
187
|
+
const fileName = `audit-${summary.contract}-${reportId}.txt`;
|
|
188
|
+
const filePath = path_1.default.join(reportsDir, fileName);
|
|
189
|
+
(0, fs_1.writeFileSync)(filePath, report);
|
|
190
|
+
ui.write("");
|
|
191
|
+
ui.write(`${constants_js_1.Sym.OK} Report saved to: ${filePath}`);
|
|
192
|
+
return filePath;
|
|
193
|
+
}
|
|
194
|
+
const auditHandler = async (context, args) => {
|
|
195
|
+
const { ui } = context;
|
|
196
|
+
const { timeout, contract, ownerMethod, disableOpcodeExtraction, verbose } = args;
|
|
197
|
+
await (0, build_utils_js_1.buildContracts)(ui);
|
|
198
|
+
const contractPath = (0, paths_js_1.findCompiledContract)(contract);
|
|
199
|
+
if (!(0, fs_1.existsSync)(contractPath)) {
|
|
200
|
+
ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contract} not found`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
// Extract opcodes if not disabled
|
|
204
|
+
let opcodes = [];
|
|
205
|
+
if (!disableOpcodeExtraction) {
|
|
206
|
+
opcodes = await (0, opcode_extractor_js_1.extractOpcodes)({
|
|
207
|
+
ui,
|
|
208
|
+
codePath: contractPath,
|
|
209
|
+
contractName: contract,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// Calculate timeout if not provided
|
|
213
|
+
// Timeout is per analyzer run (except for opcode-info where it's divided by number of opcodes)
|
|
214
|
+
let effectiveTimeout = timeout ?? null;
|
|
215
|
+
if (effectiveTimeout === null && opcodes.length > 0) {
|
|
216
|
+
effectiveTimeout = ONE_MINUTE_SECONDS * (opcodes.length + 1);
|
|
217
|
+
ui.write("");
|
|
218
|
+
ui.write(`Timeout was calculated automatically: ${effectiveTimeout} seconds per analyzer run (based on ${opcodes.length} opcodes)`);
|
|
219
|
+
}
|
|
220
|
+
const summary = {
|
|
221
|
+
contract: contract,
|
|
222
|
+
checks: [],
|
|
223
|
+
opcodeInfo: [],
|
|
224
|
+
};
|
|
225
|
+
// Run opcode-info check
|
|
226
|
+
ui.write("");
|
|
227
|
+
ui.write(`${constants_js_1.Sym.WAIT} Running opcode authorization analysis...`);
|
|
228
|
+
summary.opcodeInfo = await runOpcodeInfoCheck(contract, contractPath, ui, effectiveTimeout, opcodes, verbose);
|
|
229
|
+
// Run drain-check
|
|
230
|
+
ui.write("");
|
|
231
|
+
ui.write(`${constants_js_1.Sym.WAIT} Running drain check...`);
|
|
232
|
+
const drainResult = await runDrainCheck(contract, contractPath, ui, effectiveTimeout, opcodes, verbose);
|
|
233
|
+
summary.checks.push(drainResult);
|
|
234
|
+
// Run replay-attack-check
|
|
235
|
+
ui.write("");
|
|
236
|
+
ui.write(`${constants_js_1.Sym.WAIT} Running replay attack check...`);
|
|
237
|
+
const replayResult = await runReplayAttackCheck(contract, contractPath, ui, effectiveTimeout, verbose);
|
|
238
|
+
summary.checks.push(replayResult);
|
|
239
|
+
// Run bounce-check
|
|
240
|
+
ui.write("");
|
|
241
|
+
ui.write(`${constants_js_1.Sym.WAIT} Running bounce check...`);
|
|
242
|
+
const bounceResult = await runBounceCheck(contract, contractPath, ui, effectiveTimeout, opcodes, verbose);
|
|
243
|
+
summary.checks.push(bounceResult);
|
|
244
|
+
// Run owner-hijack-check if owner method is provided
|
|
245
|
+
if (ownerMethod) {
|
|
246
|
+
ui.write("");
|
|
247
|
+
ui.write(`${constants_js_1.Sym.WAIT} Running owner hijack check...`);
|
|
248
|
+
const ownerResult = await runOwnerHijackCheck(contract, contractPath, ui, effectiveTimeout, ownerMethod, opcodes, verbose);
|
|
249
|
+
summary.checks.push(ownerResult);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
ui.write("");
|
|
253
|
+
ui.write(`${constants_js_1.Sym.WARN} Owner-method was not specified - owner hijack check is skipped`);
|
|
254
|
+
}
|
|
255
|
+
// Print summary
|
|
256
|
+
printAuditSummary(summary, ui);
|
|
257
|
+
// Save report to file
|
|
258
|
+
saveAuditReport(summary, ui);
|
|
259
|
+
(0, utils_js_1.printCleanupInstructions)(ui);
|
|
260
|
+
};
|
|
261
|
+
const configureAuditCommand = (context) => {
|
|
262
|
+
return {
|
|
263
|
+
command: constants_js_1.AUDIT_ID,
|
|
264
|
+
description: "Run all available security checks and print a summary",
|
|
265
|
+
builder: (yargs) => yargs
|
|
266
|
+
.option("contract", {
|
|
267
|
+
alias: "c",
|
|
268
|
+
type: "string",
|
|
269
|
+
description: "Contract name",
|
|
270
|
+
demandOption: true,
|
|
271
|
+
})
|
|
272
|
+
.option("timeout", {
|
|
273
|
+
alias: "t",
|
|
274
|
+
type: "number",
|
|
275
|
+
description: "Analysis timeout in seconds per analyzer run (for opcode-info, divided by number of opcodes)",
|
|
276
|
+
})
|
|
277
|
+
.option("owner-method", {
|
|
278
|
+
alias: "m",
|
|
279
|
+
type: "string",
|
|
280
|
+
description: "The method name of get_owner getter (optional, enables owner hijack check)",
|
|
281
|
+
})
|
|
282
|
+
.option("disable-opcode-extraction", {
|
|
283
|
+
type: "boolean",
|
|
284
|
+
description: "Disable opcode extraction. This affects path selection strategy and default timeout.",
|
|
285
|
+
})
|
|
286
|
+
.option("verbose", {
|
|
287
|
+
alias: "v",
|
|
288
|
+
type: "boolean",
|
|
289
|
+
description: "Use debug output in TSA log",
|
|
290
|
+
}),
|
|
291
|
+
handler: async (argv) => {
|
|
292
|
+
await auditHandler(context, argv);
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
exports.configureAuditCommand = configureAuditCommand;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { UIProvider } from "@ton/blueprint";
|
|
2
|
+
import { CommandContext } from "../cli.js";
|
|
3
|
+
import { AnalyzerWrapper } from "../common/analyzer-wrapper.js";
|
|
4
|
+
export declare const configureBounceCheckCommand: (context: CommandContext) => any;
|
|
5
|
+
/**
|
|
6
|
+
* Runs bounce check analysis and returns the analyzer wrapper
|
|
7
|
+
* @param contractName - Name of the contract
|
|
8
|
+
* @param contractPath - Path to the compiled contract
|
|
9
|
+
* @param ui - UI provider
|
|
10
|
+
* @param timeout - Analysis timeout in seconds
|
|
11
|
+
* @param opcodes - List of opcodes to analyze
|
|
12
|
+
* @param verbose - Enable verbose output
|
|
13
|
+
* @returns AnalyzerWrapper instance
|
|
14
|
+
*/
|
|
15
|
+
export declare const runBounceCheckAnalysis: (contractName: string, contractPath: string, ui: UIProvider, timeout: number | null, opcodes: number[], verbose?: boolean, completionMessage?: string) => Promise<AnalyzerWrapper>;
|
|
@@ -0,0 +1,152 @@
|
|
|
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.runBounceCheckAnalysis = exports.configureBounceCheckCommand = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const core_1 = require("@ton/core");
|
|
9
|
+
const analyzer_wrapper_js_1 = require("../common/analyzer-wrapper.js");
|
|
10
|
+
const constants_js_1 = require("../common/constants.js");
|
|
11
|
+
const build_utils_js_1 = require("../common/build-utils.js");
|
|
12
|
+
const file_utils_js_1 = require("../common/file-utils.js");
|
|
13
|
+
const utils_js_1 = require("../reproduce/utils.js");
|
|
14
|
+
const paths_js_1 = require("../common/paths.js");
|
|
15
|
+
const opcode_extractor_js_1 = require("../common/opcode-extractor.js");
|
|
16
|
+
const os_1 = require("os");
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const ONE_MINUTE_SECONDS = 60;
|
|
19
|
+
const configureBounceCheckCommand = (context) => {
|
|
20
|
+
return {
|
|
21
|
+
command: constants_js_1.BOUNCE_CHECK_ID,
|
|
22
|
+
description: "Check if contract processes bounced messages correctly",
|
|
23
|
+
builder: (yargs) => yargs
|
|
24
|
+
.option("timeout", {
|
|
25
|
+
alias: "t",
|
|
26
|
+
type: "number",
|
|
27
|
+
description: "Analysis timeout in seconds",
|
|
28
|
+
})
|
|
29
|
+
.option("contract", {
|
|
30
|
+
alias: "c",
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Contract name",
|
|
33
|
+
demandOption: true,
|
|
34
|
+
})
|
|
35
|
+
.option("verbose", {
|
|
36
|
+
alias: "v",
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Use debug output in TSA log",
|
|
39
|
+
})
|
|
40
|
+
.option("disable-opcode-extraction", {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "Disable opcode extraction. This affects path selection strategy and default timeout.",
|
|
43
|
+
}),
|
|
44
|
+
handler: async (argv) => {
|
|
45
|
+
await bounceCheckCommand(context, argv);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
exports.configureBounceCheckCommand = configureBounceCheckCommand;
|
|
50
|
+
/**
|
|
51
|
+
* Runs bounce check analysis and returns the analyzer wrapper
|
|
52
|
+
* @param contractName - Name of the contract
|
|
53
|
+
* @param contractPath - Path to the compiled contract
|
|
54
|
+
* @param ui - UI provider
|
|
55
|
+
* @param timeout - Analysis timeout in seconds
|
|
56
|
+
* @param opcodes - List of opcodes to analyze
|
|
57
|
+
* @param verbose - Enable verbose output
|
|
58
|
+
* @returns AnalyzerWrapper instance
|
|
59
|
+
*/
|
|
60
|
+
const runBounceCheckAnalysis = async (contractName, contractPath, ui, timeout, opcodes, verbose = false, completionMessage = "Analysis complete.") => {
|
|
61
|
+
const checkerPath = (0, paths_js_1.getCheckerPath)(constants_js_1.BOUNCE_CHECK_FILENAME);
|
|
62
|
+
const schemePath = (0, paths_js_1.getCheckerPath)(constants_js_1.BOUNCE_CHECK_SCHEME_FILENAME);
|
|
63
|
+
const throwerFuncPath = (0, paths_js_1.getThrowerPath)();
|
|
64
|
+
// Compile thrower FunC to BoC
|
|
65
|
+
const throwerBocBase64 = await (0, build_utils_js_1.compileFuncFileToBase64Boc)(throwerFuncPath, constants_js_1.THROWER_FILENAME);
|
|
66
|
+
// Write compiled BoC to temporary file and run analysis
|
|
67
|
+
const tempThrowerBocPath = path_1.default.join((0, os_1.tmpdir)(), `thrower-${Date.now()}.boc`);
|
|
68
|
+
return (0, file_utils_js_1.doWithTemporaryFile)(async (tempPath) => {
|
|
69
|
+
(0, fs_1.writeFileSync)(tempPath, Buffer.from(throwerBocBase64, "base64"));
|
|
70
|
+
const properties = [
|
|
71
|
+
{ key: "Contract", value: contractName },
|
|
72
|
+
{ key: "Mode", value: constants_js_1.BOUNCE_CHECK_NAME },
|
|
73
|
+
{
|
|
74
|
+
key: "Options",
|
|
75
|
+
separator: true,
|
|
76
|
+
children: [
|
|
77
|
+
{
|
|
78
|
+
key: "Timeout",
|
|
79
|
+
value: timeout !== null ? `${timeout} seconds` : "not set",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const checkerCell = (0, core_1.beginCell)().endCell();
|
|
85
|
+
const analyzer = new analyzer_wrapper_js_1.AnalyzerWrapper({
|
|
86
|
+
ui,
|
|
87
|
+
checkerPath,
|
|
88
|
+
checkerCell,
|
|
89
|
+
properties,
|
|
90
|
+
codePath: contractPath,
|
|
91
|
+
});
|
|
92
|
+
const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
|
|
93
|
+
const sarifPath = (0, paths_js_1.getSarifReportPath)(analyzer.id);
|
|
94
|
+
await analyzer.run(constants_js_1.BOUNCE_CHECK_FILENAME, (wrapper) => [
|
|
95
|
+
"custom-checker-compiled",
|
|
96
|
+
"--checker",
|
|
97
|
+
wrapper.getTempBocPath(),
|
|
98
|
+
"--contract",
|
|
99
|
+
contractPath,
|
|
100
|
+
"--contract",
|
|
101
|
+
tempPath,
|
|
102
|
+
"--stop-when-exit-codes-found",
|
|
103
|
+
constants_js_1.ERROR_EXIT_CODE.toString(),
|
|
104
|
+
"--checker-data",
|
|
105
|
+
wrapper.getTempCheckerCellPath(),
|
|
106
|
+
"--output",
|
|
107
|
+
sarifPath,
|
|
108
|
+
...(timeout != null ? ["--timeout", timeout.toString()] : []),
|
|
109
|
+
"--exported-inputs",
|
|
110
|
+
reportDir,
|
|
111
|
+
...(verbose ? ["-v"] : []),
|
|
112
|
+
"--scheme",
|
|
113
|
+
schemePath,
|
|
114
|
+
"--continue-on-contract-exception",
|
|
115
|
+
...opcodes.flatMap((opcode) => ["--opcode", opcode.toString()]),
|
|
116
|
+
], completionMessage);
|
|
117
|
+
return analyzer;
|
|
118
|
+
}, tempThrowerBocPath);
|
|
119
|
+
};
|
|
120
|
+
exports.runBounceCheckAnalysis = runBounceCheckAnalysis;
|
|
121
|
+
const bounceCheckCommand = async (context, parsedArgs) => {
|
|
122
|
+
const { ui } = context;
|
|
123
|
+
await (0, build_utils_js_1.buildContracts)(ui);
|
|
124
|
+
if (!parsedArgs.contract) {
|
|
125
|
+
throw new Error("Contract name or path is required");
|
|
126
|
+
}
|
|
127
|
+
const contract = parsedArgs.contract;
|
|
128
|
+
const contractPath = (0, paths_js_1.findCompiledContract)(contract);
|
|
129
|
+
if (!(0, fs_1.existsSync)(contractPath)) {
|
|
130
|
+
ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contract} not found`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
let opcodes = [];
|
|
134
|
+
if (!parsedArgs["disable-opcode-extraction"]) {
|
|
135
|
+
opcodes = await (0, opcode_extractor_js_1.extractOpcodes)({
|
|
136
|
+
ui,
|
|
137
|
+
codePath: contractPath,
|
|
138
|
+
contractName: contract,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
let timeout = parsedArgs.timeout ?? null;
|
|
142
|
+
// If timeout wasn't provided, set it to 1 minute * (number_of_opcodes + 1)
|
|
143
|
+
if (timeout === null && opcodes.length > 0) {
|
|
144
|
+
timeout = ONE_MINUTE_SECONDS * (opcodes.length + 1);
|
|
145
|
+
ui.write("");
|
|
146
|
+
ui.write("The timeout was calculated automatically based on the number of opcodes.");
|
|
147
|
+
}
|
|
148
|
+
const analyzer = await (0, exports.runBounceCheckAnalysis)(contract, contractPath, ui, timeout, opcodes, parsedArgs.verbose);
|
|
149
|
+
const vulnerability = analyzer.getVulnerability();
|
|
150
|
+
analyzer.reportVulnerability(vulnerability, constants_js_1.BOUNCE_DESCRIPTION_URL);
|
|
151
|
+
(0, utils_js_1.printCleanupInstructions)(ui);
|
|
152
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const configureCleanCommand: () => any;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureCleanCommand = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const paths_js_1 = require("../common/paths.js");
|
|
6
|
+
const configureCleanCommand = () => {
|
|
7
|
+
return {
|
|
8
|
+
command: "clean",
|
|
9
|
+
description: "Clean directory with reports",
|
|
10
|
+
builder: (yargs) => yargs,
|
|
11
|
+
handler: async (_argv) => {
|
|
12
|
+
await cleanCommand();
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
exports.configureCleanCommand = configureCleanCommand;
|
|
17
|
+
const cleanCommand = async () => {
|
|
18
|
+
const reportsDir = (0, paths_js_1.findTSAReportsDirectory)();
|
|
19
|
+
(0, fs_1.rmSync)(reportsDir, { recursive: true, force: true });
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import yargs, { Argv } from "yargs";
|
|
2
|
+
import { UIProvider } from "@ton/blueprint";
|
|
3
|
+
import { CommandContext } from "../cli.js";
|
|
4
|
+
import { ReproduceParameters } from "../reproduce/network.js";
|
|
5
|
+
import { ConcreteAnalysisConfig } from "../reproduce/concrete-analysis.js";
|
|
6
|
+
import { AnalyzerWrapper } from "../common/analyzer-wrapper.js";
|
|
7
|
+
export declare const configureDrainCheckCommand: (context: CommandContext) => {
|
|
8
|
+
command: string;
|
|
9
|
+
description: string;
|
|
10
|
+
builder: (yargs: Argv) => yargs.Argv<{
|
|
11
|
+
timeout: number | undefined;
|
|
12
|
+
} & {
|
|
13
|
+
contract: string;
|
|
14
|
+
} & {
|
|
15
|
+
verbose: boolean | undefined;
|
|
16
|
+
} & {
|
|
17
|
+
"disable-opcode-extraction": boolean | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
handler: (argv: yargs.ArgumentsCamelCase) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Runs drain check analysis and returns the analyzer wrapper
|
|
23
|
+
* @param contractName - Name of the contract
|
|
24
|
+
* @param contractPath - Path to the compiled contract
|
|
25
|
+
* @param ui - UI provider
|
|
26
|
+
* @param timeout - Analysis timeout in seconds
|
|
27
|
+
* @param opcodes - List of opcodes to analyze
|
|
28
|
+
* @param verbose - Enable verbose output
|
|
29
|
+
* @returns AnalyzerWrapper instance
|
|
30
|
+
*/
|
|
31
|
+
export declare const runDrainCheckAnalysis: (contractName: string, contractPath: string, ui: UIProvider, timeout: number | null, opcodes: number[], verbose?: boolean, completionMessage?: string) => Promise<AnalyzerWrapper>;
|
|
32
|
+
export declare const drainCheckConcrete: (config: ConcreteAnalysisConfig, completionMessage?: string) => Promise<ReproduceParameters | null>;
|