Renewed security audit #41
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This audit is done by Opus 4.6 to verify fixes to the previous security audit mycelium/www_migrate_mycelium#32.
Stack: Rust backend (Axum) + Rust/WASM frontend (Dioxus) + Caddy reverse proxy
Architecture: Frontend signs transactions in WASM, backend prepares/submits to TFChain
CRITICAL Issues
1. CSP blocks external fonts and CSS (breaks production UI)
File:
deploy/Caddyfile:7The Content-Security-Policy allows
style-src 'self' 'unsafe-inline'anddefault-src 'self', but the frontend loads Bootstrap CSS, Bootstrap Icons, and Google Fonts from CDNs:cdn.jsdelivr.net(Bootstrap CSS + Icons)fonts.googleapis.com/fonts.gstatic.comThese will be blocked in production by the CSP. Either the CSP must be updated, or these assets must be self-hosted.
Fix: Update the CSP in the Caddyfile to include the CDN domains, or bundle assets locally (preferred for security).
2. No rate limiting on API endpoints
File:
crates/backend/src/main.rs:70-93There is no rate limiting middleware on any API endpoint. The
MAX_PENDINGcap (10,000) on prepare sessions prevents unbounded memory growth, but:/balance,/twin,/farm,/node) have zero rate limiting, enabling DoS against the TFChain WebSocket connectionFix: Add
tower_governoror similar rate-limiting middleware, especially on/transfer/prepare,/node/opt-out-v3/prepare, and all chain-query endpoints.3. Mnemonic held in memory for entire session
Files:
crates/frontend/src/signing.rs:18,crates/frontend/src/main.rs:98WalletStatestores the mnemonic phrase in a plainStringfor the entire browser session lifetime. In WASM, this stays in linear memory and cannot be securely zeroed. If an attacker can read WASM memory (e.g., via a browser extension or Spectre-class attack), the mnemonic is exposed.Recommendation: This is a known limitation of WASM. Consider using the vault-only flow by default (where the mnemonic is decrypted, used briefly for key derivation, then discarded) and documenting the risk.
HIGH Issues
4. No
font-srcin CSP — web fonts will be blockedFile:
deploy/Caddyfile:7The CSP has no
font-srcdirective. Default falls todefault-src 'self', blocking Google Fonts woff2 files fromfonts.gstatic.comand Bootstrap Icons font files fromcdn.jsdelivr.net.5.
connect-srconly allows devnet gateway in productionFile:
deploy/Caddyfile:7The CSP
connect-srcincludeshttps://ledger.dev.projectmycelium.com:9090(devnet) but the config also references a production gateway. Meanwhile, the default gateway URL compiled into the frontend is the devnet gateway:File:
crates/frontend/src/config.rs:6If
HEROLEDGER_GATEWAY_URLis not set at build time, production will use the devnet gateway. This is a configuration risk that could cause production to interact with dev infrastructure.6. Docker container runs as root
File:
Dockerfile:22-38The runtime stage doesn't create or switch to a non-root user. The binary runs as root inside the container, which increases the blast radius of any container escape.
Fix: Add
RUN useradd -r portalandUSER portalbefore theCMD.7. Session ID is not tied to any client identity
File:
crates/backend/src/api.rs:373-384The
transfer_prepareendpoint returns asession_id(UUIDv4), andtransfer_submitonly checks that thesigner_accountmatches the originalfrom. However, the session_id is the only secret protecting the pending transfer. If an attacker guesses or intercepts the session_id, they can submit a different (malicious) signature, although the chain would reject an invalid signature.The real risk: an attacker who intercepts the session_id can trigger the submit to consume the session, causing a denial-of-service on the legitimate user's transfer attempt.
Recommendation: Consider adding a client-side nonce or binding the session to additional client identity (e.g., a hash of the signing payload).
MEDIUM Issues
8.
MAX_PENDINGis very high (10,000)File:
crates/backend/src/state.rs:1010,000 pending sessions × (the size of
PendingTransferstruct) could use a non-trivial amount of memory. An attacker could create 10,000 sessions in rapid succession before cleanup runs.Fix: Lower to 1,000 or add per-IP rate limiting.
9. No
Permissions-PolicyheaderFile:
deploy/CaddyfileModern browsers support
Permissions-Policy(formerlyFeature-Policy) to restrict access to browser features like camera, microphone, geolocation, etc. This header is missing.Fix: Add
Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"to the Caddy headers.10. Floating-point arithmetic for financial amounts
Files:
crates/backend/src/api.rs:327,crates/shared/src/lib.rs:78amount_tftis anf64, and conversion to planck uses(req.amount_tft * 10_000_000.0).round() as u128. While the.round()mitigates most precision issues, using floating-point for financial calculations is inherently risky. For example,0.1 * 10_000_000.0 = 999999.9999999999before rounding.The tests cover basic cases, and
.round()handles them. This is low-risk but worth noting for future amounts near the precision boundary.11. No
secureattribute on vault localStorage dataFile:
crates/frontend/src/components/login.rs:53-74The vault-to-TFChain address mapping is stored in
localStoragewith keyvault_tfchain_addresses. While the actual encrypted vault uses the Web Crypto API (which is good), the address mapping is stored in plaintext. An XSS attack could read this mapping to identify which accounts are saved.This is low-impact since only public addresses are stored, not private keys.
12. CDN dependency for CSS/fonts (supply chain risk)
File:
crates/frontend/src/main.rs:157-168Bootstrap CSS/Icons and Google Fonts are loaded from CDNs without Subresource Integrity (SRI) hashes. A CDN compromise could inject malicious CSS/JS.
Fix: Either self-host these assets or add
integrityattributes.Summary of Required Actions (Priority Order)
HEROLEDGER_GATEWAY_URLis set in production buildstower_governormiddlewarePermissions-Policy