Secret Vault

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

On-chain plaintext secrets resulting in total loss of confidentiality


Description

The contract stores the user’s secret directly on-chain in plaintext. On Aptos, all global state is publicly accessible: full nodes, indexers, or anyone running an RPC can read resources without authentication. This means the stored “secret” is never actually secret, regardless of access controls in the module. Once published, the data becomes permanently visible and cannot be hidden or deleted.

Affected Code

sruct Vault has key {
secret: String
}
public entry fun set_secret(caller: &signer, secret: vector<u8>) {
let secret_vault = Vault { secret: string::utf8(secret) };
move_to(caller, secret_vault); // stores plaintext in global storage
}

Root Cause

  • Design issue: Vault.secret: String is stored in a key resource under the user’s account. By definition, resources in global storage are public.

  • Even if get_secret were perfectly restricted, the plaintext remains retrievable directly from node APIs or indexers.


Risk

Likelihood:

  • Every time a user calls set_secret, the secret is committed to Aptos global storage as plaintext. This is immediately included in the public ledger and becomes accessible to all full nodes and indexers.

  • Any third party running a node, RPC endpoint, or indexer routinely synchronizes global storage and can query the Vault resource without restrictions. This makes retrieval of the secret a standard, guaranteed operation whenever someone inspects account state.

Impact:

  • Confidentiality is completely broken: Any actor can obtain the secret through off-chain queries, without interacting with the smart contract.

  • Exposure is permanent: even deleting or overwriting later cannot erase the historical leak.


Attack Scenarios

  1. Direct RPC query

    GET /v1/accounts/<owner_address>/resource/<module_address>::vault::Vault

    returns the secret field in plaintext.

  2. Indexer query
    Any indexer service can fetch and display the secret for arbitrary accounts.

  3. Historical chain data
    Even if the resource is later cleared or updated, the original plaintext is preserved in past ledgers.


Proof of Concept

After calling set_secret, the following resource is stored:

struct Vault has key {
secret: String
}

Using the Aptos CLI or REST API, anyone can read this value:

aptos account resource <owner_address> <module_address>::vault::Vault

This command outputs the secret directly, no authentication required.


Recommended Mitigations

  • If confidentiality is required:

    • Never store secrets as plaintext on-chain.

    • Encrypt client-side before submission (e.g., AES-GCM / XChaCha20-Poly1305).

    • Store only ciphertext and metadata (nonce, hash).

    • Provide only the ciphertext via get_secret, users must decrypt off-chain with their private key.

  • If confidentiality is not required:

    • Explicitly document that secrets are public.

    • Rename functions/structs to avoid misleading users into thinking secrecy is provided.

Updates

Lead Judging Commences

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