Pre-launch: dev-mode zero rate limit with no non-loopback bind guard [SEC-I04] (F01) #458

Closed
opened 2026-06-30 17:59:07 +00:00 by totdking · 14 comments
totdking commented 2026-06-30 17:59:07 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-06-30 18:01:25 +00:00 (Migrated from gitlab.com)

Summary

When RUN_MODE is unset (defaults to Dev) and both RATE_LIMIT_RPS and RATE_LIMIT_LCD_HEAVY_RPS are set to 0, all rate governors are disabled with only a tracing::warn! log entry. The indexer .env template ships with RATE_LIMIT_RPS=0. If an operator deploys the indexer on a non-loopback bind address without setting RUN_MODE=prod, the entire API is unprotected with no hard refusal and no startup check. RUN_MODE=prod correctly clamps both values to 60 and 10 RPS respectively, but there is nothing in the Dev path to catch a misconfigured public deployment.


What Was Checked

  • indexer/src/config.rs lines 208-234: rate limit clamping logic. RUN_MODE=prod enforces minimums. Dev path emits only a tracing::warn! when both are 0.
  • indexer/.env: ships with RATE_LIMIT_RPS=0, RATE_LIMIT_LCD_HEAVY_RPS=10. The global layer is disabled in the template default.
  • No startup check exists that rejects or hard-errors when both rate limits are 0 and the bind address resolves to a non-loopback IP.
  • docs/operator-secrets.md: documents the dual-zero DoS-risk warning but does not require non-loopback check.

Expected (per checklist)

If both rate limits are 0 and the API binds to a non-loopback address, the indexer emits an ERROR-level log and refuses to start (or requires an explicit ALLOW_ZERO_RATE_LIMITS=1 override to proceed). This prevents silent public deployments with no rate protection.


Actual

Zero rate limit on a non-loopback bind address produces only a tracing::warn! log and continues serving traffic unprotected.


Suggested Fix

In Config::from_env(), add a startup check: if both RATE_LIMIT_RPS and RATE_LIMIT_LCD_HEAVY_RPS are 0 and api_bind resolves to a non-loopback IP, emit an ERROR-level log and refuse to start unless ALLOW_ZERO_RATE_LIMITS=1 is explicitly set in env. Update docs/operator-secrets.md to document the new opt-out.


Verification Checklist

  • Startup check added: non-loopback bind + both rate limits 0 causes startup failure or explicit opt-out requirement
  • Test added: config load with both limits 0 and non-loopback bind address triggers the error
  • docs/operator-secrets.md updated to document ALLOW_ZERO_RATE_LIMITS=1 opt-out if added
  • Existing Playwright / local-dev env confirmed unaffected (loopback bind, so no startup failure)

Cc: @PlasticDigits

### Summary When `RUN_MODE` is unset (defaults to Dev) and both `RATE_LIMIT_RPS` and `RATE_LIMIT_LCD_HEAVY_RPS` are set to 0, all rate governors are disabled with only a `tracing::warn!` log entry. The indexer `.env` template ships with `RATE_LIMIT_RPS=0`. If an operator deploys the indexer on a non-loopback bind address without setting `RUN_MODE=prod`, the entire API is unprotected with no hard refusal and no startup check. `RUN_MODE=prod` correctly clamps both values to 60 and 10 RPS respectively, but there is nothing in the Dev path to catch a misconfigured public deployment. --- ### What Was Checked - `indexer/src/config.rs` lines 208-234: rate limit clamping logic. `RUN_MODE=prod` enforces minimums. Dev path emits only a `tracing::warn!` when both are 0. - `indexer/.env`: ships with `RATE_LIMIT_RPS=0`, `RATE_LIMIT_LCD_HEAVY_RPS=10`. The global layer is disabled in the template default. - No startup check exists that rejects or hard-errors when both rate limits are 0 and the bind address resolves to a non-loopback IP. - `docs/operator-secrets.md`: documents the dual-zero DoS-risk warning but does not require non-loopback check. --- ### Expected (per checklist) If both rate limits are 0 and the API binds to a non-loopback address, the indexer emits an ERROR-level log and refuses to start (or requires an explicit `ALLOW_ZERO_RATE_LIMITS=1` override to proceed). This prevents silent public deployments with no rate protection. --- ### Actual Zero rate limit on a non-loopback bind address produces only a `tracing::warn!` log and continues serving traffic unprotected. --- ### Suggested Fix In `Config::from_env()`, add a startup check: if both `RATE_LIMIT_RPS` and `RATE_LIMIT_LCD_HEAVY_RPS` are 0 and `api_bind` resolves to a non-loopback IP, emit an `ERROR`-level log and refuse to start unless `ALLOW_ZERO_RATE_LIMITS=1` is explicitly set in env. Update `docs/operator-secrets.md` to document the new opt-out. --- ### Verification Checklist - [ ] Startup check added: non-loopback bind + both rate limits 0 causes startup failure or explicit opt-out requirement - [ ] Test added: config load with both limits 0 and non-loopback bind address triggers the error - [ ] `docs/operator-secrets.md` updated to document `ALLOW_ZERO_RATE_LIMITS=1` opt-out if added - [ ] Existing Playwright / local-dev env confirmed unaffected (loopback bind, so no startup failure) Cc: @PlasticDigits
totdking commented 2026-06-30 18:09:59 +00:00 (Migrated from gitlab.com)

