multiversx-dapp-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MultiversX dApp Auditor

MultiversX dApp 审计指南

Audit the frontend components of MultiversX applications built with
@multiversx/sdk-dapp
. This skill focuses on client-side security, transaction construction, and wallet integration vulnerabilities.
审计基于
@multiversx/sdk-dapp
构建的MultiversX应用前端组件。本指南聚焦客户端安全、交易构造以及钱包集成中的漏洞排查。

When to Use

适用场景

  • Reviewing React/TypeScript dApp code
  • Auditing wallet connection implementations
  • Assessing transaction signing security
  • Checking for XSS and data exposure vulnerabilities
  • Validating frontend-backend security boundaries
  • 审查React/TypeScript dApp代码
  • 审计钱包连接实现逻辑
  • 评估交易签名安全性
  • 检查XSS与数据泄露漏洞
  • 验证前端与后端的安全边界

1. Transaction Construction Security

1. 交易构造安全性

The Threat Model

威胁模型

The frontend constructs transaction payloads that users sign. Vulnerabilities here can trick users into signing malicious transactions.
前端负责构造用户需要签名的交易载荷。此处的漏洞可能导致用户被诱骗签署恶意交易。

Payload Manipulation

载荷操纵风险

typescript
// VULNERABLE: User input directly in transaction data
const sendTransaction = async (userInput: string) => {
    const tx = new Transaction({
        receiver: Address.newFromBech32(recipientAddress),
        data: Buffer.from(userInput),  // Attacker controls data!
        // ...
    });
    await signAndSend(tx);
};

// SECURE: Validate and sanitize all inputs
const sendTransaction = async (functionName: string, args: string[]) => {
    // Whitelist allowed functions
    const allowedFunctions = ['stake', 'unstake', 'claim'];
    if (!allowedFunctions.includes(functionName)) {
        throw new Error('Invalid function');
    }

    // Validate arguments
    const sanitizedArgs = args.map(arg => validateArgument(arg));

    const tx = new Transaction({
        receiver: contractAddress,
        data: Buffer.from(`${functionName}@${sanitizedArgs.join('@')}`),
        // ...
    });
    await signAndSend(tx);
};
typescript
// VULNERABLE: User input directly in transaction data
const sendTransaction = async (userInput: string) => {
    const tx = new Transaction({
        receiver: Address.newFromBech32(recipientAddress),
        data: Buffer.from(userInput),  // Attacker controls data!
        // ...
    });
    await signAndSend(tx);
};

// SECURE: Validate and sanitize all inputs
const sendTransaction = async (functionName: string, args: string[]) => {
    // Whitelist allowed functions
    const allowedFunctions = ['stake', 'unstake', 'claim'];
    if (!allowedFunctions.includes(functionName)) {
        throw new Error('Invalid function');
    }

    // Validate arguments
    const sanitizedArgs = args.map(arg => validateArgument(arg));

    const tx = new Transaction({
        receiver: contractAddress,
        data: Buffer.from(`${functionName}@${sanitizedArgs.join('@')}`),
        // ...
    });
    await signAndSend(tx);
};

Critical Checks

关键检查项

CheckRiskMitigation
Receiver address validationFunds sent to wrong addressValidate against known addresses
Data payload constructionMalicious function callsWhitelist allowed operations
Amount validationIncorrect value transfersConfirm amounts with user
Gas limit manipulationTransaction failuresUse appropriate limits
检查项风险缓解措施
接收方地址验证资金转入错误地址与已知地址进行校验
数据载荷构造恶意函数调用白名单允许的操作
金额验证转账金额错误与用户确认转账金额
Gas限制操纵交易失败使用合适的限制值

2. Signing Security

2. 签名安全性

Blind Signing Risks

盲签风险

Users may sign transactions without understanding the content:
typescript
// DANGEROUS: Signing opaque data
const signMessage = async (data: string) => {
    const hash = keccak256(data);
    return await wallet.signMessage(hash);  // User sees only hash!
};

