Secret Vault on Aptos

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

Missing Overwrite Protection in `set_secret``

Root + Impact

Description

  • Calling move_to(account, Resource{...}) without checking exists<Resource>(account) will abort if the resource already exists, so your set_secret works only once per account and then permanently fails on updates — a denial of service for ordinary updates.

  • First call: set_secret(owner, bytes)move_to creates resource → succeeds.

  • Second call by same owner: move_to tries to create resource again → abort with RESOURCE_ALREADY_EXISTS (Move runtime abort).

  • The transaction that aborts will revert any side effects in that transaction (no partial changes).

// events
#[event]
struct SetNewSecret has drop, store {//@Stored in plain text on-chain
}
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:

Impact:

  • Users cannot update their secret once set, reducing functionality. This also causes the test to fail after the first run.

Proof of Concept

  1. First call: set_secret(owner, bytes)move_to creates resource → succeeds.

  2. Second call by same owner: move_to tries to create resource again → abort with RESOURCE_ALREADY_EXISTS (Move runtime abort).

  3. The transaction that aborts will revert any side effects in that transaction (no partial changes).

// Test that specifically targets Bug #2 (create vs update).
// Passes only if set_secret supports update (exists + borrow_global_mut or equivalent).
#[test(owner = @0x1)]
fun test_set_secret_update_works(owner: &signer) {
// first set (create)
let s1 = b"first-secret".to_vec();
set_secret(owner, s1);
// second set (update) — should NOT abort; should overwrite the previous secret
let s2 = b"second-secret-updated".to_vec();
set_secret(owner, s2);
// verify the stored secret equals the updated value
let fetched = get_secret(owner);
assert!(fetched == b"second-secret-updated".to_vec(), 200);
// Optional debug print for test runners that show prints
// debug::print(&b"test_set_secret_update_works passed".to_vec());
}

Recommended Mitigation

  • Replace the single unconditional move_to in set_secret with the following logic:

    • If a Vault exists at the owner address → update in-place using borrow_global_mut<Vault>(addr).

public entry fun set_secret(owner: &signer, secret: vector<u8>) acquires Vault {
assert!(!vector::is_empty(&secret), ZERO_INPUT);
let addr = signer::address_of(owner);
if (exists<Vault>(addr)) {
let vref = borrow_global_mut<Vault>(addr);
vref.secret = secret;
} else {
move_to(owner, Vault { secret });
}
}
Updates

Lead Judging Commences

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

The `secret` can not be updated

Support

FAQs

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