Secret Vault

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

Secrets cannot be updated (logic DoS on rotation)

Description

set_secret always calls move_to<Vault>(caller, …). In Move, move_to aborts if a Vault resource already exists under that account. After the first write, all subsequent updates will abort, making rotation or correction of the secret impossible.

public entry fun set_secret(caller: &signer, secret: vector<u8>) {
let secret_vault = Vault { secret: string::utf8(secret) };
move_to(caller, secret_vault); // aborts if Vault already exists
event::emit(SetNewSecret {});
}

Root Cause

  • No exists<Vault>(addr) branch.

  • No borrow_global_mut<Vault>(addr) path to modify an existing resource.


Risk

Likelihood:

  • After the first successful set_secret, every subsequent call triggers the existing-resource abort in move_to.

  • Normal usage expects multiple updates over time (typos, rotation, new values), so the failure will be routinely hit.

Impact:

  • Owner cannot rotate or update their secret.

  • Usability failure and security weakness (rotation is a standard hardening practice).

  • Forces workarounds (e.g., migrate to a new account), which is impractical.

Proof of Concept

#[expected_failure] test_set_secret_aborts_on_update demonstrates the second set_secret call aborts.

#[test(owner = @0xcc)]
#[expected_failure] // we expect the second call to abort
fun test_set_secret_aborts_on_update(owner: &signer) {
use aptos_framework::account;
use std::signer;
// Arrange
account::create_account_for_test(signer::address_of(owner));
let first = b"first";
let second = b"second";
// First publish succeeds
set_secret(owner, first);
// Act: publishing again abort (Vault already exists under owner)
// This line triggers the expected failure.
set_secret(owner, second);
}

Recommended Mitigation

Update in place when the resource exists; otherwise create it.

(Note: acquires Vault is required for functions that borrow_global(_mut) or move_from, but not for move_to. Add acquires Vault to set_secret after this change.)

- 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>) acquires Vault {
+ let addr = signer::address_of(caller);
+ if (exists<Vault>(addr)) {
+ let vault = borrow_global_mut<Vault>(addr);
+ vault.secret = string::utf8(secret); // update in place
+ } else {
+ let secret_vault = Vault { secret: string::utf8(secret) };
+ move_to(caller, secret_vault);
+ };
+ event::emit(SetNewSecret {});
}
Updates

Lead Judging Commences

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