Root + Impact
Description
Normal Behavior
The Secret Vault contract is designed to allow only the owner to store and retrieve their private secrets securely on the Aptos blockchain.
Issue
The contract stores all "secrets" as plaintext in global storage, making them publicly readable by anyone. This completely defeats the purpose of a "secret vault" as all stored data is immediately visible to the entire world through direct blockchain queries.
struct Vault has key {
secret: String
}
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 {});
}
Risk
Likelihood:
Every secret stored is immediately exposed upon transaction confirmation
Any user, developer, or indexer can read the data without calling contract functions
Blockchain explorers and REST APIs expose this data publicly
Impact:
Complete loss of confidentiality for all stored secrets
Permanent exposure (blockchain data is immutable)
Violation of user trust and security expectations
Potential exposure of sensitive data like private keys, passwords, or personal information
Proof of Concept
The following test demonstrates how anyone can read "secrets" directly from storage:
#[test(owner = @0xcc, hacker = @0x999)]
fun test_plaintext_secret_vulnerability(owner: &signer, hacker: &signer) acquires Vault {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(hacker));
let sensitive_data = b"my_private_key_abc123";
set_secret(owner, sensitive_data);
let owner_addr = signer::address_of(owner);
let vault_resource = borrow_global<Vault>(owner_addr);
let exposed_secret = vault_resource.secret;
assert!(exposed_secret == string::utf8(sensitive_data), 100);
debug::print(&b"SECRET STOLEN: ");
debug::print(&exposed_secret);
}
Recommended Mitigation
struct Vault has key {
- secret: String
+ encrypted_secret: vector<u8> // Store only encrypted data
}
public entry fun set_secret(caller: &signer, encrypted_secret: vector<u8>) {
assert!(signer::address_of(caller) == @owner, NOT_OWNER);
- let secret_vault = Vault{secret: string::utf8(secret)};
+ let secret_vault = Vault{encrypted_secret};
move_to(caller, secret_vault);
event::emit(SetNewSecret {});
}
#[view]
public fun get_encrypted_secret(caller: address): vector<u8> acquires Vault {
assert!(caller == @owner, NOT_OWNER);
let vault = borrow_global<Vault>(@owner);
- vault.secret
+ vault.encrypted_secret
}