npx skills add ...
npx skills add trailofbits/skills --skill ton-vulnerability-scanner
Scans TON (The Open Network) smart contracts for 3 critical vulnerabilities including integer-as-boolean misuse, fake Jetton contracts, and forward TON without gas checks. Use when auditing FunC contracts.
npx skills add trailofbits/skills --skill ton-vulnerability-scanner
Systematically scan TON blockchain smart contracts written in FunC for platform-specific security vulnerabilities related to boolean logic, Jetton token handling, and gas management. This skill encodes 3 critical vulnerability patterns unique to TON's architecture.
.fc, .funccontracts/*.fc - FunC contract sourcewrappers/*.ts - TypeScript wrapperstests/*.spec.ts - Contract testston.config.ts or wasm.config.ts - TON project configWhen invoked, I will:
When vulnerabilities are found, you'll get a report like this:
I check for 3 critical vulnerability patterns unique to TON. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.
transfer_notification sender not validatedFor complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.
.fc or .func files)For each boolean:
For each Jetton handler:
For each outgoing message:
TON contracts require thorough manual review:
~, &, | operatorsReport on every pattern in §6, whether or not it turned anything up. Emit this table above the findings, with all 3 rows present:
| # | Pattern | Verdict | Evidence |
|---|---|---|---|
| 1 | Integer as Boolean | clear | searched is_/has_/flag; all set to -1 |
| 2 | Fake Jetton Contract | ||
| 3 | Forward TON Without Gas Check |
Each verdict is one of:
found — cite file:line and write the finding up in full below.clear — the pattern applies to this contract and the contract handles it. Name the function, stored
address, or check you searched for, so a reader can repeat the search.n/a — the pattern cannot apply here. Give the reason in one clause ("this contract handles no Jetton
transfer notifications"). Not having looked is not n/a.Three patterns is a short list, which makes an incomplete table harder to excuse rather than easier: a report
covering one pattern and silent on the other two reads exactly like a clean contract. Emit all three rows even
when all three are clear. A row whose Verdict cell is empty is incomplete in the same way: row 1 above is filled in to show the shape, and every row is filled in the same way before the report is done.
building-secure-contracts/not-so-smart-contracts/ton/Before completing TON contract audit:
Boolean Logic (HIGH):
~, &, | uses correct valuesJetton Security (CRITICAL):
transfer_notification handler validates sender addressGas & Forward Amounts (HIGH):
msg_value >= tx_fee + forward_amountsend_raw_message flags usedTesting:
found, clear or n/a with a reasonn/a costs one
clause and makes the judgment reviewable. Silence records nothing, and a reader cannot tell it apart from
not having checked. With only three patterns, there is no version of this scan too large to complete.=== TON VULNERABILITY SCAN RESULTS ===
Project: my-ton-contract
Files Scanned: 3 (.fc, .tact)
Vulnerabilities Found: 2
Coverage: 3/3 patterns reported
1 Integer as Boolean .............. found contracts/wallet.fc:45
2 Fake Jetton Contract ............ found contracts/staking.fc:85
3 Forward TON Without Gas Check ... clear forward amounts fixed at 0.05 TON
---
[CRITICAL] Fake Jetton Contract - Missing Sender Validation
File: contracts/staking.fc:85
Pattern: transfer_notification sender not checked against the stored Jetton wallet# Find boolean-like variables
rg "int.*is_|int.*has_|int.*flag|int.*enabled" contracts/
# Check for positive integers used as booleans
rg "= 1;|return 1;" contracts/ | grep -E "is_|has_|flag|enabled|valid"
# Look for NOT operations on boolean-like values
rg "~.*\(|~ " contracts/# Find transfer_notification handlers
rg "transfer_notification|op::transfer_notification" contracts/# Find forward amount usage
rg "forward_ton_amount|forward_amount" contracts/
rg "load_coins\(\)" contracts/
# Find send_raw_message calls
rg "send_raw_message" contracts/import { Blockchain } from "@ton/sandbox";
import { toNano } from "ton-core";
describe("Security tests", () => {
let blockchain: Blockchain;
let contract: Contract;
beforeEach(async () => {
blockchain = await Blockchain.create();
contract = blockchain.openContract(await Contract.fromInit());
});
it("should use correct boolean values", async () => {
// Test that TRUE = -1, FALSE = 0
const result = await contract.getFlag();
expect(result).toEqual(-1n); // True
expect(result).not.toEqual(1n); // Not 1!
});
it("should reject fake jetton transfer", async () => {
const attacker = await blockchain.treasury("attacker");
const result = await contract.send(
attacker.getSender(),
{ value: toNano("0.05") },
{
$$type: "TransferNotification",
query_id: 0n,
amount: toNano("1000"),
from: attacker.address,
}
);
expect(result.transactions).toHaveTransaction({
success: false, // Should reject
});
});
it("should validate gas for forward amount", async () => {
const result = await contract.send(
user.getSender(),
{ value: toNano("0.01") }, // Insufficient gas
{
$$type: "Transfer",
to: recipient.address,
forward_ton_amount: toNano("1"), // Trying to forward 1 TON
}
);
expect(result.transactions).toHaveTransaction({
success: false,
});
});
});// Test with real Jetton wallet
it("should accept transfer from real jetton wallet", async () => {
// Deploy actual Jetton minter and wallet
const jettonMinter = await blockchain.openContract(JettonMinter.create());
const userJettonWallet = await jettonMinter.getWalletAddress(user.address);
// Set jetton wallet in contract
await contract.setJettonWallet(userJettonWallet);
// Real transfer from Jetton wallet
const result = await userJettonWallet.sendTransfer(
user.getSender(),
contract.address,
toNano("100"),
{}
);
expect(result.transactions).toHaveTransaction({
to: contract.address,
success: true,
});
});