Root + Impact
Description:
The Vault
resource stores secret
as a plaintext String
in global storage.
Even though get_secret()
enforces ownership checks, anyone can bypass these by directly reading the resource off-chain using borrow_global
from a full node or indexer.
struct Vault has key {
@> secret: String
}
Risk
Likelihood:
-
Public blockchain data is globally visible.
-
Any node, indexer, or explorer can read the secret
field without calling get_secret
.
Impact:
-
Privacy breach — all secrets stored are in plaintext.
-
Once revealed on-chain, cannot be removed or hidden.
Proof of Concept
Test Contract :
#[test(owner = @0xcc, attacker = @0x999)]
fun test_secret_is_readable_offchain(owner: &signer, attacker: &signer) acquires Vault {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(attacker));
let my_secret = b"super_private";
set_secret(owner, my_secret);
let vault = borrow_global<Vault>(signer::address_of(owner));
assert!(vault.secret == string::utf8(my_secret), 1001);
}
TestOutput:
SCATERLABs/Auditing/FirstFlight/2025-07-secret-vault$ aptos move test --filter test_secret_is_readable_offchain
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING aptos-secret-vault
Running Move unit tests
[ PASS ] 0x234::vault::test_secret_is_readable_offchain
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}
SCATERLABs/Auditing/FirstFlight/2025-07-secret-vault$
Recommended Mitigation:
In Blockchain everything will be public so better to use encrypted format..
- struct Vault has key {
- secret: String
- }
+ struct Vault has key {
+ // Store encrypted secret or hash instead of plaintext
+ secret_hash: vector<u8>
+ }
- public entry fun set_secret(caller: &signer, secret: vector<u8>) {
- let secret_vault = Vault { secret: string::utf8(secret) };
- move_to(caller, secret_vault);
- event::emit(SetNewSecret {});
- }
+ public entry fun set_secret(caller: &signer, secret: vector<u8>) {
+ // Off-chain: secret should be hashed or encrypted before being passed here
+ let secret_vault = Vault { secret_hash: secret };
+ move_to(caller, secret_vault);
+ event::emit(SetNewSecret {});
+ }