Indexer hardening: rate limits, max_maker_fills, logging, body limits (#376) #379

Closed
opened 2026-06-13 07:56:26 +00:00 by PlasticDigits · 16 comments
PlasticDigits commented 2026-06-13 07:56:26 +00:00 (Migrated from gitlab.com)

Parent

Remediation bundle for #376 — Full security report.

Covers approved findings: M-05, M-06, L-04, L-05, L-08, L-09.

Current codebase

  • Rate limit warning (M-05): indexer/src/config.rs clamps zero limits to 60/10 in prod with tracing::warn. indexer/src/api/mod.rs apply_rate_limit_layer skips governors when rps == 0. Dev/QA with both limits at 0 has no startup warning.
  • max_maker_fills cap (M-06): On-chain MAX_MAKER_FILLS_HARD_CAP = 100 in smartcontracts/packages/dex-common/src/pair.rs L26. Indexer DB sim uses stale cap of 30 in indexer/src/api/db_orderbook_sim.rs L15. GET route solve LCD path in indexer/src/api/route_solver.rs L719 uses max_maker_fills.max(1) with no upper bound. Frontend uses 100 in hybridBookWalkLimits.ts.
  • LCD logging (L-04): indexer/src/lcd/mod.rs L89–127 logs full upstream URL and body snippets at WARN.
  • Blacklist-check status (L-05): indexer/src/api/compliance.rs L82–86 maps LCD failures to internal_err → HTTP 500 instead of 502/503.
  • POST body limit (L-08): indexer/src/api/mod.rs build_router has no RequestBodyLimitLayer on POST /api/v1/route/solve.
  • URL encoding (L-09): frontend-dapp/src/services/indexer/client.ts — getPair/getTrader interpolate path segments raw; getTokenDetail already uses encodeURIComponent.

Why needed

Unbounded max_maker_fills and disabled rate limits enable DoS against the indexer and upstream LCD. Logging and status-code hygiene reduce ops leakage. Body limits and URL encoding close minor abuse vectors.

Constraints / guardrails

  • M-06: Do not hardcode 30. Run benchmark (LocalTerra / existing #309-style harness) to pick cap; align indexer with on-chain MAX_MAKER_FILLS_HARD_CAP (100) or benchmarked value — "ideally much higher than 30."
  • M-05: Warning only — prod clamp behavior (#363) stays; add warn when both rate limits are 0 in any mode.
  • L-09: Frontend-only fix in indexer client; indexer should still accept normal bech32 addresses.

Relevant files

Area Paths
Config / rate limits indexer/src/config.rs, indexer/src/api/mod.rs, indexer/src/main.rs
Route solve indexer/src/api/route_solver.rs, indexer/src/api/db_orderbook_sim.rs
LCD client indexer/src/lcd/mod.rs
Compliance indexer/src/api/compliance.rs
Frontend client frontend-dapp/src/services/indexer/client.ts
On-chain reference smartcontracts/packages/dex-common/src/pair.rs
Tests indexer/tests/security.rs, indexer/tests/api_route_solve.rs
Docs docs/indexer-invariants.md, docs/operator-secrets.md
  1. M-05: After config load in config.rs or main.rs, emit tracing::warn! when RATE_LIMIT_RPS == 0 && RATE_LIMIT_LCD_HEAVY_RPS == 0 regardless of RUN_MODE.
  2. M-06: Benchmark route-solve + hybrid sim with varying max_maker_fills (1, 8, 30, 100) under LocalTerra; document p99 latency and LCD fanout. Unify MAX_MAKER_FILLS_HARD_CAP across db_orderbook_sim.rs, route_solver.rs GET path, and chain constant. Clamp GET query param to benchmarked cap.
  3. L-04: Redact host/path in WARN logs; move full URL/body to DEBUG.
  4. L-05: Use shared LCD error mapper (like api/pairs.rs) → 502/503 with generic client body.
  5. L-08: Add tower_http::limit::RequestBodyLimitLayer (64–256 KiB) on POST route solve.
  6. L-09: encodeURIComponent on all path params in client.ts (getPair, getTrader, etc.).

Acceptance criteria

  • Startup warns when both rate limits are zero (dev and prod).
  • Benchmark results documented; GET max_maker_fills clamped to unified cap (aligned with chain, not stale 30).
  • LCD WARN logs omit sensitive upstream details.
  • blacklist-check returns 502/503 on LCD failure, not 500.
  • POST route solve rejects oversized bodies with 413.
  • Indexer client URL-encodes all path segments.

Test plan

  • make setup-indexer-postgres then make test-indexer-integration
  • cd indexer && cargo test --test security
  • cd indexer && cargo test --test api_route_solve -- --test-threads=1
  • frontend-dapp: src/services/indexer/__tests__/client.test.ts
  • Benchmark script run against LocalTerra (make setup-cloud-localterra or existing bench harness)

Attack / abuse test plan

  • GET /api/v1/route/solve?...&max_maker_fills=4294967295 — must clamp to cap, complete within timeout.
  • Flood with RATE_LIMIT_RPS=0 in dev — warning logged at startup.
  • POST oversized hybrid_by_hop JSON (> limit) — 413, no OOM.
  • blacklist-check with LCD down — 502/503, generic body, no internal host in response.
  • Client request with special chars in pair address param — correct encoding, no path injection.

Verification criteria

  • indexer/tests/security.rs includes new cases for M-05, L-05, L-08.
  • Constants aligned: grep shows single MAX_MAKER_FILLS_HARD_CAP source of truth across indexer + chain.
  • No regression in route solve cache (#283) or rate limit behavior in prod mode.
## Parent Remediation bundle for [#376 — Full security report](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/376). Covers approved findings: **M-05**, **M-06**, **L-04**, **L-05**, **L-08**, **L-09**. ## Current codebase - **Rate limit warning (M-05):** `indexer/src/config.rs` clamps zero limits to 60/10 in prod with `tracing::warn`. `indexer/src/api/mod.rs` `apply_rate_limit_layer` skips governors when `rps == 0`. Dev/QA with both limits at 0 has **no** startup warning. - **max_maker_fills cap (M-06):** On-chain `MAX_MAKER_FILLS_HARD_CAP = 100` in `smartcontracts/packages/dex-common/src/pair.rs` L26. Indexer DB sim uses stale cap of 30 in `indexer/src/api/db_orderbook_sim.rs` L15. GET route solve LCD path in `indexer/src/api/route_solver.rs` L719 uses `max_maker_fills.max(1)` with **no upper bound**. Frontend uses 100 in `hybridBookWalkLimits.ts`. - **LCD logging (L-04):** `indexer/src/lcd/mod.rs` L89–127 logs full upstream URL and body snippets at `WARN`. - **Blacklist-check status (L-05):** `indexer/src/api/compliance.rs` L82–86 maps LCD failures to `internal_err` → HTTP 500 instead of 502/503. - **POST body limit (L-08):** `indexer/src/api/mod.rs` `build_router` has no `RequestBodyLimitLayer` on `POST /api/v1/route/solve`. - **URL encoding (L-09):** `frontend-dapp/src/services/indexer/client.ts` — `getPair`/`getTrader` interpolate path segments raw; `getTokenDetail` already uses `encodeURIComponent`. ## Why needed Unbounded `max_maker_fills` and disabled rate limits enable DoS against the indexer and upstream LCD. Logging and status-code hygiene reduce ops leakage. Body limits and URL encoding close minor abuse vectors. ## Constraints / guardrails - **M-06:** Do **not** hardcode 30. Run benchmark (LocalTerra / existing #309-style harness) to pick cap; align indexer with on-chain `MAX_MAKER_FILLS_HARD_CAP` (100) or benchmarked value — "ideally much higher than 30." - **M-05:** Warning only — prod clamp behavior (#363) stays; add warn when **both** rate limits are 0 in any mode. - **L-09:** Frontend-only fix in indexer client; indexer should still accept normal bech32 addresses. ## Relevant files | Area | Paths | |------|-------| | Config / rate limits | `indexer/src/config.rs`, `indexer/src/api/mod.rs`, `indexer/src/main.rs` | | Route solve | `indexer/src/api/route_solver.rs`, `indexer/src/api/db_orderbook_sim.rs` | | LCD client | `indexer/src/lcd/mod.rs` | | Compliance | `indexer/src/api/compliance.rs` | | Frontend client | `frontend-dapp/src/services/indexer/client.ts` | | On-chain reference | `smartcontracts/packages/dex-common/src/pair.rs` | | Tests | `indexer/tests/security.rs`, `indexer/tests/api_route_solve.rs` | | Docs | `docs/indexer-invariants.md`, `docs/operator-secrets.md` | ## Recommended direction 1. **M-05:** After config load in `config.rs` or `main.rs`, emit `tracing::warn!` when `RATE_LIMIT_RPS == 0 && RATE_LIMIT_LCD_HEAVY_RPS == 0` regardless of `RUN_MODE`. 2. **M-06:** Benchmark route-solve + hybrid sim with varying `max_maker_fills` (1, 8, 30, 100) under LocalTerra; document p99 latency and LCD fanout. Unify `MAX_MAKER_FILLS_HARD_CAP` across `db_orderbook_sim.rs`, `route_solver.rs` GET path, and chain constant. Clamp GET query param to benchmarked cap. 3. **L-04:** Redact host/path in WARN logs; move full URL/body to DEBUG. 4. **L-05:** Use shared LCD error mapper (like `api/pairs.rs`) → 502/503 with generic client body. 5. **L-08:** Add `tower_http::limit::RequestBodyLimitLayer` (64–256 KiB) on POST route solve. 6. **L-09:** `encodeURIComponent` on all path params in `client.ts` (`getPair`, `getTrader`, etc.). ## Acceptance criteria - [ ] Startup warns when both rate limits are zero (dev and prod). - [ ] Benchmark results documented; GET `max_maker_fills` clamped to unified cap (aligned with chain, not stale 30). - [ ] LCD WARN logs omit sensitive upstream details. - [ ] `blacklist-check` returns 502/503 on LCD failure, not 500. - [ ] POST route solve rejects oversized bodies with 413. - [ ] Indexer client URL-encodes all path segments. ## Test plan - `make setup-indexer-postgres` then `make test-indexer-integration` - `cd indexer && cargo test --test security` - `cd indexer && cargo test --test api_route_solve -- --test-threads=1` - `frontend-dapp`: `src/services/indexer/__tests__/client.test.ts` - Benchmark script run against LocalTerra (`make setup-cloud-localterra` or existing bench harness) ## Attack / abuse test plan - `GET /api/v1/route/solve?...&max_maker_fills=4294967295` — must clamp to cap, complete within timeout. - Flood with `RATE_LIMIT_RPS=0` in dev — warning logged at startup. - POST oversized `hybrid_by_hop` JSON (> limit) — 413, no OOM. - `blacklist-check` with LCD down — 502/503, generic body, no internal host in response. - Client request with special chars in pair address param — correct encoding, no path injection. ## Verification criteria - `indexer/tests/security.rs` includes new cases for M-05, L-05, L-08. - Constants aligned: grep shows single `MAX_MAKER_FILLS_HARD_CAP` source of truth across indexer + chain. - No regression in route solve cache (#283) or rate limit behavior in prod mode.
PlasticDigits commented 2026-06-13 07:56:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #376

mentioned in issue #376
PlasticDigits commented 2026-06-13 10:10:50 +00:00 (Migrated from gitlab.com)

mentioned in commit ca446a5c07

mentioned in commit ca446a5c07761c8c0aa8d6dd5aadb91e40a10e97
PlasticDigits commented 2026-06-13 10:11:05 +00:00 (Migrated from gitlab.com)

mentioned in merge request !903

mentioned in merge request !903
ghost1 commented 2026-06-13 10:14:05 +00:00 (Migrated from gitlab.com)

mentioned in commit 43e21b2b52

mentioned in commit 43e21b2b52ff780421b9b813b2a01fff23409549
PlasticDigits commented 2026-06-13 10:35:31 +00:00 (Migrated from gitlab.com)

mentioned in commit 9f1d4cb27d

mentioned in commit 9f1d4cb27d96a12905b44daff6d6ea059d2fc740
PlasticDigits commented 2026-06-13 13:57:40 +00:00 (Migrated from gitlab.com)

Verification — #379 (Indexer hardening: rate limits, max_maker_fills, logging, body limits)

Branch: main @ origin/main (no local changes)
Verifier: Cloud Agent QA
Result: FAIL — acceptance criteria not implemented on main. Issue left open.


Acceptance criteria

Criterion Result Evidence
Startup warns when both rate limits are zero (dev and prod) FAIL indexer/src/config.rs only warns per-knob when RUN_MODE=prod and that knob is 0 (clamps to 60/10). No tracing::warn! when both RATE_LIMIT_RPS=0 and RATE_LIMIT_LCD_HEAVY_RPS=0 in dev/QA. apply_rate_limit_layer skips governors when rps == 0 (indexer/src/api/mod.rs L133–135).
Benchmark documented; GET max_maker_fills clamped to unified cap (≠ stale 30) FAIL Indexer DB sim still MAX_MAKER_FILLS_HARD_CAP: u32 = 30 (indexer/src/api/db_orderbook_sim.rs L15). Chain/frontend use 100 (smartcontracts/packages/dex-common/src/pair.rs L26, hybridBookWalkLimits.ts L10). GET path uses max_maker_fills.max(1) with no upper bound (indexer/src/api/route_solver.rs L719). No benchmark doc/script for #379 max_maker_fills sweep.
LCD WARN logs omit sensitive upstream details FAIL indexer/src/lcd/mod.rs L89–127 still logs full full_url, HTTP status, and body snippets at tracing::warn!.
blacklist-check returns 502/503 on LCD failure, not 500 FAIL indexer/src/api/compliance.rs L82–86 maps LCD errors via internal_err → HTTP 500. Shared lcd_gateway_err exists (indexer/src/api/errors.rs) but is not used here.
POST route solve rejects oversized bodies with 413 FAIL No RequestBodyLimitLayer in build_router (indexer/src/api/mod.rs). Grep: no 413 / PAYLOAD_TOO_LARGE handling for route solve.
Indexer client URL-encodes all path segments FAIL getTokenDetail / getTokenPairs encode (client.ts L400–407). getPair (L102), getTrader (L281), getTraderPositions (L390), and other pair/trader subresources interpolate addresses raw. No encodeURIComponent tests in client.test.ts.

Verification criteria (issue body)

Check Result Evidence
indexer/tests/security.rs includes M-05, L-05, L-08 cases FAIL No tests for dual-zero rate-limit startup warning, blacklist-check LCD failure status, or POST body 413. Existing lcd_failure_returns_sanitized_502_body covers order-book-head only.
Single MAX_MAKER_FILLS_HARD_CAP source of truth FAIL Three values: chain 100, indexer DB sim 30, frontend 100.
No regression: route-solve cache / prod rate limits PASS cargo test --test api_route_solve -- --test-threads=1 — 23/23 ok. cargo test --test security -- --test-threads=1 — 25/25 ok (includes #363 prod LCD-heavy clamp).

Test plan (issue)

Command Result Notes
make setup-indexer-postgres PASS Postgres + indexer/.env ready.
make test-indexer-integration FAIL (unrelated) Lib test api::db_orderbook_sim::tests::zero_reserve_mirror_returns_no_output failed (pre-existing; not #379 scope).
cd indexer && cargo test --test security PASS (serial) 25/25 with --test-threads=1. 8/25 fail under default parallelism (DB seed races — pre-existing).
cd indexer && cargo test --test api_route_solve -- --test-threads=1 PASS 23/23.
frontend-dapp client.test.ts PASS 17/17 — no URL-encoding coverage for getPair/getTrader.
LocalTerra max_maker_fills benchmark harness SKIP No #379 benchmark artifact; scripts/qa/verify-issue-309.sh is expired-park gas only.

Attack / abuse plan

Scenario Result Notes
GET …/route/solve?max_maker_fills=4294967295 clamped FAIL (code) No upper clamp on GET param.
Both rate limits 0 → startup warning FAIL No warning in dev.
POST oversized hybrid_by_hop JSON → 413 FAIL No body limit layer.
blacklist-check LCD down → 502/503 FAIL Maps to 500 via internal_err.
Client path encoding / injection FAIL Raw interpolation in getPair/getTrader.

  1. Implement the six items in the issue “Recommended direction” (M-05, M-06 + benchmark, L-04, L-05, L-08, L-09).
  2. Add security.rs cases for M-05, L-05, L-08; extend client.test.ts for path encoding.
  3. Unify MAX_MAKER_FILLS_HARD_CAP (import from shared constant or re-export chain value 100).
  4. Run make test-indexer-integration serially or fix the zero_reserve_mirror lib test flake before release sign-off.

No repo changes from this verification pass — no MR opened.

## Verification — #379 (Indexer hardening: rate limits, max_maker_fills, logging, body limits) **Branch:** `main` @ `origin/main` (no local changes) **Verifier:** Cloud Agent QA **Result:** **FAIL** — acceptance criteria not implemented on `main`. Issue left **open**. --- ### Acceptance criteria | Criterion | Result | Evidence | |-----------|--------|----------| | Startup warns when both rate limits are zero (dev and prod) | **FAIL** | `indexer/src/config.rs` only warns per-knob when `RUN_MODE=prod` and that knob is `0` (clamps to 60/10). No `tracing::warn!` when **both** `RATE_LIMIT_RPS=0` and `RATE_LIMIT_LCD_HEAVY_RPS=0` in dev/QA. `apply_rate_limit_layer` skips governors when `rps == 0` (`indexer/src/api/mod.rs` L133–135). | | Benchmark documented; GET `max_maker_fills` clamped to unified cap (≠ stale 30) | **FAIL** | Indexer DB sim still `MAX_MAKER_FILLS_HARD_CAP: u32 = 30` (`indexer/src/api/db_orderbook_sim.rs` L15). Chain/frontend use **100** (`smartcontracts/packages/dex-common/src/pair.rs` L26, `hybridBookWalkLimits.ts` L10). GET path uses `max_maker_fills.max(1)` with no upper bound (`indexer/src/api/route_solver.rs` L719). No benchmark doc/script for #379 `max_maker_fills` sweep. | | LCD WARN logs omit sensitive upstream details | **FAIL** | `indexer/src/lcd/mod.rs` L89–127 still logs full `full_url`, HTTP status, and body snippets at `tracing::warn!`. | | `blacklist-check` returns 502/503 on LCD failure, not 500 | **FAIL** | `indexer/src/api/compliance.rs` L82–86 maps LCD errors via `internal_err` → HTTP **500**. Shared `lcd_gateway_err` exists (`indexer/src/api/errors.rs`) but is not used here. | | POST route solve rejects oversized bodies with 413 | **FAIL** | No `RequestBodyLimitLayer` in `build_router` (`indexer/src/api/mod.rs`). Grep: no 413 / `PAYLOAD_TOO_LARGE` handling for route solve. | | Indexer client URL-encodes all path segments | **FAIL** | `getTokenDetail` / `getTokenPairs` encode (`client.ts` L400–407). `getPair` (L102), `getTrader` (L281), `getTraderPositions` (L390), and other pair/trader subresources interpolate addresses raw. No `encodeURIComponent` tests in `client.test.ts`. | --- ### Verification criteria (issue body) | Check | Result | Evidence | |-------|--------|----------| | `indexer/tests/security.rs` includes M-05, L-05, L-08 cases | **FAIL** | No tests for dual-zero rate-limit startup warning, `blacklist-check` LCD failure status, or POST body 413. Existing `lcd_failure_returns_sanitized_502_body` covers order-book-head only. | | Single `MAX_MAKER_FILLS_HARD_CAP` source of truth | **FAIL** | Three values: chain **100**, indexer DB sim **30**, frontend **100**. | | No regression: route-solve cache / prod rate limits | **PASS** | `cargo test --test api_route_solve -- --test-threads=1` — 23/23 ok. `cargo test --test security -- --test-threads=1` — 25/25 ok (includes #363 prod LCD-heavy clamp). | --- ### Test plan (issue) | Command | Result | Notes | |---------|--------|-------| | `make setup-indexer-postgres` | **PASS** | Postgres + `indexer/.env` ready. | | `make test-indexer-integration` | **FAIL** (unrelated) | Lib test `api::db_orderbook_sim::tests::zero_reserve_mirror_returns_no_output` failed (pre-existing; not #379 scope). | | `cd indexer && cargo test --test security` | **PASS** (serial) | 25/25 with `--test-threads=1`. 8/25 fail under default parallelism (DB seed races — pre-existing). | | `cd indexer && cargo test --test api_route_solve -- --test-threads=1` | **PASS** | 23/23. | | `frontend-dapp` `client.test.ts` | **PASS** | 17/17 — no URL-encoding coverage for `getPair`/`getTrader`. | | LocalTerra `max_maker_fills` benchmark harness | **SKIP** | No #379 benchmark artifact; `scripts/qa/verify-issue-309.sh` is expired-park gas only. | --- ### Attack / abuse plan | Scenario | Result | Notes | |----------|--------|-------| | `GET …/route/solve?max_maker_fills=4294967295` clamped | **FAIL** (code) | No upper clamp on GET param. | | Both rate limits `0` → startup warning | **FAIL** | No warning in dev. | | POST oversized `hybrid_by_hop` JSON → 413 | **FAIL** | No body limit layer. | | `blacklist-check` LCD down → 502/503 | **FAIL** | Maps to 500 via `internal_err`. | | Client path encoding / injection | **FAIL** | Raw interpolation in `getPair`/`getTrader`. | --- ### Recommended next steps 1. Implement the six items in the issue “Recommended direction” (M-05, M-06 + benchmark, L-04, L-05, L-08, L-09). 2. Add `security.rs` cases for M-05, L-05, L-08; extend `client.test.ts` for path encoding. 3. Unify `MAX_MAKER_FILLS_HARD_CAP` (import from shared constant or re-export chain value **100**). 4. Run `make test-indexer-integration` serially or fix the `zero_reserve_mirror` lib test flake before release sign-off. No repo changes from this verification pass — **no MR opened**.
PlasticDigits commented 2026-06-14 03:04:14 +00:00 (Migrated from gitlab.com)

Verification — #379 (Indexer hardening: rate limits, max_maker_fills, logging, body limits)

Branch: main @ origin/main (clean working tree)
Verifier: Cloud Agent QA
Result: FAIL — acceptance criteria not implemented on main. Issue remains open.


Acceptance criteria

Criterion Result Evidence
Startup warns when both rate limits are zero (dev and prod) FAIL indexer/src/config.rs L197–221 warns and clamps each knob only when RUN_MODE=prod and that knob is 0. No tracing::warn! when both RATE_LIMIT_RPS=0 and RATE_LIMIT_LCD_HEAVY_RPS=0 in dev/QA. apply_rate_limit_layer skips governors when rps == 0 (indexer/src/api/mod.rs L129–135).
Benchmark documented; GET max_maker_fills clamped to unified cap (≠ stale 30) FAIL Indexer DB sim still MAX_MAKER_FILLS_HARD_CAP: u32 = 30 (indexer/src/api/db_orderbook_sim.rs L15). Chain/frontend use 100 (smartcontracts/packages/dex-common/src/pair.rs L26, hybridBookWalkLimits.ts L10). GET path uses max_maker_fills.max(1) with no upper bound (indexer/src/api/route_solver.rs L719). No #379 benchmark doc/script for max_maker_fills sweep.
LCD WARN logs omit sensitive upstream details FAIL indexer/src/lcd/mod.rs L89–127 logs full full_url, HTTP status, and body snippets at tracing::warn!.
blacklist-check returns 502/503 on LCD failure, not 500 FAIL indexer/src/api/compliance.rs L82–86 maps LCD errors via internal_err → HTTP 500. Shared lcd_gateway_err exists (indexer/src/api/errors.rs L9) but is not used here.
POST route solve rejects oversized bodies with 413 FAIL No RequestBodyLimitLayer in build_router (indexer/src/api/mod.rs L368+). Grep: no 413 / payload-too-large handling for route solve.
Indexer client URL-encodes all path segments FAIL getTokenDetail / getTokenPairs encode (client.ts L400–407). getPair (L102), getTrader (L281), getTraderPositions (L390), and 12+ other pair/trader subresources interpolate addresses raw. No encodeURIComponent tests in client.test.ts.

Verification criteria (issue body)

Check Result Evidence
indexer/tests/security.rs includes M-05, L-05, L-08 cases FAIL No tests for dual-zero rate-limit startup warning, blacklist-check LCD failure status, or POST body 413. Existing lcd_failure_returns_sanitized_502_body covers order-book-head only.
Single MAX_MAKER_FILLS_HARD_CAP source of truth FAIL Three values: chain 100, indexer DB sim 30, frontend 100.
No regression: route-solve cache / prod rate limits PASS cargo test --test api_route_solve -- --test-threads=1 — 23/23 ok. cargo test --test security -- --test-threads=1 — 25/25 ok (includes #363 prod LCD-heavy clamp).

Test plan (issue)

Command Result Notes
make setup-indexer-postgres PASS Postgres + indexer/.env ready.
make test-indexer-integration FAIL (unrelated) Lib test api::db_orderbook_sim::tests::zero_reserve_mirror_returns_no_output failed (pre-existing; not #379 scope). 148/149 lib tests passed.
cd indexer && cargo test --test security -- --test-threads=1 PASS 25/25. Intermittent failures without --test-threads=1 (DB seed races — pre-existing).
cd indexer && cargo test --test api_route_solve -- --test-threads=1 PASS 23/23.
frontend-dapp client.test.ts PASS 17/17 — no URL-encoding coverage for getPair/getTrader.
LocalTerra max_maker_fills benchmark harness SKIP No #379 benchmark artifact; scripts/qa/verify-issue-309.sh is expired-park gas only.

Attack / abuse plan

Scenario Result Notes
GET …/route/solve?max_maker_fills=4294967295 clamped FAIL (code) No upper clamp on GET param (route_solver.rs L719).
Both rate limits 0 → startup warning FAIL No warning in dev when both knobs are zero.
POST oversized hybrid_by_hop JSON → 413 FAIL No body limit layer on POST route solve.
blacklist-check LCD down → 502/503 FAIL Maps to 500 via internal_err.
Client path encoding / injection FAIL Raw interpolation in getPair/getTrader and related helpers.

Follow-ups

  1. Implement the six items in the issue “Recommended direction” (M-05, M-06 + benchmark, L-04, L-05, L-08, L-09).
  2. Add security.rs cases for M-05, L-05, L-08; extend client.test.ts for path encoding.
  3. Unify MAX_MAKER_FILLS_HARD_CAP at 100 (align indexer with chain constant).
  4. Fix or quarantine zero_reserve_mirror_returns_no_output lib test flake before release sign-off.

No repo changes from this verification pass — no MR opened.

## Verification — #379 (Indexer hardening: rate limits, max_maker_fills, logging, body limits) **Branch:** `main` @ `origin/main` (clean working tree) **Verifier:** Cloud Agent QA **Result:** **FAIL** — acceptance criteria not implemented on `main`. Issue remains **open**. --- ### Acceptance criteria | Criterion | Result | Evidence | |-----------|--------|----------| | Startup warns when both rate limits are zero (dev and prod) | **FAIL** | `indexer/src/config.rs` L197–221 warns and clamps each knob only when `RUN_MODE=prod` and that knob is `0`. No `tracing::warn!` when **both** `RATE_LIMIT_RPS=0` and `RATE_LIMIT_LCD_HEAVY_RPS=0` in dev/QA. `apply_rate_limit_layer` skips governors when `rps == 0` (`indexer/src/api/mod.rs` L129–135). | | Benchmark documented; GET `max_maker_fills` clamped to unified cap (≠ stale 30) | **FAIL** | Indexer DB sim still `MAX_MAKER_FILLS_HARD_CAP: u32 = 30` (`indexer/src/api/db_orderbook_sim.rs` L15). Chain/frontend use **100** (`smartcontracts/packages/dex-common/src/pair.rs` L26, `hybridBookWalkLimits.ts` L10). GET path uses `max_maker_fills.max(1)` with no upper bound (`indexer/src/api/route_solver.rs` L719). No #379 benchmark doc/script for `max_maker_fills` sweep. | | LCD WARN logs omit sensitive upstream details | **FAIL** | `indexer/src/lcd/mod.rs` L89–127 logs full `full_url`, HTTP status, and body snippets at `tracing::warn!`. | | `blacklist-check` returns 502/503 on LCD failure, not 500 | **FAIL** | `indexer/src/api/compliance.rs` L82–86 maps LCD errors via `internal_err` → HTTP **500**. Shared `lcd_gateway_err` exists (`indexer/src/api/errors.rs` L9) but is not used here. | | POST route solve rejects oversized bodies with 413 | **FAIL** | No `RequestBodyLimitLayer` in `build_router` (`indexer/src/api/mod.rs` L368+). Grep: no 413 / payload-too-large handling for route solve. | | Indexer client URL-encodes all path segments | **FAIL** | `getTokenDetail` / `getTokenPairs` encode (`client.ts` L400–407). `getPair` (L102), `getTrader` (L281), `getTraderPositions` (L390), and 12+ other pair/trader subresources interpolate addresses raw. No `encodeURIComponent` tests in `client.test.ts`. | --- ### Verification criteria (issue body) | Check | Result | Evidence | |-------|--------|----------| | `indexer/tests/security.rs` includes M-05, L-05, L-08 cases | **FAIL** | No tests for dual-zero rate-limit startup warning, `blacklist-check` LCD failure status, or POST body 413. Existing `lcd_failure_returns_sanitized_502_body` covers order-book-head only. | | Single `MAX_MAKER_FILLS_HARD_CAP` source of truth | **FAIL** | Three values: chain **100**, indexer DB sim **30**, frontend **100**. | | No regression: route-solve cache / prod rate limits | **PASS** | `cargo test --test api_route_solve -- --test-threads=1` — 23/23 ok. `cargo test --test security -- --test-threads=1` — 25/25 ok (includes #363 prod LCD-heavy clamp). | --- ### Test plan (issue) | Command | Result | Notes | |---------|--------|-------| | `make setup-indexer-postgres` | **PASS** | Postgres + `indexer/.env` ready. | | `make test-indexer-integration` | **FAIL** (unrelated) | Lib test `api::db_orderbook_sim::tests::zero_reserve_mirror_returns_no_output` failed (pre-existing; not #379 scope). 148/149 lib tests passed. | | `cd indexer && cargo test --test security -- --test-threads=1` | **PASS** | 25/25. Intermittent failures without `--test-threads=1` (DB seed races — pre-existing). | | `cd indexer && cargo test --test api_route_solve -- --test-threads=1` | **PASS** | 23/23. | | `frontend-dapp` `client.test.ts` | **PASS** | 17/17 — no URL-encoding coverage for `getPair`/`getTrader`. | | LocalTerra `max_maker_fills` benchmark harness | **SKIP** | No #379 benchmark artifact; `scripts/qa/verify-issue-309.sh` is expired-park gas only. | --- ### Attack / abuse plan | Scenario | Result | Notes | |----------|--------|-------| | `GET …/route/solve?max_maker_fills=4294967295` clamped | **FAIL** (code) | No upper clamp on GET param (`route_solver.rs` L719). | | Both rate limits `0` → startup warning | **FAIL** | No warning in dev when both knobs are zero. | | POST oversized `hybrid_by_hop` JSON → 413 | **FAIL** | No body limit layer on POST route solve. | | `blacklist-check` LCD down → 502/503 | **FAIL** | Maps to 500 via `internal_err`. | | Client path encoding / injection | **FAIL** | Raw interpolation in `getPair`/`getTrader` and related helpers. | --- ### Follow-ups 1. Implement the six items in the issue “Recommended direction” (M-05, M-06 + benchmark, L-04, L-05, L-08, L-09). 2. Add `security.rs` cases for M-05, L-05, L-08; extend `client.test.ts` for path encoding. 3. Unify `MAX_MAKER_FILLS_HARD_CAP` at **100** (align indexer with chain constant). 4. Fix or quarantine `zero_reserve_mirror_returns_no_output` lib test flake before release sign-off. No repo changes from this verification pass — **no MR opened**.
PlasticDigits commented 2026-06-14 05:49:14 +00:00 (Migrated from gitlab.com)

mentioned in commit 1d12feb3c2

mentioned in commit 1d12feb3c2fd9612a55a06358744a5df853bbf6e
PlasticDigits commented 2026-06-14 05:49:28 +00:00 (Migrated from gitlab.com)

mentioned in merge request !910

mentioned in merge request !910
PlasticDigits commented 2026-06-14 07:09:19 +00:00 (Migrated from gitlab.com)

mentioned in commit 30cfd4be2e

mentioned in commit 30cfd4be2e9d74b6f39521485ce153c5a30e5bf4
PlasticDigits commented 2026-06-14 12:54:08 +00:00 (Migrated from gitlab.com)

Verification — #379 (Indexer hardening: rate limits, max_maker_fills, logging, body limits)

Branch: main @ f5a0dc79 (clean working tree)
Verifier: Cloud Agent QA
Result: PASS — all acceptance criteria implemented and verified on main.


Acceptance criteria

Criterion Result Evidence
Startup warns when both rate limits are zero (dev and prod) PASS indexer/src/config.rs L208–211: tracing::warn! when RATE_LIMIT_RPS=0 && RATE_LIMIT_LCD_HEAVY_RPS=0 regardless of RUN_MODE. dev_dual_zero_rate_limits_load_without_clamp in security.rs confirms dev dual-zero loads without clamp.
Benchmark documented; GET max_maker_fills clamped to unified cap (≠ stale 30) PASS indexer/src/hybrid_limits.rs defines MAX_MAKER_FILLS_HARD_CAP = 100; clamp_max_maker_fills used in route_solver.rs and db_orderbook_sim.rs. Benchmark doc: docs/benchmarks/max-maker-fills-route-solve.md; harness: scripts/qa/bench-max-maker-fills-route-solve.sh. Unit test clamps 4294967295 → 100.
LCD WARN logs omit sensitive upstream details PASS indexer/src/lcd/mod.rs L110–120: WARN logs path (redacted via lcd_log_path), status, endpoint idx only; full URL/body at DEBUG.
blacklist-check returns 502/503 on LCD failure, not 500 PASS indexer/src/api/compliance.rs L86 uses lcd_gateway_err. blacklist_check_lcd_failure_returns_sanitized_502 — HTTP 502, generic body, no LCD host in response.
POST route solve rejects oversized bodies with 413 PASS RequestBodyLimitLayer on POST /api/v1/route/solve (api/mod.rs L415–417, 128 KiB). route_solve_post_oversized_body_returns_413 in security.rs.
Indexer client URL-encodes all path segments PASS pathSegment() → encodeURIComponent in client.ts; all pair/trader subresources use it. client.test.ts — 19/19 including slash/special-char encoding tests.

Verification criteria (issue body)

Check Result Evidence
indexer/tests/security.rs includes M-05, L-05, L-08 cases PASS dev_dual_zero_rate_limits_load_without_clamp, blacklist_check_lcd_failure_returns_sanitized_502, route_solve_post_oversized_body_returns_413.
Single MAX_MAKER_FILLS_HARD_CAP source of truth PASS Indexer: hybrid_limits.rs (100); chain: dex-common/pair.rs (100); frontend: hybridBookWalkLimits.ts (100). Stale indexer value 30 removed.
No regression: route-solve cache / prod rate limits PASS cargo test --test api_route_solve -- --test-threads=1 — 23/23. cargo test --test security -- --test-threads=1 — 28/28 (includes #363 prod LCD-heavy clamp).

Test plan (issue)

Command Result Notes
make setup-indexer-postgres PASS Postgres + indexer/.env ready.
make test-indexer-integration FAIL (unrelated) Lib test api::db_orderbook_sim::tests::zero_reserve_mirror_returns_no_output — 151/152 lib tests pass; pre-existing flake, not #379 scope.
cd indexer && cargo test --test security -- --test-threads=1 PASS 28/28.
cd indexer && cargo test --test api_route_solve -- --test-threads=1 PASS 23/23.
frontend-dapp client.test.ts PASS 19/19 with URL-encoding coverage.
LocalTerra max_maker_fills benchmark harness SKIP LocalTerra not running (make has-localterra → not provisioned). Benchmark doc + script present; reference table in docs/benchmarks/max-maker-fills-route-solve.md.

Attack / abuse plan

Scenario Result Notes
GET …/route/solve?max_maker_fills=4294967295 clamped PASS hybrid_limits::tests::clamp_rejects_zero_and_overflow; route_solver cache-key tests.
Both rate limits 0 → startup warning PASS config.rs dual-zero tracing::warn!.
POST oversized hybrid_by_hop JSON → 413 PASS route_solve_post_oversized_body_returns_413.
blacklist-check LCD down → 502/503 PASS blacklist_check_lcd_failure_returns_sanitized_502.
Client path encoding / injection PASS client.test.ts encodes / and + in pair/trader paths.

No repo changes from this verification pass — closing issue.

## Verification — #379 (Indexer hardening: rate limits, max_maker_fills, logging, body limits) **Branch:** `main` @ `f5a0dc79` (clean working tree) **Verifier:** Cloud Agent QA **Result:** **PASS** — all acceptance criteria implemented and verified on `main`. --- ### Acceptance criteria | Criterion | Result | Evidence | |-----------|--------|----------| | Startup warns when both rate limits are zero (dev and prod) | **PASS** | `indexer/src/config.rs` L208–211: `tracing::warn!` when `RATE_LIMIT_RPS=0 && RATE_LIMIT_LCD_HEAVY_RPS=0` regardless of `RUN_MODE`. `dev_dual_zero_rate_limits_load_without_clamp` in `security.rs` confirms dev dual-zero loads without clamp. | | Benchmark documented; GET `max_maker_fills` clamped to unified cap (≠ stale 30) | **PASS** | `indexer/src/hybrid_limits.rs` defines `MAX_MAKER_FILLS_HARD_CAP = 100`; `clamp_max_maker_fills` used in `route_solver.rs` and `db_orderbook_sim.rs`. Benchmark doc: `docs/benchmarks/max-maker-fills-route-solve.md`; harness: `scripts/qa/bench-max-maker-fills-route-solve.sh`. Unit test clamps `4294967295` → 100. | | LCD WARN logs omit sensitive upstream details | **PASS** | `indexer/src/lcd/mod.rs` L110–120: WARN logs `path` (redacted via `lcd_log_path`), status, endpoint idx only; full URL/body at DEBUG. | | `blacklist-check` returns 502/503 on LCD failure, not 500 | **PASS** | `indexer/src/api/compliance.rs` L86 uses `lcd_gateway_err`. `blacklist_check_lcd_failure_returns_sanitized_502` — HTTP 502, generic body, no LCD host in response. | | POST route solve rejects oversized bodies with 413 | **PASS** | `RequestBodyLimitLayer` on POST `/api/v1/route/solve` (`api/mod.rs` L415–417, 128 KiB). `route_solve_post_oversized_body_returns_413` in `security.rs`. | | Indexer client URL-encodes all path segments | **PASS** | `pathSegment()` → `encodeURIComponent` in `client.ts`; all pair/trader subresources use it. `client.test.ts` — 19/19 including slash/special-char encoding tests. | --- ### Verification criteria (issue body) | Check | Result | Evidence | |-------|--------|----------| | `indexer/tests/security.rs` includes M-05, L-05, L-08 cases | **PASS** | `dev_dual_zero_rate_limits_load_without_clamp`, `blacklist_check_lcd_failure_returns_sanitized_502`, `route_solve_post_oversized_body_returns_413`. | | Single `MAX_MAKER_FILLS_HARD_CAP` source of truth | **PASS** | Indexer: `hybrid_limits.rs` (100); chain: `dex-common/pair.rs` (100); frontend: `hybridBookWalkLimits.ts` (100). Stale indexer value 30 removed. | | No regression: route-solve cache / prod rate limits | **PASS** | `cargo test --test api_route_solve -- --test-threads=1` — 23/23. `cargo test --test security -- --test-threads=1` — 28/28 (includes #363 prod LCD-heavy clamp). | --- ### Test plan (issue) | Command | Result | Notes | |---------|--------|-------| | `make setup-indexer-postgres` | **PASS** | Postgres + `indexer/.env` ready. | | `make test-indexer-integration` | **FAIL** (unrelated) | Lib test `api::db_orderbook_sim::tests::zero_reserve_mirror_returns_no_output` — 151/152 lib tests pass; pre-existing flake, not #379 scope. | | `cd indexer && cargo test --test security -- --test-threads=1` | **PASS** | 28/28. | | `cd indexer && cargo test --test api_route_solve -- --test-threads=1` | **PASS** | 23/23. | | `frontend-dapp` `client.test.ts` | **PASS** | 19/19 with URL-encoding coverage. | | LocalTerra `max_maker_fills` benchmark harness | **SKIP** | LocalTerra not running (`make has-localterra` → not provisioned). Benchmark doc + script present; reference table in `docs/benchmarks/max-maker-fills-route-solve.md`. | --- ### Attack / abuse plan | Scenario | Result | Notes | |----------|--------|-------| | `GET …/route/solve?max_maker_fills=4294967295` clamped | **PASS** | `hybrid_limits::tests::clamp_rejects_zero_and_overflow`; `route_solver` cache-key tests. | | Both rate limits `0` → startup warning | **PASS** | `config.rs` dual-zero `tracing::warn!`. | | POST oversized `hybrid_by_hop` JSON → 413 | **PASS** | `route_solve_post_oversized_body_returns_413`. | | `blacklist-check` LCD down → 502/503 | **PASS** | `blacklist_check_lcd_failure_returns_sanitized_502`. | | Client path encoding / injection | **PASS** | `client.test.ts` encodes `/` and `+` in pair/trader paths. | --- No repo changes from this verification pass — closing issue.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-14 12:54:09 +00:00
Brouie commented 2026-06-28 22:41:06 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-07-13 10:33:12 +00:00 (Migrated from gitlab.com)

mentioned in issue #485

mentioned in issue #485
PlasticDigits commented 2026-08-22 10:59:15 +00:00 (Migrated from gitlab.com)

mentioned in issue #594

mentioned in issue #594
PlasticDigits commented 2026-08-22 11:02:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #595

mentioned in issue #595
PlasticDigits commented 2026-08-28 09:24:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #694

mentioned in issue #694
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#379
No description provided.