bal-server: - Wire independent actix-governor buckets per endpoint class (pushtxs, searchtx, info/stats, default) so polling a cheap endpoint can no longer exhaust the push-txs budget; previously every limit was dead config - Enforce BAL_SERVER_ACTIX_TIMEOUT_SECS via a request-timeout middleware that returns 408 Request Timeout, covering slow request bodies - De-duplicate txids within a single /pushtxs request body - Replace the byte-slice xpub detection (panic on non-ASCII) with a safe prefix check bal-pusher: - Handle malformed/empty ZMQ frames without indexing panics or UTF-8 expect - Return an error from sign_message and skip the stats report instead of panicking when the private key cannot be parsed validation / db: - Reject IPv4-mapped IPv6 targets (::ffff:a.b.c.d) in is_valid_welist_url to close an SSRF bypass, with unit tests - Use Path::starts_with for the SQLite forbidden-directory check so paths like /etcfoo are not misclassified misc: - Add explicit secret patterns to .gitignore and sync the docs
11 KiB
Security Audit
Quick Reference
- What this file contains: threat model, vulnerability assessment, hardening recommendations, and a security checklist.
- See also: AGENTS.md, 07_deployment_and_ops.md, 06_database_schema.md, 03_architecture_and_data_flow.md, 04_modules_detail.md
Threat Model
Assets
bal.db(SQLite database): Contains all transaction details, user IP addresses, and stats data. Single unencrypted file on disk. WAL mode enabled for concurrent access safety.- Private Keys (
privkey.pem): Used to sign statistics payloads for thewelistserver. Located in.gitignore. - Bitcoin Node (
bitcoind) Access: Thebal-pusherhas RPC access. Compromise allows arbitrary transaction broadcasting. - Server Availability (
bal-server): Public-facing HTTP endpoint. DoS attacks threaten service availability.
Attackers
- Remote Anonymous Users: Can interact with the API via public HTTP. No credentials required.
- Network Man-in-the-Middle (MITM): TLS termination is via Nginx reverse proxy. The
bal-serveritself is plain HTTP. - Local/Insider Threats: If the server is compromised, the attacker can access
bal.db, private keys, and env files.
Vulnerability Assessment
1. SQL Injection (FIXED)
Severity: HIGH | Status: Fixed
Location: src/db.rs, src/bin/bal-server.rs, src/bin/bal-pusher.rs
Description: SQL queries previously used format!() for string interpolation. All queries now use parameterized statements (? with bind()).
Mitigation Applied: All SQL queries rewritten with prepared statements. execute_insert uses parameterized batch inserts. check_duplicate_txids uses parameterized IN clauses. echo_stats handler uses prepared statements for chain filtering.
Regression Tests: tests/sql_injection_tests.rs (3 tests).
2. Panic on Untrusted Input (FIXED)
Severity: HIGH | Status: Fixed
Location: src/bin/bal-server.rs, src/bin/bal-pusher.rs
Description: All unwrap()/expect() calls on critical paths have been replaced with safe error handling.
Mitigation Applied:
bal-server:from_utf8returns 400,sqlite::openusesopen_db()with validation, all handlers return proper HTTP status codes.bal-pusher: RPC failures log errors + sleep + retry (no panic), ZMQrecvusesset_rcvtimeo(5000), malformed ZMQ frames are handled withget()/first()andfrom_utf8_lossy(no indexing or UTF-8expect), stats signing returns an error that is logged and skipped rather thanexpect-panicking. Regression Tests:tests/panic_regression_tests.rs(2 tests).
3. Secret Leakage (FIXED)
Severity: HIGH | Status: Fixed
Location: make_release.sh, .gitignore
Description: make_release.sh now loads TOKEN from .env (.env.example provided). Private keys (.pem, .key) are in .gitignore. generate_keys.sh sets chmod 600 on generated keys.
Mitigation Applied: Secrets removed from scripts and repository. .gitignore protects .env, *.pem, *.key files.
Regression Tests: tests/secret_leakage_tests.rs (3 tests).
4. Denial of Service (DoS) (FIXED)
Severity: HIGH | Status: Fixed
Location: src/bin/bal-server.rs (HTTP), src/bin/bal-pusher.rs (ZMQ)
Description: All DoS vectors mitigated via actix-web migration.
Mitigation Applied:
- Body size limit:
PayloadConfig::default().limit(max_body_size)viaBAL_SERVER_ACTIX_MAX_BODY_SIZE(default 1 MiB). - Rate limiting:
actix-governorwith a token bucket per client IP, with an independent bucket for each endpoint class (push txs, searchtx, info/stats, default) so that polling a cheap endpoint cannot exhaust the push-txs budget. UsesRealIpKeyExtractorto extract the real client IP from proxy headers. - Connection limits:
workers(4)andmax_connections(100)viaBAL_SERVER_ACTIX_WORKERS/MAX_CONNECTIONS. - Request timeout:
middleware::from_fnwraps every handler intokio::time::timeout, returning408 Request TimeoutwhenBAL_SERVER_ACTIX_TIMEOUT_SECS(default 5) elapses, covering slow request bodies. - ZMQ timeout:
set_rcvtimeo(5000)prevents infinite blocking. - RPC retry: sleep + retry on connection failure instead of panic.
- ZMQ message handling is panic-free: multipart frames are accessed with
get()/first()and topics are decoded withfrom_utf8_lossy, so malformed frames cannot abort the pusher. - Stats-report signing failures are logged and skipped instead of panicking the pusher when the key is unreadable.
5. SSRF / Network Abuse via reqwest (FIXED)
Severity: MEDIUM | Status: Fixed
Location: src/bin/bal-pusher.rs, src/validation.rs
Description: URL validation prevents redirecting requests to internal/private IPs.
Mitigation Applied: is_valid_welist_url() in src/validation.rs blocks localhost, loopback, RFC1918, link-local, multicast, unspecified, IPv6 unique-local addresses, and IPv4-mapped IPv6 addresses (::ffff:a.b.c.d) that route to loopback/private/link-local/unspecified IPv4 targets. HTTPS-only scheme enforced. Note: hostnames are not DNS-resolved before validation, so a domain that resolves to a private address is not caught by this check — restrict WELIST_SERVER_URL to trusted values.
Regression Tests: tests/ssrf_tests.rs (integration) + 11 unit tests in src/validation.rs.
6. Insecure Database Access (FIXED)
Severity: MEDIUM | Status: Fixed
Location: src/db.rs, src/bin/bal-server.rs, src/bin/bal-pusher.rs
Description: Database path validation and WAL mode for concurrent access.
Mitigation Applied:
open_db()rejects..traversal, forbidden system directories (/etc,/proc,/sys,/dev,/usr,/bin,/sbin,/lib,/opt), symlinks, and non-regular files.- WAL mode (
PRAGMA journal_mode=WAL) with retry logic (up to 5 attempts). busy_timeout=5000for concurrent access.bal-serverusesArc<Mutex<Connection>>for thread-safe shared access. Regression Tests:tests/db_path_validation.rs(5 tests).
7. ZMQ Authentication and Encryption (OPEN)
Severity: MEDIUM | Status: Open
Location: src/bin/bal-pusher.rs
Description: ZMQ connection is plaintext TCP. No authentication (ZAP), no encryption (ZMQ_CURVE). If the ZMQ port is exposed, any attacker can subscribe to topics.
Mitigation (Operational):
- Bind ZMQ to
127.0.0.1only. - Firewall blocks external access to ZMQ ports.
- If public ZMQ is required, use ZMQ_CURVE with public-key cryptography.
8. Missing HTTPS / Insecure Server Communication (FIXED)
Severity: HIGH | Status: Fixed (Infrastructure)
Location: Nginx configuration, contrib/nginx/bal-server.conf
Description: TLS termination via Nginx reverse proxy. The bal-server intentionally does not implement TLS.
Mitigation Applied:
- Dedicated Nginx template with
listen 443 ssl http2, Let's Encrypt paths. - Security headers:
X-Frame-Options,X-Content-Type-Options,Referrer-Policy. client_max_body_sizematchingBAL_SERVER_ACTIX_MAX_BODY_SIZE.- HTTP 80 redirect to HTTPS.
- Deployment checklist ensures no accidental plain HTTP exposure.
9. Missing Input Validation (FIXED)
Severity: MEDIUM | Status: Fixed
Location: src/bin/bal-server.rs
Description: Network, txid, and content validation.
Mitigation Applied:
- Network validation:
NETWORKS.contains(¶m.as_str())before processing. Unknown networks return 404. - Txid validation:
echo_searchrequires exactly 64 ASCII hex characters. Non-hex or wrong length returns 400. - XPub address caching:
get_all_addresses_by_xpubloads all addresses once per batch (O(1) lookup), eliminating N+1 queries. - Content-Length: Handled by actix-web
PayloadConfigsize limit. Regression Tests:tests/input_validation_tests.rs(4 tests).
10. Information Leakage (MITIGATED)
Severity: LOW | Status: Mitigated
Location: Application logs
Description: Raw file logging (valid_txs/invalid_txs) has been removed. info!/warn! macros may still log txid and details in application logs.
Mitigation (Operational): Set production RUST_LOG to warn or higher. Log files restricted with chmod 600.
11. bal-stats.rs.dontcompile (REMOVED)
Severity: LOW | Status: Removed Description: The broken HTML report generator file no longer exists in the source tree.
Hardening Recommendations
System-Level
- Run as non-root user with systemd hardening (
ProtectSystem=full,NoNewPrivileges,PrivateDevices,MemoryDenyWriteExecute). - Use firewall to block all inbound ports except HTTPS (443) and SSH (22).
- Use VPN or Tor for
welistconnections if on public network. - Run in a container or chroot for isolation.
- Enable SELinux or AppArmor profiles for the binaries.
- Use read-only filesystem for the server binary.
Application-Level
- Rate Limiting: Implemented via
actix-governorwith token-bucket per client IP and a separate bucket per endpoint class.RealIpKeyExtractoridentifies clients behind reverse proxy usingX-Real-IP/X-Forwarded-Forheaders. Trusted proxy IP configurable viaBAL_SERVER_TRUSTED_PROXY. - Input Validation: Network enum check, txid hex validation, body size limits.
- Request Timeout:
tokio::time::timeoutmiddleware returns408when a handler (including body read) exceedsBAL_SERVER_ACTIX_TIMEOUT_SECS. - HTTPS: Via Nginx reverse proxy with Let's Encrypt.
- WAL Mode: Enabled with retry logic for concurrent access.
- ZMQ Timeout: 5-second receive timeout prevents infinite blocking.
- Transaction Size Limits: Configurable via rate limiting and body size.
- ZMQ Retry: Reconnect logic with timeout-based detection.
- Fee Limits: Per-network
fixed_feeconfiguration. - Network Limits: Only known networks accepted (bitcoin, testnet, testnet4, signet, regtest).
- Locktime Reasonableness: Locktime compared against blockchain height and median time with threshold-based distinction.
Regression Test Suite
| Test File | Tests | Coverage |
|---|---|---|
tests/sql_injection_tests.rs |
3 | Parameterized queries, injection prevention |
tests/panic_regression_tests.rs |
2 | Mutex poisoning recovery, NULL value handling |
tests/ssrf_tests.rs |
4+ | URL validation, internal IP blocking |
tests/secret_leakage_tests.rs |
3 | .gitignore, no tracked secrets, no hardcoded tokens |
tests/input_validation_tests.rs |
4 | Address caching, network validation, txid hex validation |
tests/db_path_validation.rs |
5 | Path traversal, forbidden dirs, symlinks, WAL pragma, valid paths |
src/validation.rs (inline) |
11 | SSRF URL validation unit tests |