mentioned in issue #453

mentioned in issue #453
totdking commented 2026-06-30 18:37:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #381

mentioned in issue #381
Brouie commented 2026-06-30 19:05:22 +00:00 (Migrated from gitlab.com)

mentioned in merge request !983

mentioned in merge request !983
Brouie commented 2026-06-30 19:08:39 +00:00 (Migrated from gitlab.com)

mentioned in issue #451

mentioned in issue #451
Brouie commented 2026-06-30 19:08:39 +00:00 (Migrated from gitlab.com)

Done. RATE_LIMIT_RPS=0 + RATE_LIMIT_LCD_HEAVY_RPS=0 only logged a warn! — on a non-loopback bind that's an unthrottled public API.

Added a startup guard in Config::from_env(): in non-prod, if both limits are 0 AND API_BIND is non-loopback, it refuses to start (ConfigError::ZeroRateLimitNonLoopbackBind) unless ALLOW_ZERO_RATE_LIMITS=1 is set (the explicit opt-out for offline/benchmark runs). Prod already clamps both to 60/10 so the guard never fires there, and loopback binds (Playwright / local-dev / the QA indexer) keep working. bind_is_loopback() treats 127/8, ::1, and "localhost" as loopback and errs toward safety on anything else.

Tests (new): non-loopback dual-zero rejected; opt-out allows it; loopback dual-zero loads; prod-clamp loads on a public bind. indexer lib 167/0.

Shipped with #451 in MR !983, branch qa/451-458-indexer-config-guards, commit 6ae40413. Needs review/merge @PlasticDigits — leaving open for verification.

Done. `RATE_LIMIT_RPS=0` + `RATE_LIMIT_LCD_HEAVY_RPS=0` only logged a `warn!` — on a non-loopback bind that's an unthrottled public API. Added a startup guard in `Config::from_env()`: in non-prod, if both limits are 0 AND `API_BIND` is non-loopback, it refuses to start (`ConfigError::ZeroRateLimitNonLoopbackBind`) unless `ALLOW_ZERO_RATE_LIMITS=1` is set (the explicit opt-out for offline/benchmark runs). Prod already clamps both to 60/10 so the guard never fires there, and loopback binds (Playwright / local-dev / the QA indexer) keep working. `bind_is_loopback()` treats 127/8, ::1, and "localhost" as loopback and errs toward safety on anything else. Tests (new): non-loopback dual-zero rejected; opt-out allows it; loopback dual-zero loads; prod-clamp loads on a public bind. indexer lib 167/0. Shipped with #451 in MR !983, branch `qa/451-458-indexer-config-guards`, commit `6ae40413`. Needs review/merge @PlasticDigits — leaving open for verification.
PlasticDigits commented 2026-06-30 21:58:48 +00:00 (Migrated from gitlab.com)

mentioned in commit d326a2e188

mentioned in commit d326a2e18850f73f5694451b58172e3a5d965ee4
PlasticDigits commented 2026-06-30 22:19:07 +00:00 (Migrated from gitlab.com)

mentioned in commit 1c7f9b1c82

mentioned in commit 1c7f9b1c82cfde1f58beb4040f9b105bb51b0f07
PlasticDigits commented 2026-06-30 22:19:15 +00:00 (Migrated from gitlab.com)

mentioned in merge request !992

mentioned in merge request !992
PlasticDigits commented 2026-07-01 00:06:28 +00:00 (Migrated from gitlab.com)

mentioned in commit b1407f63d0

mentioned in commit b1407f63d0bac164d2b02f03c542c80c375db076
PlasticDigits commented 2026-07-01 00:10:46 +00:00 (Migrated from gitlab.com)

mentioned in commit b5752c20c5

mentioned in commit b5752c20c50b8a03699921d32dcff5d0c797ae34
PlasticDigits commented 2026-07-01 00:10:48 +00:00 (Migrated from gitlab.com)

