Secret Vault on Aptos

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

On-Chain Data Privacy Illusion and Unauthorized Secret Access

# On-Chain Data Privacy Illusion and Unauthorized Secret Access

## Description

* The `Vault` struct stores sensitive information in the `secret` field, with the expectation that access control through `get_secret()` will keep this data private

* The fundamental issue is that all on-chain state is publicly readable by anyone with access to the blockchain, regardless of access control functions. The `secret` field is stored in plain text on-chain and can be accessed directly through state reads, completely bypassing the `get_secret()` authorization check

```java

struct Vault has key {

secret: String // @> This is stored on-chain in plain text, accessible to everyone

}

#[view]

public fun get_secret(caller: address): String acquires Vault {

assert!(caller == @owner, NOT_OWNER); // @> Access control is meaningless for on-chain data

let vault = borrow_global<Vault>(@owner);

vault.secret

}

```

## Risk

**Likelihood**:

* Any user with blockchain access can read the global state directly using `borrow_global<Vault>(@owner)`

* Block explorers and indexing services automatically expose all on-chain state data

* Full nodes maintain complete state history, making all historical secrets permanently accessible

* State dumps and debugging tools provide direct access to struct fields

**Impact**:

* Complete exposure of confidential information to unauthorized parties

* Violation of user privacy expectations and potential regulatory compliance issues

* Permanent data exposure (blockchain immutability means secrets cannot be retroactively hidden)

* Loss of trust in the application's security model

## Proof of Concept

* Add this test to `secret_vault.move` file and see the results

```java

#[test(owner = @0xcc)]

fun test_secret_leak(owner: &signer) acquires Vault {

use aptos_framework::account;

account::create_account_for_test(signer::address_of(owner));

// Owner sets a secret

let secret = b"I Love ...";

set_secret(owner, secret);

// Attacker reads the state directly (bypasses get_secret)

let vault = borrow_global<Vault>(@0xcc);

let stolen_secret = &vault.secret;

debug::print(stolen_secret); // Successfully prints the "secret"

}

```

## Recommended Mitigation

  • Here are recommended mitigations

```diff

- struct Vault has key {

- secret: String

- }

+ // Option 1: Remove on-chain storage entirely

+ // Store sensitive data off-chain with on-chain references only

+

+ struct Vault has key {

+ secret_hash: vector<u8>, // Store hash for verification

+ // other non-sensitive metadata

+ }

+

+ // Option 2: Use commitment scheme

+ public entry fun commit_secret(caller: &signer, commitment: vector<u8>) {

+ let secret_vault = Vault { secret_hash: commitment };

+ move_to(caller, secret_vault);

+ }

+

+ public fun verify_secret(caller: address, secret: vector<u8>): bool acquires Vault {

+ let vault = borrow_global<Vault>(caller);

+ // Verify against commitment/hash

+ aptos_hash::sha3_256(secret) == vault.secret_hash

+ }

```

Updates

Lead Judging Commences

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

Anyone can see the `secret` on chain

Support

FAQs

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