Skip to content

Repository files navigation

crypto-safe-container

A hardened Docker environment that isolates your crypto wallets behind a whitelist-only egress proxy and a LUKS-encrypted disk.
Run MetaMask, Rabby & Phantom in a locked-down browser — even if it's compromised, stolen data can't phone home, and on-disk files are AES-encrypted.

Demo: exfiltration blocked

CI License Stars

The ProblemHow It Protects YouArchitectureQuick StartEncryptionHardware WalletsThreat Model


The Problem

Your crypto wallets and your daily browsing/coding share the same computer. That means:

  1. A bad npm install can read your MetaMask files and steal your keys
  2. A malicious website can drop malware that drains every wallet on your machine
  3. Even if malware gets your wallet data, it can silently send it to a hacker's server — and you'd never know until the funds are gone

This is not hypothetical. North Korean hackers (Lazarus Group) have stolen billions this way — posing as recruiters, sending devs a repo to clone, and npm install does the rest.

How This Project Protects You

crypto-safe-container puts your wallets inside a locked box (Docker container) with three simple rules:

Rule 1: Your wallets can't see your computer

The container is isolated. Malware inside it cannot read your host files, other browsers, SSH keys, or anything outside the box. Your wallets live in a separate world.

Rule 2: The box can only talk to sites you approve

Even if something bad gets inside the box, it cannot send your data to a hacker's server. All internet traffic goes through a proxy that only allows whitelisted domains — Ethereum RPCs, Uniswap, Aave, block explorers, etc. Everything else is blocked.

Normal setup:        Malware steals keys → sends to evil.com → funds gone

With this project:   Malware steals keys → tries evil.com → BLOCKED by proxy
                                          → tries any site → BLOCKED
                                          → data goes nowhere

Rule 3: The box is encrypted on disk

The vault profile (wallet data, browser history, cookies) lives inside a LUKS-encrypted disk image at ~/.crypto-vault/vault.luks. When the vault isn't running, the file on disk is meaningless ciphertext — even the host can't read it without the passphrase. AES-256-XTS, Argon2id KDF; same crypto your laptop's full-disk encryption uses. See Encrypted-at-rest mode for details.

Vault not running:   `vault.luks` on disk → encrypted bytes → useless
Vault running:       `./vault.sh up` → passphrase → decrypted in memory only

What's whitelisted by default?

Only what you need for DeFi — nothing more:

  • RPC providers (Infura, Alchemy, Ankr)
  • DeFi protocols (Uniswap, Aave, Curve, Lido, Jupiter, Raydium)
  • Block explorers (Etherscan, Solscan)
  • Wallet updates (MetaMask, Phantom, Rabby)
  • Hardware wallet bridges (Ledger, Trezor)

You control the list. Add or remove domains anytime in squid/whitelist.txt.

Real-world attacks this stops

Attack What happens normally With crypto-safe-container
Malicious npm install Reads your Chrome profile, steals wallet keys, sends to attacker Container has no access to your host Chrome profile
Fake DApp drains wallet You sign a bad transaction, malware exfiltrates your seed Transaction goes through (your call), but seed can't be exfiltrated to non-whitelisted servers
Clipboard hijacker Replaces your copied wallet address with attacker's Malware is inside the container — can't touch your host clipboard
VS Code tasks.json exploit Opens a folder, auto-runs malware, steals everything Your wallets aren't on the same machine (they're in the container)
Laptop stolen / disk imaged Wallet files are on disk as plaintext, brute-forcing only needs to defeat the wallet password LUKS image is encrypted ciphertext; attacker has to crack LUKS and the wallet password — two independent Argon2id/scrypt KDFs
Backup of your home dir leaks (OneDrive, Time Machine) Your wallet's encrypted seed blob is in the backup, ready for offline brute-force Only the LUKS ciphertext is in the backup — useless without the passphrase

A hardware wallet (Ledger/Trezor) is still the strongest protection. This project is the next best thing for software wallets, and works great alongside hardware wallets too (USB passthrough supported).


Architecture