mentioned in merge request !996

mentioned in merge request !996
PlasticDigits commented 2026-07-01 00:44:54 +00:00 (Migrated from gitlab.com)

Verification — #458 [SEC-I04] (F01) — PASS

Verified on main (implementation from MR !983 / 6ae40413).

Checklist

Item Result How verified
Startup check: non-loopback bind + both rate limits 0 refuses start (or explicit opt-out) PASS indexer/src/config.rs — Config::from_env() returns ConfigError::ZeroRateLimitNonLoopbackBind when RUN_MODE≠prod, RATE_LIMIT_RPS=0, RATE_LIMIT_LCD_HEAVY_RPS=0, non-loopback API_BIND, and ALLOW_ZERO_RATE_LIMITS unset. Runtime: cargo run with API_BIND=0.0.0.0 exits with Configuration error: … set ALLOW_ZERO_RATE_LIMITS=1 to override.
Test: dual-zero + non-loopback bind triggers error PASS cargo test --lib config::tests::dev_dual_zero_rate_limits_nonloopback_bind_rejected — ok. Also: opt-out (ALLOW_ZERO_RATE_LIMITS=1), loopback dual-zero, and prod clamp tests — all ok.
docs/operator-secrets.md documents ALLOW_ZERO_RATE_LIMITS=1 PASS Rows for ALLOW_ZERO_RATE_LIMITS, API_BIND, and RATE_LIMIT_RPS reference #458; cross-linked in docs/indexer-invariants.md and skills/AGENTS_INDEXER_API_LCD_SECURITY.md (H7e).
Local dev / Playwright unaffected (loopback bind) PASS scripts/deploy-dex-local.sh writes API_BIND=127.0.0.1, RATE_LIMIT_RPS=0, RATE_LIMIT_LCD_HEAVY_RPS=10 (not dual-zero). dev_dual_zero_rate_limits_loopback_bind_loads passes. Runtime loopback dual-zero reaches Starting CL8Y DEX indexer (fails later on DB timeout — expected without Postgres).

Automated suite

cd indexer && cargo test --lib -- --test-threads=1
# test result: ok. 167 passed; 0 failed

Closing as verified — no additional repo changes required.

## Verification — #458 [SEC-I04] (F01) — **PASS** Verified on `main` (implementation from MR !983 / `6ae40413`). ### Checklist | Item | Result | How verified | |------|--------|--------------| | Startup check: non-loopback bind + both rate limits `0` refuses start (or explicit opt-out) | **PASS** | `indexer/src/config.rs` — `Config::from_env()` returns `ConfigError::ZeroRateLimitNonLoopbackBind` when `RUN_MODE≠prod`, `RATE_LIMIT_RPS=0`, `RATE_LIMIT_LCD_HEAVY_RPS=0`, non-loopback `API_BIND`, and `ALLOW_ZERO_RATE_LIMITS` unset. Runtime: `cargo run` with `API_BIND=0.0.0.0` exits with `Configuration error: … set ALLOW_ZERO_RATE_LIMITS=1 to override`. | | Test: dual-zero + non-loopback bind triggers error | **PASS** | `cargo test --lib config::tests::dev_dual_zero_rate_limits_nonloopback_bind_rejected` — ok. Also: opt-out (`ALLOW_ZERO_RATE_LIMITS=1`), loopback dual-zero, and prod clamp tests — all ok. | | `docs/operator-secrets.md` documents `ALLOW_ZERO_RATE_LIMITS=1` | **PASS** | Rows for `ALLOW_ZERO_RATE_LIMITS`, `API_BIND`, and `RATE_LIMIT_RPS` reference #458; cross-linked in `docs/indexer-invariants.md` and `skills/AGENTS_INDEXER_API_LCD_SECURITY.md` (H7e). | | Local dev / Playwright unaffected (loopback bind) | **PASS** | `scripts/deploy-dex-local.sh` writes `API_BIND=127.0.0.1`, `RATE_LIMIT_RPS=0`, `RATE_LIMIT_LCD_HEAVY_RPS=10` (not dual-zero). `dev_dual_zero_rate_limits_loopback_bind_loads` passes. Runtime loopback dual-zero reaches `Starting CL8Y DEX indexer` (fails later on DB timeout — expected without Postgres). | ### Automated suite ``` cd indexer && cargo test --lib -- --test-threads=1 # test result: ok. 167 passed; 0 failed ``` Closing as verified — no additional repo changes required.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-07-01 00:44:55 +00:00
Brouie commented 2026-07-01 11:30:47 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#458
No description provided.