Skip to main content
In NEAR, your contract can call a function from another contract, and then schedule a callback to handle its result. The callback function needs to be “public” - meaning that it needs to be callable by the NEAR runtime - but it should be only callable by your own contract. Otherwise, a malicious user could call the callback directly and pretend that the cross-contract call succeeded or failed.

The vulnerable pattern

Imagine a contract that stakes and unstakes tokens on behalf of a user:
Rust: vulnerable flow

How the exploit happens

Notice that the user cannot call unstake_callback directly, because there is a #[callback_result] argument. This is a special argument that the NEAR runtime fills in with the result of the preceding promise, rather than an argument supplied by the caller. However, the user can still create a malicious contract with a function call_unstake_callback that calls unstake_callback directly, passing as parameters the original user and amount, and a positive result. In this case, the #[callback_result] will be positive, and thus the function unstake_callback will credit the user with the unstaked amount, even though the unstaking failed.

The safe pattern

In Rust, mark the callback with #[private]. This checks that the immediate caller of a public function is your own contract:
#[private] means: only the env::current_account_id() can call this function. If any other account tries to call it, the NEAR runtime will reject the call. For calls that attach NEAR tokens, see refund NEAR tokens for the failure path.