- Introduction
- Encryption Architecture
- Threat Model
- Security Features
- Attack Vectors & Mitigations
- Best Practices
- Recommendations by Use Case
- Known Limitations
- Security Disclosure
Coldstar is an air-gappable, USB-bootable Solana wallet designed with security-first principles. This document provides a comprehensive overview of the security architecture, potential threats, and recommended practices for different use cases.
Key Security Principles:
- Private keys are never stored in plaintext
- Keys exist in memory for ~100 microseconds during signing
- Memory locking prevents keys from being swapped to disk
- Immediate zeroization wipes keys from RAM after use
- Rust implementation provides memory safety and guaranteed cleanup
Private Key Generation
↓
[Secure Buffer - Memory Locked]
↓
Password → Argon2id KDF → Encryption Key (32 bytes)
↓ ↓
Random Salt Random Nonce (12 bytes)
(32 bytes)
↓
AES-256-GCM Encryption
↓
Encrypted Container (JSON) → Saved to USB
Encrypted Container (from USB)
↓
Password → Argon2id KDF (same parameters)
↓
Decryption Key
↓
AES-256-GCM Decrypt → [Secure Buffer - Memory Locked]
↓
Sign Transaction (~100 microseconds)
↓
Zeroize Memory (overwrite private key with zeros)
↓
Return Signature Only
| Component | Algorithm | Parameters |
|---|---|---|
| Key Derivation | Argon2id | 64 MB RAM, 3 iterations, 4 parallel threads |
| Encryption | AES-256-GCM | 256-bit key, 96-bit nonce |
| Authentication | GCM | Authenticated encryption (detects tampering) |
| Signing | Ed25519 | Solana-compatible elliptic curve |
| Encoding | Base64 (storage), Base58 (public keys) | Standard encodings |
Encrypted wallet containers are stored as JSON:
{
"version": 1,
"salt": "base64_encoded_32_bytes",
"nonce": "base64_encoded_12_bytes",
"ciphertext": "base64_encoded_encrypted_key",
"public_key": "base58_solana_address"
}- Argon2id - Memory-hard KDF prevents GPU-based brute force attacks
- AES-256-GCM - Provides both encryption and authentication (tamper detection)
- Memory locking (
mlock) - Prevents private keys from being swapped to disk - Transient decryption - Keys exist in plaintext for ~100 microseconds only
- Automatic zeroization - Rust's
Droptrait guarantees memory is wiped even on panic - USB seizure resistant - Encrypted container is useless without password
| Compromise Level | What This Means | Likelihood | Can Private Keys Be Exposed? | Risk Level |
|---|---|---|---|---|
| No compromise (clean OS) | Fully patched OS, no malware, normal user behaviour | High (70-80%) | No (practically) — Keys exist for ~100μs, immediately wiped | ✅ Safe |
| Low-risk user-space malware | Adware, browser extensions, clipboard clippers | Common (15-20%) | Very unlikely — Generic malware not targeting crypto operations | ✅ Low Risk (~5%) |
| Active crypto-targeting malware | RATs, stealers (Redline, Raccoon, Vidar), memory scanners | Uncommon (3-5%) | Low to Moderate — Must scan memory continuously to catch 100μs window | |
| Targeted Coldstar-specific malware | Custom tooling monitoring Coldstar/Rust processes | Rare (<1%) | Moderate to High — Attacker knows signing patterns, can hook Rust FFI | 🚨 High Risk (~70%) |
| Privilege escalation / root access | Admin/root access, debugger attachment capability | Very Rare (<0.5%) | High — Can inspect process memory, bypass mlock | 🚨 Very High Risk (~90%) |
| Kernel-level compromise | Malicious kernel driver, rootkit, modified OS | Extremely Rare (<0.1%) | Very High — Software protections ineffective | 🚨 Critical Risk (~99%) |
| Firmware / boot compromise | BIOS/UEFI backdoor, bootloader modification | Vanishingly Rare (<0.01%) | Certain — Entire trust model broken | 🚨 Total Compromise (100%) |
- Low-risk malware: Phishing, malicious browser add-ons, bundled software
- Active malware: Pirated software, cracked tools, torrent downloads
- Targeted attacks: High-value targets (whales, exchanges), reconnaissance
- Privilege escalation: Exploited 0-day vulnerabilities, social engineering
- Kernel/Firmware: Nation-state actors, advanced persistent threats (APT)
File: secure_signer/src/crypto.rs
Key Features:
- SecureBuffer - Custom type with automatic memory wiping on drop
- Memory Locking - Uses
mlock()to prevent swapping to disk - Immediate Zeroization - Private key overwritten with zeros after signing
- Panic Safety - Cleanup guaranteed even on errors (Rust's RAII)
- No Python Exposure - Private keys never enter Python memory space
Code Example:
impl Drop for SecureBuffer {
fn drop(&mut self) {
// Guaranteed to run even on panic
self.zeroize();
unsafe { munlock(self.ptr, self.len); }
}
}File: src/secure_memory.py
Features:
- Argon2i KDF - GPU-resistant password hashing
- XSalsa20-Poly1305 - Authenticated encryption (PyNaCl)
- Manual cleanup -
del+gc.collect()(less reliable than Rust)
Note: Python implementation is less secure due to unpredictable garbage collection. Rust version is strongly recommended for production use.
Attack: Malware scans process memory to capture private key during signing.
What Attacker Sees:
Memory Address: 0x7fff5fbff000
Raw Bytes (32-byte Ed25519 seed):
[a3, f2, 1b, 8e, 4d, 7c, 9a, 2f,
b5, 6e, 3d, 8c, 1a, 4f, 7e, 2b, ...]
Duration: ~100 microseconds
Mitigations:
- ✅ Minimal exposure window (100μs vs Python's seconds)
- ✅ Memory locking prevents swap file exposure
- ✅ Immediate zeroization
⚠️ Not effective against: Continuous memory scanning malware- 🚨 Defense: Use air-gapped signing for high-value transactions
Attack: Malware replaces copied wallet addresses with attacker's address.
Mitigations:
- ✅ Transaction verification display before signing
- ✅ Address whitelist system (trusted addresses only)
- ✅ Manual confirmation required
⚠️ User must verify addresses visually
Attack: User tricked into signing malicious transaction or revealing password.
Mitigations:
- ✅ Clear transaction details display before signing
- ✅ Explicit "CONFIRM" required before decrypting keys
- ✅ Amount limits for online signing
- ✅ Password strength requirements
Attack: RAM frozen with liquid nitrogen, contents extracted after power-off.
Mitigations:
- ✅ Memory locking makes this harder
- ✅ Immediate zeroization reduces window
⚠️ Not fully prevented: Keys recoverable for seconds after zeroization- 🚨 Defense: Physical security, encrypted RAM (CPU feature)
Attack: Compromised hardware/software introduced before user receives it.
Mitigations:
- ✅ Open-source code (auditable)
- ✅ Reproducible builds
⚠️ Cannot prevent: Firmware-level backdoors, compromised CPUs- 🚨 Defense: Hardware wallets from trusted vendors
-
Strong Passwords
- Minimum 12 characters
- Mix uppercase, lowercase, numbers, symbols
- Never reuse passwords
- Consider using a password manager
-
Verify Transactions
- Always check recipient address matches your intention
- Confirm amounts are correct
- Never sign transactions you didn't create
-
Keep Software Updated
- Update Coldstar regularly
- Apply OS security patches
- Use latest Rust signer version
-
Physical Security
- Store USB wallet in secure location
- Use encrypted USB drives
- Consider using multiple backups
-
Amount Limits
- Keep online wallets under $100 for daily use
- Store larger amounts in air-gapped cold storage
- Use separate wallets for different risk levels
-
Environment Isolation
- Use dedicated VM for signing (no network)
- Run from live USB OS (Alpine Linux)
- Clear clipboard before/after use
-
Audit & Monitoring
- Review transaction history regularly
- Monitor for unusual signing patterns
- Enable logging for forensic analysis
-
Dedicated Offline Computer
- Purchase used laptop (~$100-150)
- Physically remove WiFi/Bluetooth cards
- Never connect to internet, ever
- Boot from Coldstar USB only
-
Transaction Workflow
ONLINE COMPUTER → Create unsigned transaction → USB transfer ↓ OFFLINE COMPUTER → Sign with Coldstar → USB transfer ↓ ONLINE COMPUTER → Broadcast signed transaction -
Additional Hardening
- Use hardware wallet as second factor
- Implement multi-signature (2-of-3 keys)
- Store backup seeds in bank safe deposit box
Setup: Online Coldstar with standard security
Protections:
- ✅ Rust secure signer
- ✅ Transaction verification
- ✅ Strong password
Risk Level: Low to Moderate Acceptable For: Daily purchases, testing, small trades
Setup: Online Coldstar with enhanced security
Additional Protections:
- ✅ Amount limits (max $50 per transaction online)
- ✅ Address whitelist (trusted recipients only)
- ✅ Anomaly detection (unusual patterns blocked)
- ✅ Fresh password required for large amounts
Risk Level: Low Acceptable For: Regular trading, DeFi interactions
Setup: Air-gapped signing mandatory
Required Setup:
- 🚨 Dedicated offline laptop (never online)
- 🚨 USB transfer for transactions
- 🚨 Physical security for hardware
Additional Recommendations:
- Hardware wallet as backup
- Multi-signature wallet (2-of-3)
- Regular security audits
Risk Level: Very Low Acceptable For: Long-term holdings, investment portfolios
Setup: Air-gap + Hardware Wallet + Multi-Sig
Required Setup:
- 🚨 Air-gapped Coldstar (offline signing)
- 🚨 Hardware wallet (Ledger/Trezor) as second factor
- 🚨 Multi-signature (3-of-5 keys across different locations)
- 🚨 Bank safe deposit box for seed backups
Additional Recommendations:
- Professional security audit
- Insurance coverage
- Legal documentation for estate planning
Risk Level: Minimal Acceptable For: Institution-grade security, large portfolios
✅ Brute-force password attacks (Argon2id)
✅ File theft without password (AES-256-GCM encryption)
✅ Swap file exposure (memory locking)
✅ Accidental memory leaks (automatic zeroization)
✅ Simple malware (adware, clipboard hijackers)
✅ Forensic analysis of RAM dumps (minimal exposure)
❌ Sophisticated memory scanning malware (active user-space)
❌ Kernel-level rootkits (can bypass all protections)
❌ Firmware/BIOS backdoors (below OS level)
❌ Physical access attacks (evil maid, DMA)
❌ User error (phishing, password reuse)
❌ Compromised hardware (supply chain attacks)
Security vs Usability:
- Online signing is convenient but riskier
- Air-gapped signing is secure but requires extra hardware
- Balance depends on amount at risk
Python vs Rust:
- Python implementation easier to audit but less secure
- Rust implementation more complex but provides memory guarantees
- Rust version strongly recommended for production
If you discover a security vulnerability in Coldstar, please report it responsibly:
- Do NOT open a public GitHub issue
- Email security details to: syrem.dev@gmail.com
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if available)
- 24 hours - Initial acknowledgment
- 3 days - Preliminary assessment
- 7 days - Fix developed and tested
- Coordinated disclosure - Public announcement after patch released
Coldstar is open-source and community-audited. Professional security audits are planned for future releases. Contributions and code reviews are welcome.
Coldstar provides strong cryptographic security for Solana private keys through:
- Military-grade encryption (AES-256-GCM + Argon2id)
- Minimal memory exposure (~100 microseconds)
- Memory safety guarantees (Rust)
- Air-gap capability (offline signing)
The security of your wallet ultimately depends on:
- Your threat model (how much are you protecting?)
- Your operational security (passwords, physical security)
- Your execution environment (clean OS vs compromised)
Choose the appropriate security level for your use case and always assume that convenience is the enemy of security.
For maximum protection: Air-gap everything.
Audit Date: February 23, 2026
Auditor: Automated Security Review
Scope: Full codebase including Rust signer, Python application, and dependencies
A comprehensive security audit was conducted on the Coldstar cold wallet system. The audit examined:
- Rust secure signer implementation (
secure_signer/) - Python wallet management and transaction handling
- USB device management and filesystem operations
- Network RPC communication
- Cryptographic implementations
- Dependency vulnerabilities
- Input validation and sanitization
- File permissions and access controls
No critical vulnerabilities were identified that would allow direct compromise of private keys under normal operating conditions.
Location: src/iso_builder.py, src/usb.py
Severity: High
Risk: Command injection if untrusted input reaches subprocess calls
Description:
Multiple subprocess calls throughout the codebase execute system commands with user-influenced parameters. While most use subprocess.run() without shell=True (which is secure), device paths and mount points could potentially be manipulated.
Examples:
# src/usb.py - Line ~270+
subprocess.run(['diskutil', 'info', '-plist', device_id], ...)
# src/iso_builder.py - Various locations
subprocess.run(['parted', '-s', str(image_path), 'mklabel', 'msdos'], ...)
subprocess.run(['mount', partition, str(mount_point)], ...)Impact:
If an attacker can control device identifiers or paths (e.g., through symlinks or specially crafted USB device labels), they might be able to execute arbitrary commands with the privileges of the Python process.
Mitigation:
- ✅ Already using
subprocess.run()withoutshell=True(good practice) ⚠️ Need to add strict validation of all device paths and identifiers⚠️ Should sanitize mount points and verify they're within expected directories⚠️ Consider using absolute paths and checking for symlinks
Recommendation:
import os
import re
def validate_device_path(path: str) -> bool:
"""Validate device path to prevent injection"""
# Only allow /dev/* paths on Unix-like systems
if not path.startswith('/dev/'):
return False
# Prevent path traversal
if '..' in path or '//' in path:
return False
# Only allow expected device name patterns
if not re.match(r'^/dev/(sd[a-z]\d*|disk\d+s?\d*)$', path):
return False
# Resolve symlinks and verify still in /dev
try:
real_path = os.path.realpath(path)
return real_path.startswith('/dev/')
except:
return FalseLocation: src/network.py
Severity: High
Risk: RPC endpoint injection or malicious response handling
Description:
The SolanaNetwork class makes RPC calls to Solana endpoints but does not strictly validate responses. A malicious or compromised RPC endpoint could return crafted responses.
Examples:
# src/network.py
def get_balance(self, public_key: str) -> Optional[float]:
result = self._make_rpc_request("getBalance", [public_key])
lamports = result.get("result", {}).get("value", 0) # No validation
return lamports / LAMPORTS_PER_SOLImpact:
- Incorrect balance display could lead to user errors
- Malicious blockhash could cause transaction failures
- Type confusion if RPC returns unexpected data types
Mitigation:
⚠️ Add strict type checking and range validation for all RPC responses⚠️ Implement maximum values for balance and other numeric fields⚠️ Validate blockhash format before use
Recommendation:
def get_balance(self, public_key: str) -> Optional[float]:
result = self._make_rpc_request("getBalance", [public_key])
if "error" in result:
print_error(f"RPC Error: {result['error']['message']}")
return None
try:
lamports = result.get("result", {}).get("value", 0)
# Validate reasonable range
if not isinstance(lamports, (int, float)) or lamports < 0:
print_error("Invalid balance value from RPC")
return None
# Solana's max supply is ~500M SOL
if lamports > 1_000_000_000 * LAMPORTS_PER_SOL:
print_error("Balance exceeds maximum possible value")
return None
return lamports / LAMPORTS_PER_SOL
except (TypeError, ValueError) as e:
print_error(f"Error parsing balance: {e}")
return NoneLocation: src/wallet.py - save_keypair() method
Severity: Medium
Risk: Weak passwords may be brute-forced despite Argon2id
Description:
While the system requires non-empty passwords, there is no enforcement of password complexity requirements (minimum length, character diversity, etc.).
Current Code:
if not password:
print_error("Password cannot be empty!")
return FalseImpact:
Users may choose weak passwords like "password" or "12345678", which could be cracked despite Argon2id's GPU resistance. Although the parameters are strong (64MB memory, 3 iterations), simple passwords remain vulnerable to dictionary attacks.
Recommendation:
def validate_password_strength(password: str) -> Tuple[bool, str]:
"""Validate password meets security requirements"""
if len(password) < 12:
return False, "Password must be at least 12 characters long"
if not any(c.isupper() for c in password):
return False, "Password must contain at least one uppercase letter"
if not any(c.islower() for c in password):
return False, "Password must contain at least one lowercase letter"
if not any(c.isdigit() for c in password):
return False, "Password must contain at least one number"
# Check against common passwords
common_passwords = {'password', '12345678', 'qwerty', ...}
if password.lower() in common_passwords:
return False, "Password is too common. Please choose a stronger password"
return True, "Password meets requirements"Location: src/wallet.py - File operations
Severity: Medium
Risk: Race condition in file checks and operations
Description:
The code checks if files exist and then operates on them in separate steps, which creates a window for race conditions.
Example:
if not load_path.exists():
print_error(f"Keypair file not found: {load_path}")
return None
with open(load_path, 'r') as f: # File could be deleted/modified here
file_content = f.read()Impact:
In a multi-process environment or with symbolic link attacks, files could be swapped between the check and use, potentially leading to reading wrong files or denial of service.
Recommendation:
- Use exception handling instead of pre-checks
- Open files directly and handle FileNotFoundError
- Consider using atomic file operations
try:
with open(load_path, 'r') as f:
file_content = f.read()
except FileNotFoundError:
print_error(f"Keypair file not found: {load_path}")
return None
except PermissionError:
print_error(f"Permission denied: {load_path}")
return NoneLocation: config.py
Severity: Medium
Risk: Fee wallet could be compromised or misused
Description:
The infrastructure fee wallet address is hardcoded in the configuration:
INFRASTRUCTURE_FEE_WALLET = "Cak1aAwxM2jTdu7AtdaHbqAc3Dfafts7KdsHNrtXN5rT"Impact:
- If this wallet's private key is compromised, fees go to attacker
- Users cannot verify or change the fee recipient
- No transparency about fee usage
Recommendation:
- Document the fee structure clearly in user-facing documentation
- Consider making fees optional or configurable
- Implement fee wallet rotation mechanism
- Provide transparency reports on fee usage
Location: src/secure_memory.py
Severity: Medium
Risk: Private keys may linger in Python memory despite cleanup attempts
Description:
The Python fallback implementation uses gc.collect() and del to clear sensitive data, but Python's garbage collector does not guarantee immediate cleanup or memory zeroization.
Current Code:
# Clean up sensitive data
del key
del password_bytes
gc.collect()Impact:
Private keys may remain in memory longer than intended, potentially accessible through memory dumps or during Python's garbage collection cycle.
Mitigation Status:
- ✅ Rust signer is now REQUIRED (good!)
- ✅ Python implementation is documented as less secure
- ✅ System forces use of Rust signer in production
No Action Needed - The codebase already correctly mandates Rust signer usage and exits if not available.
Location: Multiple files
Severity: Low
Risk: Malformed addresses could cause crashes or undefined behavior
Description:
While there is a validate_address() method in WalletManager, it's not consistently used before operations. Some functions accept public key strings without validation.
Example:
# src/transaction.py - Line 68
def create_transfer_transaction(self, from_pubkey: str, to_pubkey: str, ...):
from_pk = Pubkey.from_string(from_pubkey) # May raise exception if invalidRecommendation:
- Always validate public keys before parsing
- Use wrapper function with proper error handling
- Return user-friendly error messages
Location: Various error handling blocks
Severity: Low
Risk: Verbose error messages might leak system information
Description:
Some error messages include detailed exception information that could reveal system paths or internal structure.
Example:
except Exception as e:
print_error(f"Failed to load keypair: {e}")
import traceback
print_warning(f"Details: {traceback.format_exc()}")Recommendation:
- In production mode, log detailed errors to a secure file
- Show users simplified error messages
- Implement debug vs. production error handling modes
Location: src/network.py
Severity: Low
Risk: Long timeouts could cause application hangs
Description:
HTTP client has a 30-second timeout, which could make the application appear frozen to users.
self.client = httpx.Client(timeout=30.0)Recommendation:
- Consider reducing timeout for better responsiveness
- Implement retry logic with exponential backoff
- Add user-visible progress indicators for network operations
The following security best practices are correctly implemented:
✅ Rust Secure Signer - Required for all signing operations, provides memory locking and automatic zeroization
✅ Argon2id KDF - Strong parameters (64MB RAM, 3 iterations) resist GPU attacks
✅ AES-256-GCM - Authenticated encryption prevents tampering
✅ No shell=True - All subprocess calls avoid shell interpretation
✅ File Permissions - Wallet files are created with 0o600 (read/write owner only)
✅ Memory Locking - Rust implementation uses mlock() to prevent swapping
✅ Zeroization - Private keys are explicitly overwritten with zeros
✅ Environment Variable Isolation - Permissive mode controlled via environment variable
✅ Panic Safety - Rust Drop trait ensures cleanup even on panic
✅ No Plaintext Storage - All private keys are encrypted at rest
aiofiles>=25.1.0
base58>=2.1.1
httpx>=0.28.1
pynacl>=1.6.1
questionary>=2.1.1
rich>=14.2.0
solana>=0.36.10
solders>=0.27.1
Status: All dependencies are at recent versions. No known critical vulnerabilities identified.
Recommendation: Implement automated dependency scanning with tools like:
pip-auditfor Pythoncargo auditfor Rust- GitHub Dependabot alerts
Key security-critical dependencies:
ed25519-dalek = "2.1"- Current stable version, actively maintainedaes-gcm = "0.10"- Current version of RustCrypto's AES-GCMargon2 = "0.5"- Current version, secure parameterszeroize = "1.7"- Memory zeroization library
Status: All cryptographic dependencies are from RustCrypto project (well-audited, industry standard).
Recommendation: Continue monitoring for updates, especially for cryptographic libraries.
- ✅ Implement strict device path validation in
usb.py - ✅ Add RPC response validation in
network.py ⚠️ Implement password strength requirements inwallet.py
- Consider fee wallet transparency and documentation
- Fix TOCTOU issues by using exception-based file handling
- Add consistent input validation for all public key operations
- Implement production vs. debug error message modes
- Add automated dependency vulnerability scanning
- Create security testing suite with fuzzing
To validate security properties, implement:
-
Unit Tests
- Test password validation with weak passwords
- Test path validation with malicious inputs
- Test RPC response parsing with malformed data
-
Integration Tests
- Test entire signing flow with Rust signer
- Verify file permissions after wallet creation
- Test error handling paths
-
Security Tests
- Fuzzing for parser inputs (JSON, base64, base58)
- Memory leak detection
- Timing attack resistance for decryption
-
Manual Testing
- Verify memory zeroization with debugger
- Test USB device handling with malformed labels
- Test RPC behavior with mock malicious endpoint
Last Updated: February 23, 2026
Security Audit Version: 1.0
Coldstar Project: https://github.com/devsyrem/homebrew-coldstar