183 Commits
Author SHA1 Message Date
glm-5.3-flash 9e2d70e59a Add missed reload-gate FD budget check file (review #010 C2) 2026-09-13 07:58:18 +00:00
glm-5.3-flash da4c3a8a12 Cross-check max_connections against RLIMIT_NOFILE at startup/reload (review #010 C2) 2026-09-13 07:58:13 +00:00
glm-5.3-flash 1bbb9c237c Share connection semaphore across listeners (review #010 C4) 2026-09-13 07:46:58 +00:00
glm-5.3-flash 9e26295cf7 Add TLS handshake timeout (review #010 C3)
tls_handshake_timeout_secs (default 10s, must be > 0) wraps
tls_acceptor.accept() in tokio::time::timeout so stalled handshakes
(slowloris / crawler slow-handshake vector) can no longer hold an FD +
semaphore permit indefinitely. Config plumbing through FullConfig /
StaticConfig / validation / reload diff + docs.
2026-09-13 07:16:43 +00:00
glm-5.3-flash 978a362536 Fix review #010 frontmatter YAML + reorder next steps (C3 > C4 > C2) 2026-09-13 07:10:12 +00:00
glm-5.3-flash e1a2d28862 Fix EMFILE accept-loop busy-spin with error backoff + log de-dup (review #010 C1) 2026-09-13 06:58:24 +00:00
glm-5.3-flash 00b05ee4b6 Add review #010: EMFILE accept-loop flood (6M ERROR lines in 2h) + mitigation 2026-09-12 08:04:24 +00:00
glm-5.3-flash 203fcadffd Fix Host header for HTTP/2 upstream requests (Gitea self-check mismatch)
HTTP/2 requests carry the client's host only in the :authority
pseudo-header, with no literal Host header. build_upstream_request
copied headers verbatim, so the upstream request had no Host, and
hyper-util's legacy client (set_host=true default) filled it in from
the upstream URI, sending Host: 127.0.0.1:3000 instead of the client's
host. Backends like Gitea build absolute URLs from that host, causing
the admin self-check mismatch and intermittent wrong asset URLs.

Reconstruct Host from the request URI authority for HTTP/2, preserve
the client's Host header for HTTP/1.1, and let the URI authority win
if both are present.
2026-09-12 05:38:52 +00:00
glm-5.2 71ad3c2905 Fix streaming-body watchdog bug (review #009 C2) + dead-upstream test wiring (C3)
C2: IdleTrackingService decremented in_flight when the handler returned
the Response, but for streaming responses (e.g. git clone) the body is
still in flight, so the watchdog could kill the connection mid-stream.
Fix: wrap the response body in IdleTrackingBody which owns the decrement,
releasing it on body EOF/error/drop (guarded against double-decrement by
an AtomicBool) and calling touch() on each data frame. The handler-level
decrement now only happens on the Err branch. Verified by a reproducer
test (streaming_body_not_killed_by_idle_watchdog) that streams 15 chunks
over 3s with a 500ms idle timeout — red on 4ab8c51, green with this fix.

C3: the two pre-existing idle-timeout tests pointed at 127.0.0.1:18080
where nothing listens, so they validated watchdog behaviour against an
immediate 504 error response (HTTP/1.1 prefix + in_flight==1 hold for
a 504 just as for a 200) and never exercised a real upstream round-trip.
Rewired them to spawn a real TestUpstream and assert HTTP/1.1 200 OK.
Removed the dead no-arg make_proxy_router/start_test_https_server wrappers.

Adds http-body as a direct dep (for the Frame/Body trait) and
tokio-stream as a dev-dep (for the slow-stream test helper).

cargo test: 226 unit + 40 integration green; cargo clippy --all-targets clean.

C1 (deploy to dev1) remains open and out of scope for this repo session.
2026-08-19 09:48:13 +00:00
glm-5.2 ff8819e950 Add review #009: undeployed #008 fix + streaming-body watchdog bug
Documents two issues discovered 2026-08-19:
- C1: review #008 fix (commit 4ab8c51) never deployed to dev1; the
  running binary still leaks FDs (433/1024 after 8 days, ~9 FDs/min).
- C2: the watchdog in IdleTrackingService decrements in_flight when
  the handler returns the Response, but for streaming responses (e.g.
  git clone) the body is still in flight, so the watchdog can kill
  the connection mid-stream. Requires an IdleTrackingBody wrapper
  that owns the decrement until the body reaches EOF or is dropped.
2026-08-19 09:29:42 +00:00
glm-5.2 4ab8c516d8 Fix HTTP/2 idle timeout defeated by keep-alive pings (review #008)
The C1 fix from review #007 added keep_alive_interval(15s) +
keep_alive_timeout(60s) on the HTTP/2 builders. However,
keep_alive_timeout is the timeout for receiving a PONG response to a
PING, not an idle timeout. Well-behaved HTTP/2 clients (including
crawlers) respond to PINGs, resetting the timeout indefinitely. After
6 days of uptime, the proxy had 930+ idle connections (some 145 hours
old) that were never reaped.

Fix: add a custom idle timeout that tracks real request activity and
closes connections with no in-flight requests for longer than the
configured timeout, regardless of PING/PONG activity.

- IdleState: per-connection last_activity + in_flight counter
- IdleTrackingService: tower Service wrapper that updates last_activity
  and in_flight on call() and on response completion (prevents killing
  long-running requests like large git clones)
- idle_watchdog: races against serve_connection in tokio::select!,
  fires only when in_flight == 0 and idle_for >= timeout
- HTTP/1.1 path: header_read_timeout already covers between-request
  idle (verified in hyper source); watchdog is defense-in-depth
- HTTP/2 path: watchdog is the primary fix (hyper has no native
  request-activity-based idle timeout)

Also refactored build_manual_server_config to extract
build_manual_server_config_from_certs for testability (in-memory
certs/keys for integration tests with rcgen).

Tests: 8 unit tests for IdleState/watchdog logic, 2 integration tests
verifying idle connections are closed and active ones are not.

Closes review #008.
2026-08-10 09:35:21 +00:00
glm-5.2 0885486028 Fix connection lifecycle, pool bounds, and log rotation (review #007)
Critical:
- C1: Set server-side idle/keep-alive timeouts on both hyper builders
  (http2 keep_alive_interval=15s + keep_alive_timeout, http1
  header_read_timeout). Both builders now set TokioTimer (required to
  avoid runtime panic). Prevents FD exhaustion from abandoned TLS
  connections — the root cause of the 2026-07-24 outage.
- C2: Add Semaphore(max_connections) gating the accept loop. Provides
  backpressure via OS TCP backlog when all permits are taken.

Warnings:
- W1: Add SIGUSR1 log-reopen handler. New ReopenableFileWriter
  (Arc<ArcSwap<File>> via custom MakeWriter) atomically swaps the log
  file. Enables postrotate logrotate without copytruncate, which caused
  the 1.15GB sparse file that wedged fail2ban.
- W2: Set pool_max_idle_per_host(10) on both upstream clients, bounding
  idle upstream connections per host.
- W3: Add connection_idle_timeout_secs to StaticConfig (default 60).
- W4: Add max_connections to StaticConfig (default 1024).

Both new fields are validated (> 0) and included in static config drift
detection on reload. Docs (config.md, README, ADR-009) updated.
2026-07-28 10:16:26 +00:00
glm-5.2 e803817350 Add fail2ban 4xx/badbots filters, jail backend fix, and review #007
- Add reverse-proxy-4xx and reverse-proxy-badbots fail2ban filters
- Set backend=auto and ignoreip on all jails (fixes silent no-match
  when defaults-debian.conf inherits systemd backend)
- Document three-jail setup and REQUEST log format in README
- Add review #007 covering connection lifecycle, logging, and deployment
  drift triggered by the 2026-07-24 FD exhaustion incident
2026-07-28 10:02:19 +00:00
glm-5.1 f6e62a37ef Update README for admin HTTP API, mark tasks completed
- Replace admin socket section with admin HTTP API section
- Update config table (admin_socket_path → admin_key_path)
- Update project structure (admin/auth.rs, handler.rs, config/mod.rs)
- Update architecture diagram (Admin HTTP API instead of Unix socket)
- Update Docker compose volume (admin-key instead of socket dir)
- Mark all four task files as status: completed with summaries
2026-06-15 06:34:21 +00:00
glm-5.1 0f4e9d596f Update docs: reviews #005/#006 resolved status, AGENTS.md admin refactor
- Review #005: update W2/W5/W6 rows to show resolved status
- Review #005: update reviewed_code to reference new admin files
- Review #006: update status from draft to resolved
- Review #006: update Category 4 (SIGHUP/admin reload) for mtime check
- Review #006: update Category 6 (admin interface) for HTTP API
- Review #006: update P4 and reviewed_code for new file paths
- AGENTS.md: update project structure (admin/, config/mod.rs)
- AGENTS.md: update architecture concepts (admin HTTP, TOCTOU, wildcard)
- AGENTS.md: update config format (admin_key_path)
- AGENTS.md: update testing (admin HTTP tests)
- AGENTS.md: update common modifications (curl instead of socat)
2026-06-15 06:18:12 +00:00
glm-5.1 143ebaae93 Add wildcard bind acceptance/rejection tests for ConfigReloadHandle reload
Tests verify that reload accepts 0.0.0.0 bind when cli_allow_wildcard_bind
is true and rejects it when false.
2026-06-15 06:15:04 +00:00
glm-5.1 c6dda716f4 Add mtime TOCTOU check and wildcard flag to ConfigReloadHandle (ADR-029/030)
Extract shared read_and_validate_config() with before/after mtime check
to detect mid-write config file changes. Add ReloadError enum with
FileChangedDuringRead variant. Return HTTP 409 Conflict on mtime change
from admin reload endpoint. Store cli_allow_wildcard_bind in
ConfigReloadHandle and use it in reload() validation instead of hardcoded
false. Update all ConfigReloadHandle::new() call sites.
2026-06-15 06:13:48 +00:00
glm-5.1 3ea3f56de7 Replace Unix socket admin API with authenticated HTTP admin API (ADR-028)
Remove src/admin/socket.rs and replace with Bearer token HTTP auth on the
health check listener (port 9900). New src/admin/auth.rs provides SHA-256
key hashing with constant-time comparison; src/admin/handler.rs implements
/admin/reload, /admin/status, and /admin/rotate-key. Admin endpoints return
404 when disabled (empty admin_key_path), 401 on bad auth. Config field
renamed admin_socket_path → admin_key_path. Deployment files updated for
key file mount instead of socket directory.
2026-06-15 06:01:35 +00:00
glm-5.1 cfe0ae522d Fix task frontmatter: use pending/open, isolated/docs, review/documentation per taskgraph schema 2026-06-15 05:23:43 +00:00
glm-5.1 161049a17d Add ADR-029/030, implementation tasks, and spec updates for admin socket removal
Security review #005 identified critical vulnerabilities in the Unix domain
socket admin API (C1 symlink race, C2 no auth, C3 info leak, W1-W7, S1-S6).
ADR-028 (already accepted) replaces the socket with an authenticated HTTP
admin API on the health check port. This commit adds the remaining spec work:

- ADR-029: Config file TOCTOU mitigation (mtime check on reload)
- ADR-030: Store cli_allow_wildcard_bind in ConfigReloadHandle for consistent
  reload validation
- Implementation tasks for the admin HTTP migration (fix/admin-http-api),
  TOCTOU fix (fix/config-reload-toctou), and wildcard flag fix
  (fix/wildcard-flag-reload)
- Updated review #005 status to resolved with per-finding disposition
- Resolved OQ-16: POST for state-changing admin endpoints, GET for read-only
- Updated all architecture docs to reference new ADRs, use admin_key_path
  instead of admin_socket_path, and reflect POST method for /admin/reload
2026-06-15 05:19:42 +00:00
glm-5.1 9096ec5873 Add attack surface review #006 — systematic enumeration of untrusted input entry points 2026-06-14 10:27:01 +00:00
glm-5.1 0af1683c49 Add architectural recommendation to replace Unix socket with authenticated HTTP 2026-06-14 09:22:41 +00:00
glm-5.1 38649e9485 Add security review #004 (post-fix) and #005 (admin socket) 2026-06-14 07:45:13 +00:00
glm-5.1 6400c90cb3 Mark review/post-security-fix-review as completed — all 10 criteria PASS 2026-06-12 14:35:34 +00:00
glm-5.1 75d9c263cb Mark fix/upstream-host-validation as completed 2026-06-12 14:34:24 +00:00
glm-5.1 ccb574c259 Merge remote-tracking branch 'origin/fix/fix/upstream-host-validation' 2026-06-12 14:33:56 +00:00
glm-5.1 4ee9486561 feat(upstream-host-validation): validate host part of upstream address in config
Add host part validation to is_valid_upstream: IPv4/IPv6 addresses must parse
as valid IpAddr, bracket-enclosed hosts must parse as IPv6, DNS names must
pass is_valid_hostname. Previously, values like '!!!bad!!!:3000' would pass.
2026-06-12 14:33:48 +00:00
glm-5.1 9730d155d2 Mark fix/token-bucket-field-visibility as completed 2026-06-12 14:33:01 +00:00
glm-5.1 64a651242c Merge remote-tracking branch 'origin/fix/fix/token-bucket-field-visibility' 2026-06-12 14:32:36 +00:00
glm-5.1 cf3f00fc53 fix(token-bucket-field-visibility): make TokenBucket fields private except last_access 2026-06-12 14:32:29 +00:00
glm-5.1 a8155d92f9 Mark fix/tls-mode-wildcard-mismatch as completed 2026-06-12 14:31:08 +00:00
glm-5.1 717ee8e6cd Merge remote-tracking branch 'origin/fix/fix/tls-mode-wildcard-mismatch' 2026-06-12 14:30:08 +00:00
glm-5.1 dbedb8846c Mark fix/rename-misleading-test as completed 2026-06-12 14:29:51 +00:00
glm-5.1 f6e6e15ebf feat(fix/tls-mode-wildcard-mismatch): remove unreachable TlsMode wildcard arm and add count mismatch check
Removed #[non_exhaustive] from TlsMode and the wildcard _ arm in the
match tls_mode block in main.rs. Since setup_tls already rejects unknown
modes with bail!, the wildcard was unreachable dead code. Removing it
ensures the compiler catches future TlsMode variant additions. Added
defense-in-depth count mismatch check after the acceptor loop to catch
any silent listener/acceptor mismatch from zip truncation.
2026-06-12 14:29:48 +00:00
glm-5.1 d9b3a436f1 Merge remote-tracking branch 'origin/fix/fix/rename-misleading-test' 2026-06-12 14:28:41 +00:00
glm-5.1 855c0f1d67 fix(rename-misleading-test): rename misleading test and use from_sites in dynamic config test 2026-06-12 14:28:19 +00:00
glm-5.1 8ff8c71783 Mark fix/rate-limiter-connectinfo-tests as completed 2026-06-12 14:27:08 +00:00
glm-5.1 c2201707bb Merge remote-tracking branch 'origin/fix/fix/rate-limiter-connectinfo-tests' 2026-06-12 14:25:22 +00:00
glm-5.1 603d722ad0 feat(rate-limiter): add ConnectInfo-based tests for rate limiter (ADR-025) 2026-06-12 14:24:17 +00:00
glm-5.1 21186b8265 Mark fix/http-port-type-u16 and fix/log-root-cert-count as completed 2026-06-12 14:21:37 +00:00
glm-5.1 1ae06b0478 Merge remote-tracking branch 'origin/fix/fix/log-root-cert-count' 2026-06-12 14:21:12 +00:00
glm-5.1 8ac39d9cd8 feat(fix/log-root-cert-count): log system root certificate count at startup 2026-06-12 14:20:43 +00:00
glm-5.1 d338dcab38 Merge remote-tracking branch 'origin/fix/fix/http-port-type-u16' 2026-06-12 14:20:37 +00:00
glm-5.1 77117c29eb feat(http-port-type): change http_port from u32 to u16 per spec (W12) 2026-06-12 14:20:15 +00:00
glm-5.1 9a3b8831c7 Mark fix/json-format-without-logfile as completed 2026-06-12 14:19:49 +00:00
glm-5.1 245d2a69ff Merge remote-tracking branch 'origin/fix/fix/json-format-without-logfile' 2026-06-12 14:19:02 +00:00
glm-5.1 01e3b1cd9a Mark 6 fix tasks as completed (admin-socket-resource-limits, upstream-uri-error-handling, remove-dead-code-remnants, acme-contact-validation, admin-socket-reload-mutex-visibility, connector-timeout-ceiling) 2026-06-12 14:18:23 +00:00
glm-5.1 5ea0bee73f fix(logging): add .json() to stdout-only layer in init_json None branch 2026-06-12 14:18:17 +00:00
glm-5.1 d224d7b409 Merge remote-tracking branch 'origin/fix/fix/connector-timeout-ceiling' 2026-06-12 14:16:47 +00:00
glm-5.1 16ec84eea2 Merge remote-tracking branch 'origin/fix/fix/admin-socket-reload-mutex-visibility' 2026-06-12 14:15:13 +00:00