Security: /metrics may be publicly exposed when METRICS_BIND matches API or 0.0.0.0 #125

Closed
opened 2026-05-03 12:04:36 +00:00 by PlasticDigits · 4 comments
PlasticDigits commented 2026-05-03 12:04:36 +00:00 (Migrated from gitlab.com)

Summary

indexer/src/api/mod.rs line 399

If METRICS_BIND is set to 0.0.0.0 or the same as the public API bind address, the /metrics endpoint is publicly accessible, leaking operational metrics (DB query counts, request rates, error rates). Default is env-dependent.

Always bind metrics on 127.0.0.1 (localhost only) and proxy behind authenticated reverse proxy.

## Summary `indexer/src/api/mod.rs` line 399 If `METRICS_BIND` is set to `0.0.0.0` or the same as the public API bind address, the `/metrics` endpoint is publicly accessible, leaking operational metrics (DB query counts, request rates, error rates). Default is env-dependent. ## Recommended fix Always bind metrics on `127.0.0.1` (localhost only) and proxy behind authenticated reverse proxy.
PlasticDigits commented 2026-05-03 12:37:32 +00:00 (Migrated from gitlab.com)

mentioned in commit 3b65750681

mentioned in commit 3b65750681c7a65e3721c8ef6a6d68a8750ca573
PlasticDigits commented 2026-05-03 12:38:20 +00:00 (Migrated from gitlab.com)

Implemented on main (please verify)

Summary: Prometheus GET /metrics is no longer mounted on the public API Axum router. It is served only on a dedicated TCP listener parsed from METRICS_BIND (host:port, or legacy non-: value → 127.0.0.1:METRICS_PORT, default port 9095). DEPLOY_ENV (defaults: production when RUN_MODE=prod, else dev) rejects 0.0.0.0 and :: on that metrics address; set DEPLOY_ENV=qa (or staging) for non-prod hosts that need an all-interfaces bind.

Docs / crosslinks: docs/operator-secrets.md, docs/indexer-invariants.md, docs/environment-matrix.md, runbook + incident template, docs/README.md, skills/AGENTS_LOCALNET_TRADING_SWARM.md.

Verification checklist

  • Production (RUN_MODE=prod, default DEPLOY_ENV): METRICS_BIND=0.0.0.0:9095 → process exits with config error; 127.0.0.1:9095 → OK.
  • DEPLOY_ENV=qa with RUN_MODE=prod: 0.0.0.0:9095 allowed.
  • With metrics enabled, curl to API base URL /metrics returns 404; scrape succeeds only against METRICS_BIND.
  • Logs show both API and metrics listen addresses when metrics are on.

/cc @brouie — please confirm in your environment. Leaving issue open per process.

## Implemented on `main` (please verify) **Summary:** Prometheus `GET /metrics` is no longer mounted on the public API Axum router. It is served only on a **dedicated TCP listener** parsed from `METRICS_BIND` (`host:port`, or legacy non-`:` value → `127.0.0.1:METRICS_PORT`, default port **9095**). `DEPLOY_ENV` (defaults: **production** when `RUN_MODE=prod`, else **dev**) **rejects** `0.0.0.0` and `::` on that metrics address; set `DEPLOY_ENV=qa` (or `staging`) for non-prod hosts that need an all-interfaces bind. **Docs / crosslinks:** `docs/operator-secrets.md`, `docs/indexer-invariants.md`, `docs/environment-matrix.md`, runbook + incident template, `docs/README.md`, `skills/AGENTS_LOCALNET_TRADING_SWARM.md`. **Verification checklist** - [ ] Production (`RUN_MODE=prod`, default `DEPLOY_ENV`): `METRICS_BIND=0.0.0.0:9095` → process exits with config error; `127.0.0.1:9095` → OK. - [ ] `DEPLOY_ENV=qa` with `RUN_MODE=prod`: `0.0.0.0:9095` allowed. - [ ] With metrics enabled, `curl` to API base URL `/metrics` returns **404**; scrape succeeds only against `METRICS_BIND`. - [ ] Logs show both API and metrics listen addresses when metrics are on. /cc @brouie — please confirm in your environment. Leaving issue open per process.
Brouie commented 2026-05-03 23:10:28 +00:00 (Migrated from gitlab.com)

verified on main. ran cargo test --lib config → 10/10 pass, 0 failed. covers all the cases from your checklist: production_rejects_inaddr_any_metrics_bind, production_rejects_ipv6_unspecified_metrics_bind (prod + :: also rejected), production_accepts_loopback_metrics_bind, prod_run_mode_with_deploy_env_qa_allows_inaddr_any, dev_allows_inaddr_any_metrics_bind, metrics_legacy_flag_binds_loopback. on the routing side, src/api/mod.rs is clean — build_metrics_router() at line 411 only mounts /metrics on the dedicated listener (line 410 doc comment is explicit), and lines 437-448 spin metrics up on its own tokio::net::TcpListener::bind(metrics_sa) separate from the API listener via tokio::try_join!. so curl to api base /metrics returning 404 is architecturally guaranteed — it's not on that router at all. line 438 tracing::info!("Prometheus metrics listening on {}") covers the logging item. all 4 checklist items pass at source + unit level, didn't see a need to spin up the binary 4x with env permutations since the config gate the unit tests cover is the same gate the runtime hits.

verified on main. ran `cargo test --lib config` → 10/10 pass, 0 failed. covers all the cases from your checklist: `production_rejects_inaddr_any_metrics_bind`, `production_rejects_ipv6_unspecified_metrics_bind` (prod + :: also rejected), `production_accepts_loopback_metrics_bind`, `prod_run_mode_with_deploy_env_qa_allows_inaddr_any`, `dev_allows_inaddr_any_metrics_bind`, `metrics_legacy_flag_binds_loopback`. on the routing side, src/api/mod.rs is clean — `build_metrics_router()` at line 411 only mounts /metrics on the dedicated listener (line 410 doc comment is explicit), and lines 437-448 spin metrics up on its own `tokio::net::TcpListener::bind(metrics_sa)` separate from the API listener via `tokio::try_join!`. so curl to api base /metrics returning 404 is architecturally guaranteed — it's not on that router at all. line 438 `tracing::info!("Prometheus metrics listening on {}")` covers the logging item. all 4 checklist items pass at source + unit level, didn't see a need to spin up the binary 4x with env permutations since the config gate the unit tests cover is the same gate the runtime hits.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-04 03:41:54 +00:00
PlasticDigits commented 2026-05-26 08:00:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #200

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