┌──────────────────────────────────────────────────────────┐
│                    Your Host Machine                      │
│                                                           │
│    X11 ─────── Chromium window (native, pixel-perfect)   │
│                       │                                   │
│    ┌──────────────────┼───────── vault-internal ───────┐  │
│    │                  │          (no internet)          │  │
│    │   ┌──────────────▼────────────────────────────┐   │  │
│    │   │        crypto-vault (172.30.0.10)          │   │  │
│    │   │                                            │   │  │
│    │   │   Chromium (X11 forwarding to host)        │   │  │
│    │   │   MetaMask / Phantom / Rabby               │   │  │
│    │   │   Foundry (forge / cast / anvil)            │   │  │
│    │   │   Node.js 20 + Hardhat + ethers.js          │   │  │
│    │   │                                            │   │  │
│    │   │   --cap-drop ALL                           │   │  │
│    │   │   no-new-privileges                        │   │  │
│    │   │   Profile dir → bind to LUKS mount ┐       │   │  │
│    │   └────────────────────────────────────┼──────┘   │  │
│    │                  │ proxy:3128          │           │  │
│    │   ┌──────────────▼────────────────┐    ▼           │  │
│    │   │  egress-proxy (172.30.0.2)    │  ┌──────────┐  │  │
│    │   │  Squid → whitelist.txt ONLY   │  │ LUKS2    │  │  │
│    │   └──────────────────┬────────────┘  │ image    │  │  │
│    │                      │                │ (AES-XTS)│  │  │
│    └──────────────────────┼────────────────┴──────────┘  │
│                           ▼                               │
│                       Internet                            │
└──────────────────────────────────────────────────────────┘

Key security properties:

  • No direct internet — The vault sits on an internal: true Docker network
  • Whitelist-only egress — All traffic passes through a Squid proxy; only ~50 explicitly-listed DeFi/Web3 domains are reachable
  • Encrypted at rest by default — Vault profile lives inside a LUKS2 disk image (AES-256-XTS, Argon2id KDF); ciphertext on disk when vault is locked
  • Hardened containercap_drop: [ALL], no-new-privileges, non-root user (UID 1000), PID/memory limits
  • Extension allowlist — Chrome policy blocks every extension except MetaMask, Phantom, Rabby (by ID)
  • Native X11 rendering — Chromium runs as a native window — no VNC blur, no compression
  • npm locked downignore-scripts=true globally prevents postinstall RCE inside the vault
  • Minimal basedebian:bookworm-slim + Debian's chromium (no Google binary blob), ~1.9 GB image
  • Hardware wallet ready — USB passthrough for Ledger/Trezor (keys never touch software)

Quick Start

Prerequisites: Docker Engine 20.10+ and Docker Compose v2

Linux / WSL2

X11 works out of the box (WSLg on WSL2, native X server on Linux).

git clone https://github.com/lywedo/crypto-safe-container.git
cd crypto-safe-container

./vault.sh up

On first run, ./vault.sh up walks you through three prompts:

  1. Install cryptsetup (one-time host dependency for LUKS) — auto-installs via apt/pacman/dnf with sudo.
  2. Create a LUKS passphrase — encrypts the vault profile on disk. Use ≥20 random characters from a password manager.
  3. Chromium opens with a welcome page linking to MetaMask, Phantom, and Rabby on the Chrome Web Store.

Subsequent runs just ask for the LUKS passphrase and launch. When you close Chromium, the disk auto-locks.

Optional: Add to your app launcher / Start Menu

make install-linux
# Search "Crypto Vault" in your app launcher

macOS

Requires XQuartz for X11 support. LUKS isn't available on macOS — use FileVault for full-disk encryption instead, and the vault falls back to a plain Docker volume (it prints a warning at startup).

brew install --cask xquartz
# Log out and back in after installing XQuartz

git clone https://github.com/lywedo/crypto-safe-container.git
cd crypto-safe-container

VAULT_INSECURE=1 ./vault.sh up    # explicit opt-out of LUKS

Optional: Add to Launchpad

make install-mac

Skipping encryption (CI / evaluation / demos)

VAULT_INSECURE=1 ./vault.sh up

This uses an unencrypted Docker volume (the pre-LUKS default). Useful for kicking the tyres, but don't store real wallets this way.


Wallet Extensions

On first launch, Chrome opens a welcome page with install links for:

Extension Extension ID
MetaMask — Ethereum wallet & DApp browser nkbihfbeogaeaoehlefnkodbefgpgknn
Phantom — Solana, Ethereum & multi-chain wallet bfnaelmomeimhlpmgjnjophhpkkoljpa
Rabby Wallet — Multi-chain wallet with security alerts acmacodkjbdgmoleebolmdjonilkdbch

Click "Add to Chrome" for each one. This only needs to be done once — extensions are stored in a persistent Docker volume (crypto-vault-chrome) and survive container restarts.

Only these three extensions can be installed

The Chrome managed policy in chrome-policies.json uses:

"ExtensionInstallBlocklist": ["*"],
"ExtensionInstallAllowlist": [
  "nkbihfbeogaeaoehlefnkodbefgpgknn",
  "bfnaelmomeimhlpmgjnjophhpkkoljpa",
  "acmacodkjbdgmoleebolmdjonilkdbch"
]

