Secret Vault on Aptos

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

SecretVault - Insufficient Access Control in get_secret() Allows Unauthorized Secret Access

Description

The get_secret() function uses an address parameter instead of &signer, allowing any caller to pass the owner's address and retrieve secrets without authentication. This violates the core security principle that "only the owner should be able to retrieve their secret."

Root Cause

The function accepts an address parameter which can be provided by any caller, rather than requiring a &signer which proves transaction authorization. The current implementation only checks if the provided address matches @owner, but doesn't verify that the caller actually controls that address.

#[view]
public fun get_secret(caller: address): String acquires Vault {
// @audit-issue incorrect as anyone can pass the owner address
assert!(caller == @owner, NOT_OWNER);
let vault = borrow_global<Vault>(@owner);
vault.secret
}

Key issues:

  1. Uses address parameter instead of &signer for authentication

  2. Any attacker can simply pass @owner as the parameter to bypass the check

  3. The #[view] decorator prevents using &signer, creating a design conflict

Risk

Likelihood: High - Trivial to exploit by passing the owner's address as parameter

Impact: High - Complete exposure of secret data to any unauthorized caller

Impact

High severity because:

  • Completely breaks confidentiality of stored secrets

  • Allows any account to read the owner's secret without authorization

  • Defeats the entire purpose of a "secret" vault

Proof of Concept

With the vulnerable #[view] function using address parameter, any attacker can read the owner's secret:

#[test_only]
use std::debug::print;
#[test_only]
use std::signer;
#[test(account = @attacker, owner = @owner)]
fun test_view(account: signer, owner: signer) {
vault::set_secret(&owner, b"new secret");
// Attacker simply passes owner's address to read the secret
let value = vault::get_secret(signer::address_of(&owner));
print(&value);
}

Test Result - Attacker succeeds:

Running Move unit tests
[debug] "new secret"
[ PASS ] 0xa77ac4::Attacker::test_view

The attacker successfully reads the owner's secret!

Recommended Mitigation

Change the function to use &signer for proper authentication and remove the #[view] decorator:

- #[view]
- public fun get_secret(caller: address): String acquires Vault {
- assert!(caller == @owner, NOT_OWNER);
+ public fun get_secret(caller: &signer): String acquires Vault {
+ assert!(signer::address_of(caller) == @owner, NOT_OWNER);
let vault = borrow_global<Vault>(@owner);
vault.secret
}

Test After Mitigation:

After removing #[view] and changing to &signer:

#[test(account = @attacker, owner = @owner)]
fun test_view(account: signer, owner: signer) {
vault::set_secret(&owner, b"new secret");
// Attacker tries to use their own signer
let value = vault::get_secret(&account);
print(&value);
}

Test Result - Attacker fails:

Running Move unit tests
[ FAIL ] 0xa77ac4::Attacker::test_view
Test failures:
Failures in 0xa77ac4::Attacker:
┌── test_view ──────
│ error[E11001]: test failure
│ ┌─ /Users/dev/git/secu/audit_first_flight/2025-07-secret-vault/sources/secret_vault.move:32:5
│ │
│ 32 │ assert! (signer::address_of(caller) == @owner,NOT_OWNER);
│ │ ^^^^^^ Test was not expected to error, but it aborted with code 1

But the owner can still access their secret:

#[test(account = @attacker, owner = @owner)]
fun test_view(account: signer, owner: signer) {
vault::set_secret(&owner, b"new secret");
// Owner uses their own signer
let value = vault::get_secret(&owner);
print(&value);
}

Test Result - Owner succeeds:

Running Move unit tests
[debug] "new secret"
[ PASS ] 0xa77ac4::Attacker::test_view

This ensures only the actual owner (who can provide a valid &signer) can retrieve their secret, properly enforcing authentication.

Updates

Lead Judging Commences

bube Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of signer check in `get_secret`

Support

FAQs

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

Give us feedback!