Secret Vault

First Flight #46
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Missing Resource Existence Checks Causing Uncontrolled Aborts

Root + Impact

Description

Normal Behavior

The get_secret function should gracefully handle cases where no vault exists for the requested address, providing clear error messages and meaningful feedback to users about why the operation failed.

Issue

The get_secret function calls borrow_global<Vault>(@owner) without first checking if the resource exists, causing uncontrolled aborts with cryptic error messages when no vault has been created. This results in poor user experience where users receive technical error codes instead of clear feedback about the system state.

#[view]
public fun get_secret(caller: address): String acquires Vault {
assert!(caller == @owner, NOT_OWNER);
let vault = borrow_global<Vault>(@owner); // @> Aborts with MISSING_DATA if no vault exists
vault.secret
}

Risk

Likelihood:

  • Any call to get_secret before set_secret triggers the error

  • New users will encounter this immediately upon trying to read non-existent data

  • The calling order determines success/failure unpredictably

  • No mechanism exists to check vault existence before calling

Impact:

  • Poor user experience: Users receive cryptic technical error messages

  • Unpredictable behavior: Function success depends on hidden state and calling order

  • Development friction: Developers cannot easily handle the "no data" case

  • Support burden: Users need help interpreting technical error codes

  • API inconsistency: No way to distinguish between different failure scenarios

Proof of Concept

The following tests demonstrate the missing existence checks vulnerability:

#[test(owner = @0xcc)]
#[expected_failure(abort_code = 0x60001)] // RESOURCE_NOT_FOUND
fun test_get_secret_aborts_on_missing_vault(owner: &signer) acquires Vault {
account::create_account_for_test(signer::address_of(owner));
let owner_addr = signer::address_of(owner);
// Verify no vault exists
assert!(!exists<Vault>(owner_addr), 1000);
// This aborts with cryptic MISSING_DATA error instead of graceful handling
let _secret = get_secret(owner_addr); // ❌ ABORTS with technical error!
}
#[test(owner = @0xcc, user_a = @0x111, user_b = @0x222)]
fun test_missing_existence_checks_vulnerability(owner: &signer, user_a: &signer, user_b: &signer) {
// Demonstrate the ordering dependency
let owner_addr = signer::address_of(owner);
let user_a_addr = signer::address_of(user_a);
// Initially no vaults exist
assert!(!exists<Vault>(owner_addr), 100);
assert!(!exists<Vault>(user_a_addr), 101);
// User A creates a vault
set_secret(user_a, b"user_a_secret");
// Now user_a has a vault but owner still doesn't
assert!(!exists<Vault>(owner_addr), 102); // Still false
assert!(exists<Vault>(user_a_addr), 103); // Now true
// get_secret(@owner) would still abort with MISSING_DATA
// even though other users have successfully created vaults
}
#[test(owner = @0xcc)]
fun test_proper_existence_checking_pattern(owner: &signer) acquires Vault {
let owner_addr = signer::address_of(owner);
// PROPER PATTERN: Check existence first
if (exists<Vault>(owner_addr)) {
let vault = borrow_global<Vault>(owner_addr);
// Safe to access vault.secret
} else {
// Provide clear feedback instead of crashing
// Could return Option<String>, custom error, or default value
};
// After setting secret, the check works
set_secret(owner, b"now_we_have_a_secret");
if (exists<Vault>(owner_addr)) {
let vault = borrow_global<Vault>(owner_addr);
// Now safe to access
};
}

Recommended Mitigation

+ const NO_VAULT_EXISTS: u64 = 2;
#[view]
public fun get_secret(caller: address): String acquires Vault {
assert!(caller == @owner, NOT_OWNER);
+ assert!(exists<Vault>(@owner), NO_VAULT_EXISTS);
let vault = borrow_global<Vault>(@owner);
vault.secret
}
Updates

Lead Judging Commences

bube Lead Judge 11 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Lack of `Vault` existence check in `get_secret`

There is no security impact on the protocol, therefore this is an Informational finding. Also, it is a user mistake, if the user calls `get_secret` without first calling `set_secret`.

Support

FAQs

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