- Add make_ping_key_revocation.py: onion revocations are signed by the identity key, DNS TXT revocations are self-signed by the revoked key - Document the revocation format, endpoint, helper usage, lifetime and no-revocation-of-identity-key limits - Update the security audit, references and env docs for the new flow and the v2/onion-only scope
13 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. - WeTime ping keys:
SSL_KEY_PATHsigns stats/pings. Onion delegation adds a dedicated, rotatable online key; the onion identity key stays offline and is used only to issue delegation certificates.
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. Onion Ping Delegation (DESIGN / ACCEPTED RISK)
Severity: LOW | Status: Accepted
Location: src/bin/bal-pusher.rs (validate_onion_delegation, delegation_for_url); certificate verification on the WeTime /ping endpoint
Description: Onion pings may carry a delegation certificate that binds a dedicated Ed25519 ping key to the onion host until valid_until. The certificate is signed by the onion identity key embedded in the .onion address, so the identity key can stay offline.
Compromise impact: An attacker who obtains the online ping key and its certificate can sign pings and metadata for that onion host until the key is revoked or the certificate expires. It cannot take over the onion service identity, sign for another host, or forge a certificate without the identity key.
Revocation: POST /ping-keys/revoke accepts a signed WELIST-PING-REVOCATION-V1 statement. Onion revocations are signed by the identity key (which cannot itself be revoked); DNS TXT revocations are self-signed by the key being revoked, the only authority available for a TXT record. Entries are permanent and key-scoped, checked before the ping signature on both paths, and fail closed on database errors.
Accepted risks:
- Rotation alone does not revoke: a leaked key must be explicitly revoked. If the signing authority is lost (onion identity key, or the DNS private key), the key remains usable until
valid_untilor the DNS cache TTL passes. Use short lifetimes (30–90 days). - Self-signed DNS revocation: anyone holding the DNS key, including an attacker who stole it, can submit a revocation. This only stops that key, which the attacker could already impersonate; it cannot revoke other keys.
- Replay: the signed payload has no nonce. The WeTime service discards duplicate
(executor, chain, height, blockhash)pings without side effects, so a recorded ping cannot refresh liveness or roll back metadata. Mitigation Applied: The offline identity key issues certificates;SSL_KEY_PATHholds only the delegated key. A present but invalid, expired or mismatched certificate is rejected without downgrade,delegationis rejected on non-onion hosts, and revoked keys are rejected for both authentication paths.
12. 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 |