Secret Vault on Aptos

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

Improper Access Control in get_secret Enables Unauthorized Disclosure of Stored Secrets

Description

The contract secret_vault::vault attempts to provide a private vault where only the account owner can store and later retrieve their secret. However, the get_secret function is declared as a #[view] and takes an arbitrary addressparameter. Any user can call this function with the @owner address, bypass the intended ownership restriction, and directly read the stored secret.

Additionally, because the secret is stored as plaintext in an on-chain resource, even if access control were correct, the information is still publicly visible to full nodes and indexers. This completely breaks the confidentiality guarantee of the module.

Affected Code

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

Root Cause

  • The function relies on an address argument rather than authenticating with a &signer.

  • The assert!(caller == @owner, NOT_OWNER) check is ineffective since any user can simply pass @owner.

  • Secrets are stored unencrypted on-chain, so confidentiality is fundamentally unattainable.

Proof of Concept (PoC)

The following demonstrates how a malicious actor can trivially bypass the intended access restriction:

  1. Setup (Owner stores a secret): The legitimate user calls set_secret to save their secret in the on-chain Vault.

  2. Attack (Malicious actor retrieves it): An attacker simply provides the owner’s public address as the callerargument to get_secret. Because there is no binding to the actual signer, the function will return the secret.

  3. Alternative (Direct state inspection): Even without calling the function, anyone can fetch the resource data directly from the blockchain since it is stored in plaintext.

// 1. Owner sets secret
vault::set_secret(owner_signer, b"super_secret_password");
// 2. Attacker retrieves it by passing owner's address
let stolen = vault::get_secret(@owner);
// stolen == "super_secret_password"
# 3. Direct state read (no Move function call required)
aptos account resource --account <owner_address> --resource-type secret_vault::vault::Vault
# Output shows the secret directly:
{
"secret": "super_secret_password"
}

Impact

  • High severity: Any account can read the supposedly private data.

  • No actual secrecy is provided. Both on-chain and off-chain actors can trivially extract the stored value.

  • Intended functionality (“only the owner can see their secret”) is broken.

Risk

  • Severity: Critical

  • Likelihood: High (trivial to exploit, requires no special privileges).

  • Impact: Total loss of confidentiality, potential exposure of sensitive user data.

Recommended Mitigation

  • Change get_secret to take &signer and enforce signer authentication, not an arbitrary address.

- public fun get_secret (caller: address):String acquires Vault{
- assert! (caller == @owner,NOT_OWNER);
+ public fun get_secret(caller: &signer): String acquires Vault {
- let vault = borrow_global<Vault >(@owner);
+ let vault = borrow_global<Vault>(signer::address_of(caller));
  • Do not rely on on-chain plaintext for secrets. Require client-side encryption before storing sensitive data. On-chain modules cannot guarantee confidentiality.

Updates

Lead Judging Commences

bube Lead Judge 14 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of signer check in `get_secret`

Anyone can see the `secret` on chain

Support

FAQs

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