Secret Vault on Aptos

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

set_secret() function cannot update existing secret Vault

Root + Impact

Description

  • Normally, the set_secret function allows the owner to store a secret in a Vault resource. Each account can hold a single instance of the Vault resource under the owner’s address. This applies for modules as well => Aptos Docs

  • The specific issue arises because move_to will abort if a resource of the same type already exists under the caller’s account. This means the owner cannot overwrite an existing secret, causing a transaction revert when attempting to set another secret.

public entry fun set_secret(caller:&signer,secret:vector<u8>){
// initialize vault resource with secret in memory
let secret_vault = Vault{secret: string::utf8(secret)};
// store the Vault under the caller’s account address
@> move_to(caller,secret_vault); // @audit cannot update secret , only set a resource type once
// emit an event
event::emit(SetNewSecret {});
}

Risk

Likelihood:

  • Any attempt to call set_secret more than once per account will trigger this behavior.

  • Users or scripts expecting the secret to be updatable will encounter a revert.

Impact:

  • Prevents updating or rotating secrets in the Vault, limiting usability.

  • May lead to confusion or failed transactions for integrators expecting multiple updates.

Proof of Concept

The PoC uses localnet and terminal commands to simulate how move_to aborts the tx if the onwer already has a secret.

  • Start local Aptos node & faucet

aptos node run-local-testnet --with-faucet

Now we need to create a new terminal and:

After each step, if successful, the terminal will output a json with field "success": true

  • Initialize local account for deployment. It will ask you to enter a key, just press enter and it will generate account address and fund it

  • If you have already created a profile, Aptos would be initialized for it, so no need to overwrite the existing configuration

aptos init --profile 0x52 --network local
  • Publish/Deploy

Skip this step, If you already have a published module.

aptos move publish --profile 0x52 --named-addresses owner=0x52,secret_vault={generated localnet owner address}
  • Set some random secret

To check if you already have a stored secret run:

curl http://127.0.0.1:8080/v1/accounts/<owner_address>/resource/<owner_address>::vault::Vault

Output:

{
"type": "0x41e28042b2c616fa84c6d490a7ae956e9702348f8d58915b35dcf3491da72431::vault::Vault",
"data": {
"secret": "my_secret"
}
}

If not, create one:

aptos move run --function-id {generated localnet owner address}::vault::set_secret --args 'string:my_secret' --profile 0x52
  • Now, try to create/update the current secret my_secret

aptos move run --function-id {generated localnet owner address}::vault::set_secret --args 'string:new_secret' --profile 0x52

Output:

{
"Error": "Simulation failed with status: Execution failed in 0x41e28042b2c616fa84c6d490a7ae956e9702348f8d58915b35dcf3491da72431::vault::set_secret (on instruction MoveTo(StructDefinitionIndex(1))) at code offset 6\nExecution failed with message: Failed to move resource into 41e28042b2c616fa84c6d490a7ae956e9702348f8d58915b35dcf3491da72431"
}

move_to fails because Vault already exists under that address, so a second set_secret call from the same account will revert before even reaching the event emission.

Recommended Mitigation

I would recommend using the borrow_global_mut command if a secret already exists for the caller. This way, if a secret already exists and the caller wants to update it, we get a mutable reference to the resource and update the secret.

NOTE: whenever a function reads or mutates a global resource (like Vault), you must declare it in the function signature with an acquires annotation.

public entry fun set_secret(caller:&signer,secret:vector<u8>) acquires Vault{
// check if caller already has a Vault resource
if (exists<Vault>(signer::address_of(caller))) {
// if Vault exists, get a mutable reference to it
let vault_ref = borrow_global_mut<Vault>(signer::address_of(caller));
// since reference is mutable, we can update Vault secret to the new value
vault_ref.secret = string::utf8(secret);
} else {
let secret_vault = Vault{secret: string::utf8(secret)};
// create very first secret
move_to(caller, secret_vault);
}
event::emit(SetNewSecret {});
}

Now, we can recompile the module, publish it again, set very first secret and update the secret to a new value:

{
"Result": {
"transaction_hash": "0x651739af61225ef4caaaa4a6c99c78a78a4912d07b221533a617dcc7dce36625",
"gas_used": 5,
"gas_unit_price": 100,
"sender": "87782349cf9abb3491e8feebfaf6a62885df1cc3aa4d6ece4dfc1de9585c1a51",
"sequence_number": 2,
"replay_protector": {
"SequenceNumber": 2
},
"success": true,
"timestamp_us": 1755270784820923,
"version": 302,
"vm_status": "Executed successfully"
}
}

To get the new value just run curl http://127.0.0.1:8080/v1/accounts/<owner_address>/resource/<owner_address>::vault::Vault

{
"type": "0x87782349cf9abb3491e8feebfaf6a62885df1cc3aa4d6ece4dfc1de9585c1a51::vault::Vault",
"data": {
"secret": "new_secret"
}
}
Updates

Lead Judging Commences

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