Secret Vault on Aptos

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

Write-once / Secret Rotation DoS

Root + Impact

Description

  • The set_secret() function uses move_to to publish a new Vault resource under the caller’s account every time it is invoked

  • In Move, move_to aborts if the resource already exists at that address, which means:

    • The first call works, storing the initial secret.

    • Any subsequent call by the same account aborts, because Vault already exists

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: High

  • Any real user will eventually need to update or change the stored secret. The bug is guaranteed to manifest on the second use.

Impact: Medium

  • Permanent denial of service on secret rotation.

  • Breaks usability and may lock users into an outdated or incorrect secret forever.

Proof of Concept

Add the following test, then run the command: aptos move test -f test_move_to_twice_aborts

#[test(owner = @0xcc)]
#[expected_failure] // second move_to<Vault> at same address aborts (resource already exists)
fun test_move_to_twice_aborts(owner: &signer) {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
// First publish succeeds.
set_secret(owner, b"first");
// Second publish tries move_to again → aborts because a Vault already exists.
// This shows "one-shot" behavior: you can't rotate/update via move_to.
set_secret(owner, b"second");
}

Recommended Mitigation

Replace unconditional move_to with a create-or-update pattern, this allows the first write to succeed and subsequent calls to safely update the secret.

public entry fun set_secret_rotatable(caller:&signer,secret:vector<u8>) acquires Vault {
if (exists<Vault>(signer::address_of(caller))) {
let v = borrow_global_mut<Vault>(signer::address_of(caller));
v.secret = string::utf8(secret);
} else {
move_to(caller, Vault { secret: string::utf8(secret) });
};
event::emit(SetNewSecret {});
}
Updates

Lead Judging Commences

bube Lead Judge 15 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.