As of 10 Nov 2026, TON has 6 audit reports by 2 security firms on Trustblock; the latest is by Trail of Bits, dated 25 Mar 2024. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.
Newest first
1 other security report listed, not counted
| Report | Security firm | Date | Scope | Critical findings | Source |
|---|---|---|---|---|---|
| TON Foundation Multisignature Wallet | Security firm: Trail of Bits | Date: | Scope: —Not recorded; see the report | None open | GitHub ↗ |
| TON-EVM Bridge | Security firm: CertiK | Date: | Scope: Files listed | None open | skynet.certik.com ↗ |
| TON Stablecoin | Security firm: CertiK | Date: | Scope: —Not recorded; see the report | None open | skynet.certik.com ↗ |
| Ton Bridge Web Application Penetration test of off-chain systemsNot counted as an audit report | Security firm: CertiK | Date: | Scope: —Not recorded; see the report | None open | skynet.certik.com ↗ |
| TON Masterchain Contracts Formal VerificationFormal verification | Security firm: CertiK | Date: | Scope: —Not recorded; see the report | None open | skynet.certik.com ↗ |
| TON Vesting | Security firm: CertiK | Date: | Scope: —Not recorded; see the report | None open | skynet.certik.com ↗ |
| The Open Network - Audit 1 (Phase 2) | Security firm: CertiK | Date: | Scope: —Not recorded; see the report | None open | skynet.certik.com ↗ |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 3 | 1 | 2 |
| Medium | 2 | 0 | 11 |
| Low | 17 | 0 | 37 |
The report gives no description here.
Open the reportMalicious actor can receive a valid block, alter the `block->data->deps` array and rebroadcast the block to other nodes. New block will be valid for catchain nodes but invalid for BCP. The block validation works this way: 1. Overlay gets the block and calls `CatChainReceiverImpl::receive_message_from_overlay(src, data)` 2. `catchain_block` is fetched from `data`, the rest is treated `payload` 3. `CatChainReceiverImpl::receive_block(src, catchain_block, payload)` is called 4. `CatChainReceiverImpl::validate_block_sync(block, payload)` is called 5. `CatChainReceivedBlock::pre_validate_block(block, payload)` is called - `block->incarnation` is verified - `block->src` is verified - `block->data->deps.size()` is verified to be not more than `max_deps` - `block->data->prev->src` is verified to be the same as `block->src` - `block->data->prev->height + 1 == block->height` is verified - it is verified that there are no `deps` from the same source - it is verified that `payload` is not empty - **`block->data->deps` array is not checked and not covered by signature** 6. For each `deps` and `prev` as argument `CatChainReceiverImpl::validate_block_sync(dep)` is called - Signature (by `dep.src`) of `block_dep_id` is checked. `block_dep_id = { incarnation, source_hash(dep.src), dep.height, dep.data_hash }` 8. Signature (by `src`) of `block_id` is checked. `block_id = { incarnation, source_hash(src), height, hash(payload) }` of original block 9. `CatChainReceiverImpl::create_block(block, payload)` is called and updates `blocks_[hash(block_id)]` This can lead to network stalling without ability to punish the malicious actor. For example: 1. Malicious actor can clear the `deps` array of received block and force `CatChainReceiverImpl::synchronize_with()` all other nodes. They will accept the modified block and `CatChainReceiverImpl::deliver_block()` immediately to `ValidatorSessionImpl::preprocess_block()` with undefined result. 2. Malicious actor can replace one `dep` with unexisting correctly signed `block_id` and push it to other nodes. They will accept it, will never be able to deliver that block to `ValidatorSession`, and will reject the same block with correct `deps` from other nodes as duplicate.
Open the reportThe report gives no description here.
Open the reportThe report gives no description here.
Open the report`config_param` contains `bridge_address` and `oracles_address`. Governance (`multisig-code`) is saved as `oracles_address`. It can only send messages to `bridge_address` or to any address if it is no longer assigned as `oracles_address`. Bridge (`bridge_code`) is saved as `bridge_address`. It accepts "swap" commands from any address (locking TONs) or control commands from `oracles_address`. If Validators update both `config_param` values at once, then old Bridge no longer can get control commands: old Governance will be unauthorized, new Governance will send commands only to new Bridge. The reward accumulated on old Bridge will be locked. If Validators update only `bridge_address`, then old Bridge no longer can get control commands: Governance will send commands only to the new Bridge. That means that to perform a Bridge "migration", new Governance contract has to be modified to allow additional messages recipient, new Bridge contract has to be modified to accept `0xf00d` message. That can potentially lead to stake extraction risk.
Open the report`votes-collector` collects Secp256k1 signatures to be used in ETH Bridge call. One malicious oracle can submit invalid signatures to each voting. This will practically block the ETH side of the bridge - it will `revert()` trying to validate the signature. To restore the correct work, the honest oracles have to 1. Notice that ETH transactions are failing 2. Determine which signature is invalid 3. Cut the invalid signature from the list 4. Resubmit the failed ETH transactions 5. Ignore the malicious oracle in future This will likely take a significant time to implement.
Open the report`jetton-minter` during processing of `op::mint` message uses `calculate_user_jetton_wallet_address()` to prepare the `jetton-wallet` address. However, even if `to_address` is not in basechain, the generated wallet address is in `workchain = 0`. As a result, the minted jettons will not be accessible.
Open the reportWhen `op::internal_transfer` message is bounced from one `jetton-wallet` to another one, the `balance` is updated (increased back). However, if `op::internal_transfer` message is bounced from `jetton-wallet` to `jetton-minter`, the `total_supply` is not updated. `master_msg` provided to `op::mint` is not validated and passed as is to `jetton-wallet`. Underflow can happen. As a result the total circulating supply will be less than `total_supply`.
Open the reportDuring processing of `op::upgrade` the inconsistent `new_data` can be assigned. That will invalidate all the deployed wallets or `total_supply` value. `new_code` is not validated for correctness. Inconsistent storage can be also set during `jetton-minter` deployment. During processing of `op::call_to` and `op::mint` the `master_msg` and `amount` are not validated. During processing of `op::change_content` it is not validated that updated `content` follows [TIP-64 (Token Data Standard)](https://github.com/ton-blockchain/TIPs/issues/64). Incorrect data can be assigned. `symbol`, `name`, `decimals` can be changed. Well known Jetton can be renamed to something else or to another well known Jetton. This can influence the Jetton price. During processing of `op::change_admin` the `new_admin_address` is not validated. Assigning the wrong admin can prevent further minting.
Open the reportThe result of the "npm audit" command shows the application is using outdated libraries with know vulnerabilities. 
Open the reportThe function `process_new_stake` has a test that the submitted stake is larger than `min_stake`. ```func if (msg_value < min_stake) { ;; stake too small, return it return return_stake(s_addr, query_id, 5); } ``` However, nothing prevents a user from submitting a bid larger that `max_stake`. The function `try_elect` will then restrict the bid to be less than `max_stake`: ```func var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256)); ``` If the user submitted more than `max_stake`, then at this point the contract will forget about the excess part, which will become unretrievably locked in the contract balance. Note that `max_stake` is a configuration parameter which can be changed at any time by the owner of the config contract. This makes the situation potentially more error prone, because such a change could happen between the user's bid submission and the next election. So if the limit was lowered, the user could submit a bid which was valid at the time time of submission but triggered this bug at the time of election.
Open the reportMalicious actor can create many valid blocks to overflow internal structures of honest nodes and provoke their crash. 1. Many blocks with increasing `height` can be formed and sent by the malicious actor 2. Honest node Overlay gets the block and calls `CatChainReceiverImpl::receive_message_from_overlay(src, data)` 3. `CatChainReceiverImpl::receive_block()` is called 4. `CatChainReceiverImpl::create_block()` is called 5. `CatChainReceivedBlock::create()` allocates memory in heap Old blocks unloading is not implemented in general.
Open the reportAccording to [Whitepaper](https://test.ton.org/catchain.pdf) the Catchain blocks form a directed acyclic graph using their `deps`. The Catchain code doesn't ensure that. Let's assume: 1. Malicious actor A creates a BlockA and shares its `block_id_A (incarnation, hash(src), height, hash(payload)` with malicious actor B 2. Malicious actor B creates a BlockB, shares its `block_id_B` with A 3. Malicious actor A uses `block_id_B` as part of `BlockA->data->deps` and broadcasts the block in the Catchain 4. Malicious actor B uses `block_id_A` as part of `BlockB->data->deps` and broadcasts the block in the Catchain 5. Honest node C gets two blocks that depend on each other Currently the code works correctly in this situation.
Open the report`spend_delay` in fact holds the timestamp, not "delay".
Open the reportThe meaning of `state_flags` is not described. Setting the first bit prevents locking of TONs, however, doesn't prevent withdrawing.
Open the reportContract storage organization and incoming message layout are not described explicitly.
Open the reportLicense Identifier should be on the first line.
Open the report```cpp=83 ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000 var msg = begin_cell() .store_uint(0x10, 6) ;; #int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddressInt = 0 1 0 0 0 00 ``` First comment mentions the binary 011000 (0x18), however, the code uses 010000 (0x10). The second comment mostly duplicates the first one, but erroneously contains `MsgAddressInt` and wrong bit sequence.
Open the reportThere are typos: - "transder" is supposed to be "transfer" - "transfer" is supposed to be "burn"
Open the reportThe function `load_data()` reads all the fields of Storage, however, it does not ensure that the slice is empty after that.
Open the reportAccording to the code zero `status` is considered "unlocked". Any other `status` is considered "locked". Locked status forbids performing `send_tokens()` but allows to `burn_tokens()` and `receive_tokens()`.
Open the reportIn `calculate_jetton_wallet_state_init()` `StateInit` structure is used: ```c _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) = StateInit; ``` In `calculate_jetton_wallet_address()` `MsgAddressInt` structure is used: ```c addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt; ``` `send_raw_message()` arguments preparation is not documented. `send_raw_message()` `mode` argument is not documented in comments. No corresponding constants introduced.
Open the report`Storage` comment in `jetton-wallet` doesn't mention `status` field. `transfer` and `internal_transfer` comments don't contain the structure tags. Other structures: `transfer_notification`, `excesses`, `burn`, `burn_notification`, - should be described. `founds` is supposed to be `funds`. `steel` is supposed to be `still`. `uin4` is supposed to be `uint4`. `is only decreases` is supposed to be `only decreases`.
Open the reportAccording to [TIP-74 standard (TON fungible tokens)](https://github.com/ton-blockchain/TIPs/issues/74) "After processing the request, the receiver's jetton-wallet **must** send at least `in_msg_value - forward_amount - 2 * max_tx_gas_price` to the `response_destination` address. If the sender jetton-wallet cannot guarantee this, it must immediately stop executing the request and throw error." So, the `op::transfer` is allowed to do two processings only. However, these values can be deducted: 1. Up to `min_tons_for_storage` can be left on the `receiver_wallet` 2. Up to 2 forward fees can be spent: one for `op::internal_transfer` from `sender_wallet` to `receiver_wallet`, one for `op::transfer_notification` from `receiver_wallet` to `receiver_owner` 3. 2 processing fees can be spent: one for `op::transfer` processing, one for `op::internal_transfer` processing In fact `op::excesses` message will get only `op::transfer::in_msg_value - forward_ton_amount - min_tons_for_storage - 2 * fwd_fee - 2 * max_tx_gas_price`. Also, `gas_consumption` constant is hardcoded, not obtained from `ConfigParam21 gas_limit`. This makes the calculations inaccurate for different workchains.
Open the reportThe HTTP response of the GET request to https://ton.org/bridge/ doesn't contain any security headers to prevent the website from loading in an iframe.
Open the reportThe bridge application "https://ton.org/bridge/" is hosted on a third-party platform, "Github". If the application is hosted on a third-party platform, then the team is trusting the platform's own security to prevent an attacker from modifying the site to perform malicious actions within the application. The host "ton.org" points to the Cloudflare CDN. One of the benefits of using Cloudflare is that it offers security features such as "WAF" and DDOS protection. However, the application can be accessed with the alternative URL "https://newton-blockchain.github.io/bridge/". This exposed the application from Cloudflare's protection.
Open the reportThe `conduct_elections` function computes the next validator set and then stores it in the `past_elect` dictionary. The key it is stored under is the `elect_at` field, the time at which the election takes place. If there somehow was already a past validator set stored under the same key, the past set would be overwritten and all the stakes in it lost. The `elect_at` field is chosen in `announce_new_elections` to be a time in the future (`now() + elect_begin_before`), so intuitively it will be distinct from other election times in the past. However, there is some extra logic that can move it to a slightly earlier time ("pretend that the elections started at t0"). Exactly which time `t0` would be depends on configuration parameters which are set and changed outside the elector contract, which makes it hard to formally prove that there can never be a clash. The likelihood of this going wrong may seem remote, but another check would make the contract more self-contained.
Open the reportThere are two cases in the elector contract when it sends a message and includes approximately 1 Gram of value in order to pay for the message processing costs. These are in `process_new_stake` (the call to `send_confirmation`) and in `conduct_elections` (the call to `send_validator_set_to_config`). In `process_new_stake` this cost is deducted from the posted stake, so that the accounting is correct: ```func msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation ``` However, there is no logic in `conduct_elections` which explains where the value of the message should be deducted from, which breaks the invariant that data structures reflect the current balance of the contract.
Open the reportThe function `announce_new_elections` takes an argument `elect` despite declaring a local variable with the same name which is used in the function body. The argument is redundant.
Open the report`mode = 3` means: +1 - sender pays fees, +2 - ignore errors. It is unclear, why "ignore errors" mode is enforced by the contract.
Open the reportThe function `recv_internal()` is defined as `impure`, but it does not modify the storage or throw an exception.
Open the report`CatChainReceivedBlockImpl` has fields `block_deps_`, `deps_`, `rev_deps_`, `pending_deps_`. - `block_deps_` holds pointers to blocks from other nodes - `deps_` holds **heights** of this block source chain pieces - `rev_deps_` holds pointers to blocks that depend on this one - `pending_deps_` holds the number of full blocks required to `CatChainReceiverImpl::run_block()` `deps_` name is misleading.
Open the report`CatChainReceivedBlockImpl` has fields `data_`, `hash_`, `payload_` and `data_hash_`. `data_` field is unused. `data_hash_` in fact holds `payload_` hash.
Open the reportCatchain expects every full block received to have not more than `max_deps` dependencies. `max_deps` is 4 at this moment. However, when full block is sent to network, the `deps` size is not checked.
Open the report```cpp=85 if (!blocks_.size()) { ``` `blocks_.empty()` can be used instead of `!blocks_.size()`.
Open the report`map::emplace()` returns `std::pair<iterator,bool>`. `iterator` can be used to avoid the call to `map::find()`.
Open the reportIndexing `blamed_sources_[src_id]` is done without validation of `src_id` correctness.
Open the report```cpp=35 auto B = *top_blocks_.get_random(); CHECK(B != nullptr); top_blocks_.remove(B->hash()); if (B->source() == sources_.size() || !blamed_sources_[B->source()]) { ``` The code allows `B->source() == sources_.size()`, however, all the insertions to `top_blocks_` ensure the valid `src_`.
Open the report`CatChainReceiverSourceImpl::validate_dep_sync()` is never used. `CatChainReceiverImpl::validate_block_sync(dep)` is used instead.
Open the reportThe resulting size of `ids` vector is known in advance. Using of `vector::push_back()` leads to unnecessary relocations.
Open the report`CatChainReceiverImpl::destroy()` is supposed to stop trying after 10 failed attempts. However, the counter is not incremented - it will try endlessly. The same problem affects also `validator/db/archive-slice.cpp` (out of audit scope).
Open the reportMagic numbers are used directly in code.
Open the report```cpp=568 B->written(); CHECK(B); ``` The pointer `B` is dereferenced before it is checked.
Open the reportThe code assumes `dst` argument is valid and source `S` is non-zero. No check is performed before dereferencing.
Open the reportThe linked statement does not affect the functionality of the codebase. Both `r` and `my_vt[i] - vt[i]` are checked to be positive.
Open the report```cpp=283 td::BufferSlice raw_data{32}; raw_data.as_slice().copy_from(as_slice(id)); ``` Magic number 32 assumes the hash value fits 32 bytes.
Open the report"syncronize" is supposed to be "synchronize".
Open the reportIf the function doesn't modify the argument, it should accept it as `const *` or `const &`. ```cpp=362 td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, tl_object_ptr<ton_api::catchain_block> &block, td::Slice payload) { ``` This and some other functions don't modify the arguments. Methods that don't modify the object state should be declared as `const`. If cycle doesn't modify the container elements, the variable `X` should be declared as `const`: ```cpp=400 for (auto &X : block->data_->deps_) { ```
Open the reportIn many functions non-trivial arguments are passed by value. This leads to unnecessary copying. Non-trivial arguments are `PublicKeyHash`, `CatChainSessionId`, `td::Slice`, `td::SharedSlice` and others.
Open the reportIt is not recommended to use `l`, `o` and some other variable names for better code readability.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="the-open-network"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us