In the past two weeks, two separate campaigns reminded us that your wallet's biggest attack surface isn't the smart contract — it's the browser extension sitting three pixels away from your seed phrase.
ShieldGuard, dismantled by Okta Threat Intelligence in March 2026, posed as a wallet-protection extension while silently harvesting addresses, scraping full HTML from Binance, Coinbase, and MetaMask post-login, and executing remote code via a C2 server. It bypassed Chrome's Manifest V3 restrictions using a custom JavaScript interpreter inside a closed Shadow DOM.
Meanwhile, Google's Threat Intelligence Group disclosed Coruna (aka CryptoWaters), an iOS exploit kit that lures users to fake exchange frontends to extract wallet seed phrases — but its distribution chain relied heavily on malicious browser extensions as the initial pivot.
And just days ago, CVE-2026-3928 proved that Manifest V3's permission model still allows UI spoofing via crafted extensions — the exact vector that makes phishing overlays on wallet confirmation dialogs possible.
If you're building, auditing, or just using crypto browser extensions, here's the toolkit and methodology security researchers are using right now.
The Threat Model: What Malicious Extensions Actually Do
Before diving into tools, understand the three attack patterns every crypto extension audit should cover:
Pattern 1: Silent Data Exfiltration
ShieldGuard's playbook — capture DOM content from exchange/wallet pages after authentication, extract addresses from visited URLs, and beacon data to a C2.
Pattern 2: Permission Escalation via Manifest Manipulation
The "buy-and-infect" model: acquire a legitimate extension, push an update that expands manifest.json permissions, and use staged payload delivery (activate malicious behavior only after N uses or N days).
Pattern 3: UI Overlay Attacks
Exploit CVE-2026-3928-class vulnerabilities to overlay fake confirmation dialogs on top of real wallet prompts. The user thinks they're approving a $50 swap; they're actually signing an unlimited token approval.
The Audit Toolkit
1. CRXcavator (Static Analysis)
What: Automated Chrome extension risk scoring. Analyzes permissions, content security policy, third-party library usage, and known-vulnerable dependencies.
Why it matters for crypto: CRXcavator flags extensions requesting tabs, webRequest, <all_urls>, or activeTab — the permission set that enables cross-site data harvesting. ShieldGuard would have scored critical on permissions alone.
Usage:
# Upload .crx file or enter Chrome Web Store ID
# https://crxcavator.io/
# Key flags for crypto audit:
# - permissions: tabs, webRequest, <all_urls>, storage
# - CSP: unsafe-eval, unsafe-inline (code injection vectors)
# - External calls: any domains not owned by the extension publisher
Red flags for crypto extensions:
-
webRequest+<all_urls>= can intercept every HTTP request including exchange API calls -
clipboardRead/clipboardWrite= address swap attacks - No CSP or
unsafe-evalin CSP = remote code execution possible
2. Extension Source Viewer + Manual Manifest Audit
Don't trust the store listing. Every audit starts with reading the actual manifest.json:
// ShieldGuard's manifest requested these "security" permissions:
{
"permissions": [
"activeTab",
"storage",
"tabs",
"scripting"
],
"host_permissions": [
"https://*.binance.com/*",
"https://*.coinbase.com/*",
"https://*.metamask.io/*",
"https://*.opensea.io/*",
"https://*.uniswap.org/*"
],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}]
}
Audit checklist:
- [ ] Does
host_permissionsscope match the extension's stated purpose? - [ ] Are
content_scriptsinjected into<all_urls>or only necessary domains? - [ ] Does the extension use
chrome.storage.sessionfor secrets (better) orchrome.storage.local(worse — persists unencrypted)? - [ ] Is there a
background.service_workerthat makes external network calls? - [ ] Does
web_accessible_resourcesexpose internal pages to web content?
3. Semgrep Custom Rules for Extension Code
Static analysis on the unpacked extension source catches patterns that CRXcavator misses:
# semgrep-rules/crypto-extension-audit.yaml
rules:
- id: extension-exfiltrates-dom
message: Extension captures full DOM content - potential data exfiltration
severity: ERROR
languages: [javascript, typescript]
patterns:
- pattern-either:
- pattern: document.documentElement.outerHTML
- pattern: document.body.innerHTML
- pattern: document.documentElement.innerHTML
- pattern-inside: |
fetch($URL, ...)
- id: extension-reads-crypto-addresses
message: Extension scans for cryptocurrency address patterns
severity: WARNING
languages: [javascript, typescript]
pattern-either:
- pattern: |
/0x[a-fA-F0-9]{40}/
- pattern: |
/[1-9A-HJ-NP-Za-km-z]{32,44}/
- pattern: |
/(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,62}/
- id: extension-shadow-dom-hiding
message: >
Extension creates closed Shadow DOM - may hide malicious UI overlays
(ShieldGuard used this to evade detection)
severity: WARNING
languages: [javascript, typescript]
pattern: $EL.attachShadow({mode: "closed"})
- id: extension-eval-bypass
message: Extension uses indirect eval or Function constructor (MV3 bypass)
severity: ERROR
languages: [javascript, typescript]
pattern-either:
- pattern: new Function(...)
- pattern: setTimeout($STR, ...)
- pattern: setInterval($STR, ...)
- pattern: $X["eval"](...)
- id: extension-clipboard-hijack
message: Extension writes to clipboard - potential address swap attack
severity: WARNING
languages: [javascript, typescript]
pattern-either:
- pattern: navigator.clipboard.writeText(...)
- pattern: document.execCommand("copy")
Run against unpacked extension:
# Unpack the .crx
unzip -d extension_src wallet-guard-v2.1.crx
# Run audit rules
semgrep --config semgrep-rules/crypto-extension-audit.yaml extension_src/
# Also run the default javascript security ruleset
semgrep --config "p/javascript" extension_src/
4. Dynamic Analysis with Chrome DevTools Protocol
Static analysis misses staged payloads — extensions that behave normally for days before activating. Use CDP for runtime monitoring:
# monitor_extension.py - Track extension network activity
import asyncio
from pyppeteer import launch
async def monitor_extension(extension_path, duration_minutes=60):
browser = await launch(
headless=False,
args=[
f'--load-extension={extension_path}',
'--disable-extensions-except=' + extension_path,
]
)
page = await browser.newPage()
# Intercept all network requests
suspicious_requests = []
async def on_request(request):
url = request.url
# Flag requests to non-extension, non-target domains
if not url.startswith('chrome-extension://'):
suspicious_requests.append({
'url': url,
'method': request.method,
'headers': request.headers,
'postData': request.postData if request.method == 'POST' else None,
'timestamp': asyncio.get_event_loop().time()
})
print(f"[NET] {request.method} {url}")
page.on('request', on_request)
# Navigate to crypto sites the extension claims to protect
test_sites = [
'https://app.uniswap.org',
'https://www.binance.com',
'https://metamask.io',
]
for site in test_sites:
await page.goto(site)
await asyncio.sleep(30) # Observe behavior per site
# Check: did the extension make requests to unexpected domains?
unique_domains = set()
for req in suspicious_requests:
from urllib.parse import urlparse
domain = urlparse(req['url']).netloc
unique_domains.add(domain)
print(f"\n[AUDIT] Extension contacted {len(unique_domains)} external domains:")
for d in sorted(unique_domains):
print(f" - {d}")
await browser.close()
asyncio.run(monitor_extension('./extension_src', duration_minutes=30))
5. Wallet-Specific: Transaction Simulation Interception Test
The most dangerous extension attack is invisible transaction modification. Test for it:
// inject_test.js - Verify extension doesn't modify wallet transactions
// Run in browser console while extension is active
// Step 1: Create a known-good transaction
const testTx = {
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18',
value: '0x2386F26FC10000', // 0.01 ETH
data: '0x',
chainId: 1
};
// Step 2: Hook window.ethereum.request to capture what actually gets sent
const originalRequest = window.ethereum.request.bind(window.ethereum);
window.ethereum.request = async function(args) {
if (args.method === 'eth_sendTransaction') {
console.log('[AUDIT] Transaction params sent to wallet:');
console.log(JSON.stringify(args.params[0], null, 2));
// Compare with original
const sent = args.params[0];
if (sent.to.toLowerCase() !== testTx.to.toLowerCase()) {
console.error('\ud83d\udea8 RECIPIENT ADDRESS MODIFIED BY EXTENSION!');
console.error(`Expected: ${testTx.to}`);
console.error(`Got: ${sent.to}`);
return; // Block the transaction
}
if (sent.value !== testTx.value) {
console.error('\ud83d\udea8 VALUE MODIFIED BY EXTENSION!');
}
if (sent.data !== testTx.data) {
console.error('\ud83d\udea8 CALLDATA INJECTED BY EXTENSION!');
}
}
return originalRequest(args);
};
The Solana Angle: Phantom and Extension Trust
Solana wallet extensions face the same threats. Phantom, Backpack, and Solflare all run as browser extensions with broad permissions. The Solana-specific twist:
// What a malicious extension could do via injected JavaScript:
// 1. Hook window.solana.signTransaction
// 2. Modify the transaction before signing
// 3. Add instructions that drain token accounts
// Defense: Anchor programs should validate all signers
#[derive(Accounts)]
pub struct SecureTransfer<'info> {
#[account(mut)]
pub from: Account<'info, TokenAccount>,
#[account(mut)]
pub to: Account<'info, TokenAccount>,
// Critical: authority must be the actual token owner
#[account(
constraint = authority.key() == from.owner
@ ErrorCode::UnauthorizedSigner
)]
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
But on-chain validation can't save you if the extension modifies the entire transaction before it reaches the wallet for signing. The user sees a clean preview; the signed payload is different.
Audit tool for Solana extensions: Use @solana/web3.js transaction deserialization to log every transaction the extension creates:
// Solana extension audit hook
const { Transaction } = require('@solana/web3.js');
// Intercept signTransaction
const originalSign = window.solana.signTransaction;
window.solana.signTransaction = async function(tx) {
// Deserialize and log all instructions
const decoded = Transaction.from(tx.serialize());
console.log('[AUDIT] Instructions in transaction:');
decoded.instructions.forEach((ix, i) => {
console.log(` [${i}] Program: ${ix.programId.toBase58()}`);
console.log(` Keys: ${ix.keys.map(k => k.pubkey.toBase58()).join(', ')}`);
console.log(` Data: ${ix.data.toString('hex')}`);
});
// Check for unexpected programs (token drains, authority changes)
const suspiciousPrograms = decoded.instructions
.filter(ix => !KNOWN_SAFE_PROGRAMS.includes(ix.programId.toBase58()));
if (suspiciousPrograms.length > 0) {
console.error('\ud83d\udea8 UNKNOWN PROGRAM IN TRANSACTION');
// Alert user, block signing
}
return originalSign.call(this, tx);
};
Building a Continuous Audit Pipeline
One-time audits aren't enough. Extensions auto-update. Here's a CI pipeline that catches malicious updates:
# .github/workflows/extension-audit.yml
name: Crypto Extension Security Audit
on:
schedule:
- cron: '0 6 * * 1' # Weekly Monday 6 AM UTC
workflow_dispatch:
jobs:
audit-extensions:
runs-on: ubuntu-latest
steps:
- name: Download latest extension versions
run: |
# List of crypto extensions to monitor
EXTENSIONS=(
"nkbihfbeogaeaoehlefnkodbefgpgknn" # MetaMask
"bfnaelmomeimhlpmgjnjophhpkkoljpa" # Phantom
"acmacodkjbdgmoleebolmdjonilkdbch" # Rabby
)
for ext in "${EXTENSIONS[@]}"; do
python3 scripts/download_crx.py "$ext" --output "extensions/$ext.crx"
unzip -o -d "extensions/$ext" "extensions/$ext.crx"
done
- name: Diff against previous version
run: |
for ext_dir in extensions/*/; do
ext_id=$(basename "$ext_dir")
if [ -d "baseline/$ext_id" ]; then
echo "=== Diffing $ext_id ==="
diff -rq "baseline/$ext_id" "$ext_dir" || true
# Flag new permissions
python3 scripts/diff_manifest.py \
"baseline/$ext_id/manifest.json" \
"$ext_dir/manifest.json"
fi
done
- name: Run Semgrep audit
run: |
semgrep --config semgrep-rules/crypto-extension-audit.yaml \
extensions/ --json > audit-results.json
- name: Check for new external domains
run: |
python3 scripts/extract_domains.py extensions/ > current_domains.txt
if [ -f baseline_domains.txt ]; then
comm -13 baseline_domains.txt current_domains.txt > new_domains.txt
if [ -s new_domains.txt ]; then
echo "\ud83d\udea8 NEW EXTERNAL DOMAINS DETECTED:"
cat new_domains.txt
fi
fi
- name: Update baseline
run: |
rm -rf baseline/
cp -r extensions/ baseline/
cp current_domains.txt baseline_domains.txt
Lessons from ShieldGuard's Takedown
Okta's analysis revealed ShieldGuard survived for months because:
- Staged activation — No malicious behavior for the first 50 page visits
- Closed Shadow DOM — DevTools couldn't inspect the injected UI
- Custom JS interpreter — Bypassed MV3's code execution restrictions without triggering static analysis
- Referral incentives — Victims recruited more victims via "airdrop" rewards
The defense playbook:
| Attack Technique | Detection Tool | What to Look For |
|---|---|---|
| Staged activation | Dynamic analysis (CDP) | Behavioral changes after N uses or N days |
| Closed Shadow DOM | Semgrep rule extension-shadow-dom-hiding
|
attachShadow({mode: "closed"}) |
| Custom JS interpreter | Manual code review |
eval-like constructs, custom bytecode VMs |
| Airdrop lure | Social engineering review | Extension marketing promising free tokens |
Practical Recommendations
For users:
- Install only open-source wallet extensions with published audit reports
- Use hardware wallets for signing — the extension becomes a read-only interface
- Review extension permissions after every update (check
chrome://extensions) - Run one wallet extension per browser profile — isolation limits blast radius
For developers building wallet extensions:
- Publish Semgrep configs with your source code so the community can verify builds
- Use
chrome.storage.session(encrypted, ephemeral) for secrets — neverchrome.storage.local - Implement CSP without
unsafe-evalorunsafe-inline - Pin all third-party dependencies with integrity hashes
For auditors:
- Add the Semgrep rules from this article to your extension audit toolkit
- Run dynamic analysis for at least 7 days to catch staged payloads
- Diff every extension update against baseline — permission expansion is the #1 signal
- Test transaction interception on both EVM and Solana wallet hooks
The Immunefi 2026 report shows $4.67 billion drained in the last two years, with 84% of hacked tokens never recovering. But not all of that was smart contract exploits. An increasing share came through the browser — the one attack surface most DeFi security researchers still ignore.
ShieldGuard and Coruna proved the playbook works. The tools to detect it exist. The question is whether we use them before the next extension in the Chrome Web Store starts draining wallets.
Part of the DeFi Security Research series. Previously: Calldata Injection | Wallet Drainer-as-a-Service
Top comments (0)