This blocks every Chrome Web Store extension by default, then re-permits only MetaMask, Phantom, and Rabby by ID. Attempting to install anything else — even from the Chrome Web Store — is blocked by Chrome itself, not the proxy.

This is a defence-in-depth measure: if a malicious site ever bypassed the proxy, it still couldn't sneak a rogue extension into your wallet browser.

Adding more extensions

To allow another extension, find its ID (visible in the Chrome Web Store URL: chromewebstore.google.com/detail/<name>/<ID>) and add it to ExtensionInstallAllowlist in chrome-policies.json, then rebuild:

docker compose --profile vault build crypto-vault
./vault.sh up

Consider the security tradeoff carefully — every extension you add is another supply-chain risk.


CLI Tools

Web3 CLI tools are available inside the vault and via the helper script:

# Open a shell inside the vault
./vault.sh shell

# Foundry commands (from host)
./vault.sh forge init my-project
./vault.sh forge build
./vault.sh forge test
./vault.sh cast balance vitalik.eth --rpc-url https://eth.llamarpc.com

# Hardhat (from host)
./vault.sh hardhat compile

# Or work interactively
./vault.sh shell
cd projects && npx hardhat init

All vault.sh Commands

Command Description
./vault.sh setup Check/install host deps (cryptsetup, e2fsprogs)
./vault.sh up Unlock disk, launch Chromium (X11). Runs setup first.
./vault.sh down Stop everything, lock the encrypted disk
./vault.sh shell Bash shell in an ephemeral vault container
./vault.sh forge <args> Run Foundry forge
./vault.sh cast <args> Run Foundry cast
./vault.sh hardhat <args> Run Hardhat
./vault.sh disk <subcmd> Manage encrypted disk: init / open / close / status / resize
./vault.sh logs [service] Follow container logs
./vault.sh whitelist Show allowed domains
./vault.sh reload-proxy Apply whitelist changes
./vault.sh smoketest Verify proxy invariants
./vault.sh backup Copy the encrypted disk image (or tarball if VAULT_INSECURE=1)
./vault.sh status Show services + encrypted disk state
./vault.sh nuke Delete ALL vault data (incl. the LUKS image)

Environment overrides: VAULT_INSECURE=1 (skip LUKS), VAULT_SIZE=20G (image size at first init), CRYPTO_DIR=/some/path (where the LUKS image lives — default ~/.crypto-vault).


Managing the Whitelist

The proxy blocks everything not in squid/whitelist.txt. To add a new protocol:

# Add a domain
echo ".newprotocol.xyz" >> squid/whitelist.txt

# Apply without restarting the vault
./vault.sh reload-proxy

Default whitelist includes: Ethereum/Solana RPCs, major DeFi (Uniswap, Aave, Jupiter, Raydium), block explorers, Chrome Web Store, wallet update servers, IPFS gateways, and hardware wallet bridges. See squid/whitelist.txt for the full list.


Verify the proxy works

After any change to squid/ (or just to sanity-check your install), run:

./scripts/smoketest.sh

This asserts the three properties the proxy must guarantee:

  1. Whitelisted domain reachableapi.etherscan.io returns 200 through the proxy
  2. Non-whitelisted blockedevil.com is denied by Squid with 403 Forbidden
  3. Direct internet unreachable — bypassing the proxy from vault-internal times out (network is truly isolated)

The same script runs in CI (see .github/workflows/ci.yml), so any whitelist change gets validated on every push.


Encrypted-at-rest mode (default)

The vault's Chromium profile lives inside a LUKS2-encrypted disk image at ~/.crypto-vault/vault.luks. The host sees one opaque file; opening it requires a passphrase you choose at first run.

  • AES-256-XTS block-level encryption (same as Linux full-disk encryption)
  • Argon2id KDF — modern, memory-hard, brute-force resistant
  • One single file on disk: easy to back up, move, or delete
  • Hard size cap (default 5 GB, configurable)
  • Authenticated by the kernel's dm-crypt — tampering is detected on read

First run

./vault.sh up
# > First-run setup: creating encrypted disk image...
# > [sudo password — for cryptsetup]
# > Enter LUKS passphrase: ********
# > Verify passphrase: ********
# > (creates ~/.crypto-vault/vault.luks, ~5 GB, formats LUKS + ext4)
# > Launching Chromium...

Use a ≥20 character random passphrase from a password manager. If you forget it AND lose your seed phrase, the wallet is unrecoverable. (Your seed phrase remains the real backup; the passphrase protects only the convenience copy of state.)

Want a different size? VAULT_SIZE=20G ./vault.sh up on first run.

