Add security review #004 (post-fix) and #005 (admin socket)

This commit is contained in:
2026-06-14 07:45:13 +00:00
parent 6400c90cb3
commit 38649e9485
2 changed files with 792 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
---
status: draft
last_updated: 2026-06-12
reviewed_code:
- src/main.rs
- src/server.rs
- src/cli.rs
- src/config/static_config.rs
- src/config/validation.rs
- src/proxy/handler.rs
- src/proxy/error.rs
- src/rate_limit/mod.rs
- src/rate_limit/bucket.rs
- src/admin/socket.rs
- src/logging/mod.rs
- src/logging/format.rs
- src/tls/acceptor.rs
- src/tls/config.rs
- tests/integration_test.rs
- tests/helpers/http_test_helper.rs
reviewer: code-reviewer
based_on: docs/reviews/003-security-and-bug-review.md
---
# Post-Fix Review #004
## Purpose
Verify that the 4 critical, 12 warning, and 10 suggestion findings from review
#003 were correctly addressed. Flag any regressions or remaining issues.
## Critical Findings — All Fixed
### C1. Rate Limiter IP Source ✓
`src/rate_limit/mod.rs:66-73` — Now uses `ConnectInfo` exclusively. No fallback
to client-supplied `X-Forwarded-For`. Returns 429 if `ConnectInfo` is absent.
Three new tests verify: ConnectInfo-only extraction, rejection without
ConnectInfo, and XFF header ignored (same bucket for different XFF values).
### C2. InFlightCounter Increment ✓
`src/server.rs:23-28,81``InFlightGuard::new()` now calls
`counter.increment()` on construction. The guard is created with
`InFlightGuard::new(in_flight.clone())` at line 81. Drain interval tightened
from 50ms to 100ms.
### C3. Connect Timeout Ceiling ✓
`src/proxy/handler.rs:230,234,242``CONNECT_TIMEOUT_CEILING_SECS = 30`
replaces the hardcoded 5s. Per-site `tokio::time::timeout` can now enforce any
value up to 30s.
### C4. JSON Format Without Log File ✓
`src/logging/mod.rs:55-56``.json()` added to the `None` branch of
`init_json`.
---
## Warning Findings — All Fixed
| # | Finding | Status |
|---|---------|--------|
| W1 | Upstream host validation | ✓ `is_valid_upstream` validates host part (IPv6 bracket, IP, hostname). 9 new tests. |
| W2 | ACME contact validation | ✓ Checks non-empty email with `@` after `mailto:`. 2 new tests (`mailto:` empty, no `@`). |
| W3 | Query string silently dropped | ✓ `build_upstream_uri` returns `Result`, logs warning, handler returns 502. |
| W4 | Admin socket resource limits | ✓ 5s read timeout, 4096 byte limit via `reader.take(4096)`. 3 new tests. |
| W5 | TlsMode wildcard mismatch | ✓ Wildcard arm removed. Count mismatch check added at `main.rs:190-196`. |
| W6 | RawConfig/FullConfig duplication | ✓ `RawConfig` removed. `load_config` uses `FullConfig::parse()` + `into_static_and_dynamic()`. |
| W7 | cleanup_stale_socket side effect | Not addressed (acceptable per review — Phase 1). |
| W8 | Misleading test name | ~ Test name unchanged despite commit claim. See N1 below. |
| W9 | Empty routing_table in test helper | ✓ `test_dynamic_config_with_limit` now uses `DynamicConfig::from_sites()`. |
| W10 | TokenBucket pub fields | ✓ `tokens`, `last_refill`, `rate`, `max` now private. `last_access` is `pub(crate)`. |
| W11 | reload_mutex exposed for testing | ✓ Gated with `#[cfg(test)]`. |
| W12 | http_port u32 vs u16 | ✓ `http_port` changed to `u16`. All `as u32` casts removed. |
---
## Suggestions — Addressed
| # | Suggestion | Status |
|---|-----------|--------|
| S1 | Remove dead code | ✓ `log_rate_limit!`, `log_config_reload!`, `format_event_fields`, 4 unused `ProxyError` variants, `build_multi_domain_server_config`, `SniCertResolver`, `TestUpstream::url()`/`upstream_addr()` all removed. `KvVisitor` gated with `#[cfg(test)]`. |
| S3 | Log root cert count | ✓ `root_certs()` logs count at info, warns if zero. |
| S4 | Admin socket resource limits | ✓ (same as W4) |
| S5 | Consolidate config types | ✓ (same as W6) |
| S6 | TokenBucket fields private | ✓ (same as W10) |
| S8 | Connect timeout ceiling | ✓ (same as C3) |
| S10 | ConnectInfo rate limit tests | ✓ 3 new tests in `src/rate_limit/mod.rs`. |
| S2, S7, S9 | Eviction config, status info, acme_contact Vec | Deferred (Phase 2 / config format change). |
---
## New Findings
### N1. Test Name `test_health_check_disabled_when_port_zero` Still Misleading
**File**: `tests/integration_test.rs:82`
**Problem**: Commit `855c0f1` claimed to rename this test but the name is
unchanged. Port 0 means "OS picks a random port" — the health check listener
still starts. The actual disable logic is `health_check_port > 0` in
`main.rs:94`. The test name implies it tests the disable path but it tests the
random-port binding path.
**Severity**: Suggestion — rename to `test_health_check_binds_random_port_when_zero`.
---
### N2. `FullConfig` Test Struct Duplicated in `static_config.rs`
**File**: `src/config/static_config.rs:189-206`
**Problem**: The test module defines its own `FullConfig` struct with
`#[allow(dead_code)]` that mirrors the production `FullConfig` in
`config/mod.rs`. If fields change, both must be updated. This is test
infrastructure only, so low risk.
**Severity**: Suggestion — use the production `FullConfig` from `config/mod.rs`
in these tests, or accept the duplication as intentional test isolation.
---
### N3. `is_valid_upstream` Accepts Bare IPv6 Without Brackets
**File**: `src/config/validation.rs:327`
**Problem**: `::1:3000` passes validation because `::1` parses as a valid
`IpAddr`. The config spec says IPv6 should use bracket notation
(`[::1]:3000`). In practice nobody writes bare IPv6 with a port, and the
address would still work, so this is extremely low risk.
**Severity**: Suggestion — could tighten by requiring brackets for IPv6, but
not worth the churn.
---
## Summary
All 4 critical and 11 of 12 warning findings are properly fixed. W7 was
explicitly deferred as acceptable for Phase 1. W8 (test rename) was claimed
fixed but the name wasn't actually changed — minor. Three new suggestion-level
notes, none blocking.
The codebase is in significantly better shape than at review #003. The rate
limiter spoofing vulnerability (C1) was the most important fix and it's done
correctly with good test coverage.
| Severity | Count |
|----------|-------|
| Critical | 0 |
| Warning | 0 |
| Suggestion | 3 (new, minor) |