The gRPC method ValidateDbPassword (protobuf RPC) is a backward-compatibility stub: the SQLite database is not encrypted, the request password field is ignored, and the response is always success after a per-IP gate.
The implementation is validate_db_password in src/rpc/service.rs. It:
- Resolves the client address and runs
check_rate_limiton the shared in-memoryRateLimiter(src/rpc/rate_limiter.rs). - Drops
passwordon the floor (let _ = req.password;). - Calls
record_successon the limiter for that IP (clears any tracked state for that key). - Returns
ValidateDbPasswordResponsewithsuccess: true.
Important: validate_db_password does not call record_failure. So exponential backoff and lockout described below are generic RateLimiter capabilities (used by unit tests and available if another call site ever records failures). They are not driven by repeated ValidateDbPassword calls with “wrong passwords,” because passwords are not validated.
See Issue #569 for background.
| Concept | Where |
|---|---|
| RPC name | ValidateDbPassword in proto/admin.proto |
| Handler | validate_db_password in src/rpc/service.rs |
| Limiter | password_rate_limiter: Arc<RateLimiter> on AdminServiceImpl |
| Success path | record_success(&remote_addr) after ignoring password |
The in-memory limiter is keyed by client IP. It exposes check_rate_limit, record_failure, and record_success.
When record_failure is used (e.g. in unit tests, or a hypothetical future handler), the limiter can apply exponential backoff and lockout:
Failed attempts (record_failure) |
Effect |
|---|---|
| 1st | Immediate + 1s delay |
| 2nd | Immediate + 2s delay |
| 3rd | Immediate + 4s delay |
| 4th | Immediate + 8s delay |
| 5th+ | Locked out for 5 minutes |
After record_success, that IP’s failure state is cleared (see record_success in rate_limiter.rs).
Not exercised by ValidateDbPassword today: the validate_db_password handler never invokes record_failure, so clients only exercising this RPC do not accumulate “failed attempts” through wrong passwords.
const MAX_ATTEMPTS: u32 = 5;
const LOCKOUT_DURATION: Duration = Duration::from_secs(300); // 5 minutes
const BASE_DELAY_MS: u64 = 1000; // 1 secondThe limiter uses tokio::sync::Mutex. The lock is dropped before the exponential backoff sleep inside record_failure so the mutex is not held across the delay.
validate_db_passwordlogs receipt of the RPC at INFO (client IP).RateLimitermay emit WARN for backoff/lockout whencheck_rate_limitdenies an IP that already has failure state, or whenrecord_failureruns — paths that matter for unit tests and for generic use of the limiter, not for password validation onValidateDbPassword.
How the original issue’s ideas map to the codebase today:
| Suggestion | Notes |
|---|---|
| Per-IP rate limiting | check_rate_limit runs before the handler body. |
| Exponential backoff / lockout | Implemented inside RateLimiter; not triggered by ValidateDbPassword (no record_failure). |
| Audit logging | tracing in service + limiter. |
| Localhost-only | Default RPC bind 127.0.0.1 (see settings.toml / docs/RPC.md). |
| Strong auth | Out of scope for this stub; would need API keys or similar. |
Unit tests in src/rpc/rate_limiter.rs exercise record_failure, lockout, record_success, and eviction — they document limiter behavior, not password checking.
- Issue: #569
- RPC docs:
docs/RPC.md - Default RPC config binds to
127.0.0.1:50051(localhost only)