Secret Vault

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

H- Missing Owner Check in set_secret Allows Anyone to Overwrite Owner’s Secret

Root + Impact

Description

Only the designated owner (@owner address from Move.toml) should be able to store a secret in the Vault resource. Other accounts should have no ability to modify the owner’s stored secret.

Issue:

set_secret does not verify that the caller is the owner. This allows any account to call set_secret and overwrite the Vault resource belonging to the owner. In Move, move_to will replace the existing resource if one exists for that account, so this directly destroys the original secret.

public entry fun set_secret(caller:&signer, secret:vector<u8>) {
let secret_vault = Vault{secret: string::utf8(secret)};
// @> No ownership check here
move_to(caller, secret_vault);
event::emit(SetNewSecret {});
}

Risk

Likelihood:

  • Any user can call set_secret without restriction.

  • This will occur immediately when a malicious actor sees the module on-chain, since the entry function is public and permissionless.

Impact:

  • Permanent loss of the original secret stored by the owner.

  • Integrity breach: malicious data is stored in place of the true secret, breaking application trust.

Proof of Concept:

Test case:

#[test(owner = @0xcc, attacker = @0x999)]
fun test_overwrite_secret(owner: &signer, attacker: &signer) acquires Vault {
use aptos_framework::account;
// Create accounts for both the legitimate owner and an attacker
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(attacker));
// STEP 1: Owner stores their legitimate secret
let original_secret = b"legit secret";
set_secret(owner, original_secret);
// At this point, Vault is stored under @0xcc (the owner’s address)
// STEP 2: Attacker creates their *own* Vault
// PROBLEM: set_secret() uses the caller's signer address and does not check if they are the `owner`
let malicious_secret = b"hacked secret";
set_secret(attacker, malicious_secret);
// This creates a *separate* Vault resource at @0x999 (attacker’s address)
// STEP 3: Attacker retrieves their own Vault
let vault = borrow_global<Vault>(signer::address_of(attacker));
// STEP 4: This passes — attacker’s Vault contains their malicious secret
assert!(vault.secret == string::utf8(malicious_secret), 1000);
// 💡 Issue demonstrated:
// Any account can call set_secret() to store *their own* Vault.
// The spec requires that only the single `owner` address should be able to store a secret.
// This is a logical flaw — there’s no global ownership check in set_secret().
}

Test Results:

Auditing/FirstFlight/2025-07-secret-vault\$ aptos move test --filter test\_overwrite\_secret
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING aptos-secret-vault
Running Move unit tests
\[ PASS ] 0x234::vault::test\_overwrite\_secret
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}

Recommended Mitigation:

Give permisson only to the owner and restrict to others...

public entry fun set_secret(caller:&signer, secret:vector<u8>) {
- Not checking any Owner this cause protocol (mentioned in the docs(.md))
let secret_vault = Vault{secret: string::utf8(secret)};
move_to(caller, secret_vault);
event::emit(SetNewSecret {});
}
After add the assert u can restrict the permission and give permission to Owner only
public entry fun set_secret(caller:&signer, secret:vector<u8>) {
+ assert!(signer::address_of(caller) == @owner, NOT_OWNER);
let secret_vault = Vault{secret: string::utf8(secret)};
move_to(caller, secret_vault);
event::emit(SetNewSecret {});
}
Updates

Lead Judging Commences

bube Lead Judge 11 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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