Secret Vault

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

L- Secrets are publicly readable on-chain

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));
// Owner sets secret
let my_secret = b"super_private";
set_secret(owner, my_secret);
// Simulate attacker reading the Vault directly from chain state
let vault = borrow_global<Vault>(signer::address_of(owner));
// This bypasses get_secret and access control
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 {});
+ }
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.