Daily use

./vault.sh up
# > Enter LUKS passphrase: ********
# > Launching Chromium...
# (work, then close the browser)
# > Lock the encrypted disk: ./vault.sh disk close   (or it's already done by `down`)

The encrypted disk is opened before Chromium starts and closed when it exits. State is preserved across sessions inside the LUKS image.

Prerequisites

On first ./vault.sh up, the script runs a preflight check (scripts/preflight.sh) and prompts you to install any missing host packages — cryptsetup and e2fsprogs. It auto-detects your package manager (apt / pacman / dnf / zypper).

You can also run the check explicitly:

./vault.sh setup

Why these tools aren't shipped inside the container: LUKS is a Linux kernel feature, and cryptsetup has to run on the host with access to /dev/mapper. Doing it from inside the container would require --privileged, a much bigger security trade-off than one auto-installed package.

WSL2's kernel already has the loop and dm-crypt modules, so cryptsetup is the only missing piece for most users.

macOS: LUKS is a Linux kernel feature. On macOS, use FileVault for full-disk encryption (covers the Docker volume), or manually set up an APFS-encrypted disk image. The vault will print a warning and fall back to the unencrypted Docker volume.

Disk lifecycle commands

./vault.sh disk init [size]   # Re-init (rare — usually done by first ./vault.sh up)
./vault.sh disk open          # Manually unlock + mount
./vault.sh disk close         # Manually unmount + lock
./vault.sh disk status        # Show state (initialized? locked? mounted?)
./vault.sh disk resize 10G    # Grow the image (close it first)

Backup

./vault.sh backup
# (Closes the disk, then copies vault.luks to backups/<timestamp>/.)
# The backup file is already encrypted — safe to store offline.

The LUKS image is the backup format. Copy vault.luks to a USB stick — anyone who finds it sees ciphertext.

Opting out (CI, evaluation, demos)

VAULT_INSECURE=1 ./vault.sh up

This bypasses LUKS entirely and uses a plain Docker volume. Use only for evaluation and CI — your wallet data is unencrypted on disk in this mode. The first-run flow doesn't ask for a passphrase in this mode.

