Secret Vault

First Flight #46
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Severity: high
Valid

Critical Plaintext Secret Storage Vulnerability

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 // @> Critical: Plaintext storage with no encryption
}
public entry fun set_secret(caller:&signer,secret:vector<u8>){
let secret_vault = Vault{secret: string::utf8(secret)}; // @> Converts to plaintext string
move_to(caller,secret_vault); // @> Stores plaintext in global storage
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;
// Setup test accounts
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(hacker));
// Owner stores what they think is a "secret"
let sensitive_data = b"my_private_key_abc123";
set_secret(owner, sensitive_data);
let owner_addr = signer::address_of(owner);
// VULNERABILITY: Anyone can read the secret directly from storage
let vault_resource = borrow_global<Vault>(owner_addr);
let exposed_secret = vault_resource.secret;
// Prove the secret is completely exposed
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
}
Updates

Lead Judging Commences

bube Lead Judge 11 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Anyone can see the `secret` on chain

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.