// SECURE: Show clear message to user
const signMessage = async (message: string) => {
    // Display message to user before signing
    const confirmed = await showConfirmationDialog({
        title: 'Sign Message',
        content: `You are signing: "${message}"`,
        warning: 'Only sign messages you understand'
    });

    if (!confirmed) throw new Error('User rejected');
    return await wallet.signMessage(message);
};
用户可能在未理解交易内容的情况下签署交易:
typescript
// DANGEROUS: Signing opaque data
const signMessage = async (data: string) => {
    const hash = keccak256(data);
    return await wallet.signMessage(hash);  // User sees only hash!
};

// SECURE: Show clear message to user
const signMessage = async (message: string) => {
    // Display message to user before signing
    const confirmed = await showConfirmationDialog({
        title: 'Sign Message',
        content: `You are signing: "${message}"`,
        warning: 'Only sign messages you understand'
    });

    if (!confirmed) throw new Error('User rejected');
    return await wallet.signMessage(message);
};

Transaction Preview Requirements

交易预览要求

Before signing, users should see:
  • Recipient address (with verification if known)
  • Amount being transferred
  • Token type (EGLD, ESDT, NFT)
  • Function being called (if smart contract interaction)
  • Gas cost estimate
签署前,用户应能查看以下信息:
  • 接收方地址(若为已知地址需附带验证标识)
  • 转账金额
  • 代币类型(EGLD、ESDT、NFT)
  • 调用的智能合约函数(若涉及合约交互)
  • Gas成本预估

3. Sensitive Data Handling

3. 敏感数据处理

Private Key Security

私钥安全

typescript
// CRITICAL VULNERABILITY: Never do this
localStorage.setItem('privateKey', wallet.privateKey);
localStorage.setItem('mnemonic', wallet.mnemonic);
sessionStorage.setItem('seed', wallet.seed);

// Check for these patterns in code review:
// - Any storage of private keys, mnemonics, or seeds
// - Logging of sensitive data
// - Sending sensitive data to APIs
typescript
// CRITICAL VULNERABILITY: Never do this
localStorage.setItem('privateKey', wallet.privateKey);
localStorage.setItem('mnemonic', wallet.mnemonic);
sessionStorage.setItem('seed', wallet.seed);

// Check for these patterns in code review:
// - Any storage of private keys, mnemonics, or seeds
// - Logging of sensitive data
// - Sending sensitive data to APIs

Secure Patterns

安全实践

typescript
// CORRECT: Use sdk-dapp's secure session management
import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp';

initApp({
    storage: {
        getStorageCallback: () => sessionStorage  // Session only, not persistent
    },
    dAppConfig: {
        nativeAuth: {
            expirySeconds: 3600  // Short-lived tokens
        }
    }
});
typescript
// CORRECT: Use sdk-dapp's secure session management
import { initApp } from '@multiversx/sdk-dapp/out/methods/initApp/initApp';

initApp({
    storage: {
        getStorageCallback: () => sessionStorage  // Session only, not persistent
    },
    dAppConfig: {
        nativeAuth: {
            expirySeconds: 3600  // Short-lived tokens
        }
    }
});

Access Token Security

访问令牌安全

typescript
// VULNERABLE: Token exposed in URL
window.location.href = `https://api.example.com?accessToken=${token}`;

// VULNERABLE: Token in console
console.log('Auth token:', accessToken);

// SECURE: Token in Authorization header, never logged
fetch(apiUrl, {
    headers: {
        'Authorization': `Bearer ${accessToken}`
    }
});
typescript
// VULNERABLE: Token exposed in URL
window.location.href = `https://api.example.com?accessToken=${token}`;

// VULNERABLE: Token in console
console.log('Auth token:', accessToken);

// SECURE: Token in Authorization header, never logged
fetch(apiUrl, {
    headers: {
        'Authorization': `Bearer ${accessToken}`
    }
});

4. XSS Prevention

4. XSS防护

User-Generated Content

用户生成内容

typescript
// VULNERABLE: Direct HTML injection
const UserProfile = ({ bio }: { bio: string }) => {
    return <div dangerouslySetInnerHTML={{ __html: bio }} />;  // XSS!
};

// SECURE: React's default escaping
const UserProfile = ({ bio }: { bio: string }) => {
    return <div>{bio}</div>;  // Automatically escaped
};

// If HTML is necessary, sanitize first
import DOMPurify from 'dompurify';

const UserProfile = ({ bio }: { bio: string }) => {
    const sanitized = DOMPurify.sanitize(bio);
    return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
};
typescript
// VULNERABLE: Direct HTML injection
const UserProfile = ({ bio }: { bio: string }) => {
    return <div dangerouslySetInnerHTML={{ __html: bio }} />;  // XSS!
};

// SECURE: React's default escaping
const UserProfile = ({ bio }: { bio: string }) => {
    return <div>{bio}</div>;  // Automatically escaped
};

// If HTML is necessary, sanitize first
import DOMPurify from 'dompurify';

const UserProfile = ({ bio }: { bio: string }) => {
    const sanitized = DOMPurify.sanitize(bio);
    return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
};

URL Handling

URL处理

typescript
// VULNERABLE: Unvalidated redirect
const handleCallback = () => {
    const returnUrl = new URLSearchParams(window.location.search).get('returnUrl');
    window.location.href = returnUrl!;  // Open redirect!
};

// SECURE: Validate redirect URL
const handleCallback = () => {
    const returnUrl = new URLSearchParams(window.location.search).get('returnUrl');
    const allowed = ['/', '/dashboard', '/profile'];
    if (allowed.includes(returnUrl || '')) {
        window.location.href = returnUrl!;
    } else {
        window.location.href = '/';  // Default safe redirect
    }
};
typescript
// VULNERABLE: Unvalidated redirect
const handleCallback = () => {
    const returnUrl = new URLSearchParams(window.location.search).get('returnUrl');
    window.location.href = returnUrl!;  // Open redirect!
};

// SECURE: Validate redirect URL
const handleCallback = () => {
    const returnUrl = new URLSearchParams(window.location.search).get('returnUrl');
    const allowed = ['/', '/dashboard', '/profile'];
    if (allowed.includes(returnUrl || '')) {
        window.location.href = returnUrl!;
    } else {
        window.location.href = '/';  // Default safe redirect
    }
};

5. API Communication Security

5. API通信安全

HTTPS Enforcement

HTTPS强制要求

typescript
// VULNERABLE: HTTP connection
const API_URL = 'http://api.example.com';  // Insecure!

// SECURE: Always HTTPS
const API_URL = 'https://api.example.com';

// Verify in code: all API URLs must use https://
typescript
// VULNERABLE: HTTP connection
const API_URL = 'http://api.example.com';  // Insecure!

// SECURE: Always HTTPS
const API_URL = 'https://api.example.com';

// Verify in code: all API URLs must use https://

Request/Response Validation

请求/响应验证

typescript
// VULNERABLE: Trusting API response blindly
const balance = await fetch('/api/balance').then(r => r.json());
displayBalance(balance.amount);  // What if API is compromised?

// SECURE: Validate response structure
const response = await fetch('/api/balance').then(r => r.json());
if (typeof response.amount !== 'string' || !/^\d+$/.test(response.amount)) {
    throw new Error('Invalid balance response');
}
displayBalance(response.amount);
typescript
// VULNERABLE: Trusting API response blindly
const balance = await fetch('/api/balance').then(r => r.json());
displayBalance(balance.amount);  // What if API is compromised?

// SECURE: Validate response structure
const response = await fetch('/api/balance').then(r => r.json());
if (typeof response.amount !== 'string' || !/^\d+$/.test(response.amount)) {
    throw new Error('Invalid balance response');
}
displayBalance(response.amount);

6. Audit Tools and Techniques

6. 审计工具与技术

Network Traffic Analysis

网络流量分析

bash
undefined
bash
undefined

Use browser DevTools Network tab to inspect:

Use browser DevTools Network tab to inspect:

- All API requests and responses

- All API requests and responses

- Transaction data being sent

- Transaction data being sent

- Headers (especially Authorization)

- Headers (especially Authorization)

- WebSocket messages

- WebSocket messages

Or use proxy tools:

Or use proxy tools:

- Burp Suite

- Burp Suite

- mitmproxy

- mitmproxy

- Charles Proxy

- Charles Proxy

undefined
undefined

Code Review Patterns

代码审查模式

bash
undefined
bash
undefined

Search for dangerous patterns

Search for dangerous patterns

grep -r "localStorage" src/ grep -r "dangerouslySetInnerHTML" src/ grep -r "eval(" src/ grep -r "privateKey|mnemonic|seed" src/ grep -r "console.log" src/ # Check for sensitive data logging
undefined
grep -r "localStorage" src/ grep -r "dangerouslySetInnerHTML" src/ grep -r "eval(" src/ grep -r "privateKey|mnemonic|seed" src/ grep -r "console.log" src/ # Check for sensitive data logging
undefined

Browser Security Headers Check

浏览器安全头检查

Content-Security-Policy: default-src 'self'; script-src 'self'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin

7. Authentication Flow Audit

7. 认证流程审计

Wallet Connection

钱包连接

typescript
// Verify wallet connection flow:
// 1. User initiates connection
// 2. Wallet provider prompts for approval
// 3. Only public address shared with dApp
// 4. No private data transmitted

// Check UnlockPanelManager usage
const unlockPanelManager = UnlockPanelManager.init({
    loginHandler: () => {
        // Verify: no sensitive data handling here
        navigate('/dashboard');
    }
});
typescript
// Verify wallet connection flow:
// 1. User initiates connection
// 2. Wallet provider prompts for approval
// 3. Only public address shared with dApp
// 4. No private data transmitted

// Check UnlockPanelManager usage
const unlockPanelManager = UnlockPanelManager.init({
    loginHandler: () => {
        // Verify: no sensitive data handling here
        navigate('/dashboard');
    }
});

Session Management

会话管理

typescript
// Verify session security:
// - Token expiration enforced
// - Logout clears all session data
// - No persistent sensitive storage

const handleLogout = async () => {
    const provider = getAccountProvider();
    await provider.logout();
    // Verify: session storage cleared
    sessionStorage.clear();
    navigate('/');
};
typescript
// Verify session security:
// - Token expiration enforced
// - Logout clears all session data
// - No persistent sensitive storage

const handleLogout = async () => {
    const provider = getAccountProvider();
    await provider.logout();
    // Verify: session storage cleared
    sessionStorage.clear();
    navigate('/');
};

8. Audit Checklist

8. 审计检查清单

Transaction Security

交易安全

  • All transaction data validated before signing
  • User shown clear transaction preview
  • Recipient addresses validated
  • Amount confirmations for large transfers
  • Gas limits appropriate for operation
  • 所有交易数据在签署前均经过验证
  • 向用户展示清晰的交易预览
  • 接收方地址已验证
  • 大额转账需用户确认金额
  • Gas限制与操作匹配

Data Security

数据安全

  • No private keys in any storage
  • No sensitive data in console logs
  • Access tokens not exposed in URLs
  • HTTPS enforced for all API calls
  • 任何存储介质中均无私人密钥
  • 控制台日志中无敏感数据
  • 访问令牌未暴露在URL中
  • 所有API调用均强制使用HTTPS

XSS Prevention

XSS防护

  • No
    dangerouslySetInnerHTML
    with user input
  • No
    eval()
    with user input
  • URL redirects validated
  • Content-Security-Policy headers present
  • 未使用
    dangerouslySetInnerHTML
    处理用户输入
  • 未使用
    eval()
    处理用户输入
  • URL重定向已验证
  • 已配置Content-Security-Policy安全头

Session Security

会话安全

  • Token expiration enforced
  • Logout clears all session data
  • Protected routes properly guarded
  • Re-authentication for sensitive operations
  • 令牌过期机制已生效
  • 登出操作清除所有会话数据
  • 受保护路由已正确守卫
  • 敏感操作需重新认证

9. Report Template

9. 报告模板

markdown
undefined
markdown
undefined

dApp Security Audit Report

dApp Security Audit Report

Scope

Scope

  • Application: [name]
  • Version: [version]
  • Files reviewed: [count]
  • Application: [name]
  • Version: [version]
  • Files reviewed: [count]

Findings

Findings

Critical

Critical

IDDescriptionLocationRecommendation
C1.........
IDDescriptionLocationRecommendation
C1.........

High

High

...
...

Medium

Medium

...
...

Informational

Informational

...
...

Recommendations Summary

Recommendations Summary

  1. [Priority recommendation]
  2. ...
  1. [Priority recommendation]
  2. ...

Conclusion

Conclusion

[Overall assessment]
undefined
[Overall assessment]
undefined