What LUKS protects (and what it doesn't)

Threat Encrypted at rest?
Laptop stolen while powered off Protected
Backup of your home dir leaks to the cloud Protected — ciphertext is useless without the passphrase
Malware reads ~/.crypto-vault/vault.luks while the disk is locked Protected
Malware reads the vault while it's unlocked and mounted Not protected — plaintext is on the mount
Keylogger captures your passphrase as you type it Not protected
Weak wallet password gets brute-forced (after the LUKS layer is broken) Use a strong wallet password too
sudo is compromised on the host Not protected — cryptsetup runs as root

Defense-in-depth, not silver bullet. Combine with full-disk encryption (BitLocker/FileVault), a strong wallet password, and a hardware wallet for the realistic threat model.


Hardware Wallets

Ledger and Trezor work via USB passthrough. Your private keys stay on the device's secure element — they never touch the container.

Step 1: Install udev rules on your host (Linux / WSL2)

# Ledger
wget -q -O - https://raw.githubusercontent.com/LedgerHQ/udev-rules/master/add_udev_rules.sh | sudo bash

# Trezor
sudo curl https://data.trezor.io/udev/51-trezor.rules -o /etc/udev/rules.d/51-trezor.rules
sudo udevadm control --reload-rules

Step 2: Uncomment USB passthrough in vault-x11.sh (add these flags to the docker run command)

--device /dev/bus/usb:/dev/bus/usb \
--device /dev/hidraw0:/dev/hidraw0 \
--group-add plugdev

Persistent Data

Where your wallet data actually lives:

Mode What persists Where
Default (LUKS) Chromium profile, extensions, wallet data Inside ~/.crypto-vault/vault.luks (one encrypted file)
Default (LUKS) Downloads Docker volume crypto-vault-downloads
Default (LUKS) Foundry/Hardhat projects Docker volume crypto-vault-projects
VAULT_INSECURE=1 Chromium profile Docker volume crypto-vault-chrome (plaintext)

Data survives ./vault.sh down and reboots. Only ./vault.sh nuke deletes it — and nuke removes the LUKS image, the Docker volumes, and the marker file. Once nuked, your seed phrase is your only path back.

Important: the encrypted disk only protects the Chromium profile (where wallet extensions store their data). Downloads and Foundry projects use plain Docker volumes. Don't put seed phrases or private keys in ~/projects/ — that directory is not encrypted.


Backup & Recovery

./vault.sh backup

In default LUKS mode, this:

  1. Locks the encrypted disk (safely closing all writes)
  2. Copies ~/.crypto-vault/vault.luks to backups/YYYYMMDD_HHMMSS/vault.luks

The backup is already encrypted — safe to put on a USB stick or in cloud storage. Anyone who finds it sees random bytes; only your LUKS passphrase can open it.

In VAULT_INSECURE=1 mode, this tars the Docker volume:

-> backups/YYYYMMDD_HHMMSS/chrome-profile.tar.gz

Encrypt before storing: gpg -c backups/*/chrome-profile.tar.gz

Your seed phrase is the real backup. Both backup formats preserve wallet settings (custom tokens, RPC endpoints, transaction history) but the seed itself is also in there, encrypted by the wallet's password. If you lose both the backup and your seed phrase, your funds are gone. If you have the seed phrase, you can always restore from scratch on a fresh vault.


Threat Model & Limitations

The protection works in layers. Each layer defends against a different class of attack:

Threat Defense Limit
Malicious npm install Container has no access to host Container shares the kernel — kernel exploits can escape (rare but real)
Data exfiltration over network Squid proxy whitelist; internal: true Docker network DNS poisoning of an allowlisted domain is theoretically possible
Browser zero-day RCE in the vault cap_drop: [ALL], no-new-privileges, non-root, internal-only network Can't fully prevent exploitation, but exfiltration is still blocked
Disk theft (powered-off laptop) LUKS encryption (AES-256-XTS, Argon2id) Weak passphrase → brute-forceable; combine with BitLocker/FileVault
Cloud backup leaking your home dir LUKS image is ciphertext Same: passphrase strength is everything
Host malware reading disk while vault is locked LUKS encryption None — ciphertext only
Host malware reading vault while unlocked Wallet's own scrypt encryption of the seed Strong wallet password is the only barrier; malware as root can dump LUKS keys from kernel memory
Malicious extension install Chrome policy: blocklist * + allowlist [MetaMask, Phantom, Rabby] None — Chrome refuses
Clipboard hijacker on host n/a (X11 is shared) Not protected — use Rabby's address verification
Signing a malicious transaction n/a Not protected — read what you sign; use a hardware wallet for serious holdings
Your seed phrase being lost / leaked n/a Not protected — store on metal, offline, in two locations

When you need stronger isolation

The container is rung 2 on the isolation ladder: better than running a wallet on your daily browser, not as strong as a VM or Qubes OS. For:

  • Wallets > $1,000 → use a hardware wallet (Ledger/Trezor). The vault becomes the signing UI, not the key store.
  • Wallets > $50,000 → also consider a dedicated VM (KVM/VirtualBox), Qubes OS, or multisig (Safe). Container isolation is real but not equivalent to hardware virtualization.

A hardware wallet remains the single strongest protection because private keys never exist in software, regardless of how compromised the host is. Everything in this project composes well with hardware wallets — USB passthrough is supported.


File Structure

crypto-safe-container/
├── docker-compose.yml          # Egress proxy service (always loaded)
├── docker-compose.disk.yml     # LUKS-encrypted disk override (auto-loaded in default mode)
├── Dockerfile                  # Chromium + Foundry + Node.js image
├── vault.sh                    # Helper CLI (preferred entry point)
├── vault-x11.sh                # Launch Chromium via X11 (called by vault.sh up)
├── chrome-policies.json        # Chrome managed policy (extension allow/blocklist, etc.)
├── welcome.html                # First-run wallet install page (loaded in Chromium)
├── Makefile                    # install-linux / install-mac targets
├── crypto-vault.desktop        # Linux .desktop entry (for app launchers)
├── .dockerignore
├── .gitignore
├── squid/
│   ├── squid.conf              # Proxy configuration
│   └── whitelist.txt           # Allowed domains (edit this!)
├── scripts/
│   ├── preflight.sh            # Host dep check + auto-install (cryptsetup, e2fsprogs)
│   ├── disk.sh                 # LUKS init/open/close/status/resize
│   └── smoketest.sh            # Verify proxy invariants (used by CI)
└── .github/
    ├── workflows/ci.yml
    └── ISSUE_TEMPLATE/

Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

The most impactful contributions right now:

  • Whitelist additions for popular DeFi protocols
  • Security hardening improvements
  • Testing on different Docker/OS versions
  • Documentation — especially video demos

License

MIT — use it, fork it, protect your crypto.


If this project helped you, consider giving it a star.
Every star helps more developers discover it before they get scammed.

About

Isolated Docker environment for crypto wallets. Your MetaMask/Phantom/Rabby run in a locked-down browser that can only reach whitelisted DeFi sites — even if compromised, stolen keys can't phone home.

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages