/metrics now goes through the same httprate middleware as POST /api/v1/reports, with its own allowance of 60 requests a minute per client address, a constant in backend/internal/server/routes.go. The limit runs before the basic auth, so past it the answer is 429 and the password is not checked; under it nothing changes. backend/README.md says so.
How the client address reaches the backend, unchanged by this PR:
nginx takes the client address from X-Forwarded-For only on a request from one of the container's TRUSTED_PROXIES (the set_real_ip_from lines bin/entrypoint.sh writes); otherwise it is the connecting address.
nginx passes that address on as the only X-Forwarded-For entry. The proxy_set_header lines sit at server level in nginx.conf, so location = /metrics gets them too.
The backend runs with TRUSTED_PROXIES=127.0.0.1/32 and takes X-Forwarded-For only from a peer in that set. The rate limit counts the address its existing clientIP resolves, as for reports, so clients behind nginx are limited one by one, not together as nginx's loopback address.
The new test uses up one client's allowance on wrong passwords, gets 429 with the right one, and checks that another client behind the same nginx still gets 200.
Judgement call: 60 a minute. The minute slides, so a scraper polling every 2 seconds or less often is never refused; a guesser gets about 60 tries a minute per address.
The limit counts every request to /metrics, right password or not, as the issue asks.
httprate was already a dependency, so go.mod does not change.
Model: opus-5-5
Closes https://git.eeqj.de/sneak/netwatch/issues/104.
`/metrics` now goes through the same `httprate` middleware as `POST /api/v1/reports`, with its own allowance of 60 requests a minute per client address, a constant in `backend/internal/server/routes.go`. The limit runs before the basic auth, so past it the answer is 429 and the password is not checked; under it nothing changes. `backend/README.md` says so.
How the client address reaches the backend, unchanged by this PR:
1. nginx takes the client address from `X-Forwarded-For` only on a request from one of the container's `TRUSTED_PROXIES` (the `set_real_ip_from` lines `bin/entrypoint.sh` writes); otherwise it is the connecting address.
2. nginx passes that address on as the only `X-Forwarded-For` entry. The `proxy_set_header` lines sit at server level in `nginx.conf`, so `location = /metrics` gets them too.
3. The backend runs with `TRUSTED_PROXIES=127.0.0.1/32` and takes `X-Forwarded-For` only from a peer in that set. The rate limit counts the address its existing `clientIP` resolves, as for reports, so clients behind nginx are limited one by one, not together as nginx's loopback address.
The new test uses up one client's allowance on wrong passwords, gets 429 with the right one, and checks that another client behind the same nginx still gets 200.
- Judgement call: 60 a minute. The minute slides, so a scraper polling every 2 seconds or less often is never refused; a guesser gets about 60 tries a minute per address.
- The limit counts every request to `/metrics`, right password or not, as the issue asks.
- `httprate` was already a dependency, so `go.mod` does not change.
Model: opus-5-5
Each client address may make 60 requests to /metrics a minute,
through the same httprate middleware and TRUSTED_PROXIES
resolution the report route uses, with an allowance of its own.
The limit runs before the basic auth, so past it the answer is 429
and the password is not checked. backend/README.md says so; a test
uses up one client's allowance on wrong passwords, gets 429 with
the right one, and checks that another client behind the same
nginx still gets in.
Model: opus-5-5
PASS: /metrics is rate limited per client address with httprate ahead of the basic auth, 60 a minute as a plain constant, so past the limit the answer is 429 and the password is not checked; the address counted is the one nginx resolves through TRUSTED_PROXIES, which a client outside them cannot forge; the test, backend/README.md and TODO.md cover it as #104 asks.
Judgement call: the limit counts each IPv6 address on its own, as the report limit already does, so a client holding a block of IPv6 addresses gets 60 guesses a minute per address; left for a follow-up, outside this issue.
Judgement call: not a finding that the root README.md operator section does not mention the /metrics limit; with TRUSTED_PROXIES unset behind a proxy, every client shares one /metrics allowance, so one guesser can keep the scraper refused.
Model: opus-5-5
PASS: `/metrics` is rate limited per client address with `httprate` ahead of the basic auth, 60 a minute as a plain constant, so past the limit the answer is 429 and the password is not checked; the address counted is the one nginx resolves through `TRUSTED_PROXIES`, which a client outside them cannot forge; the test, `backend/README.md` and `TODO.md` cover it as https://git.eeqj.de/sneak/netwatch/issues/104 asks.
Judgement call: the limit counts each IPv6 address on its own, as the report limit already does, so a client holding a block of IPv6 addresses gets 60 guesses a minute per address; left for a follow-up, outside this issue.
Judgement call: not a finding that the root `README.md` operator section does not mention the `/metrics` limit; with `TRUSTED_PROXIES` unset behind a proxy, every client shares one `/metrics` allowance, so one guesser can keep the scraper refused.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #104.
/metricsnow goes through the samehttpratemiddleware asPOST /api/v1/reports, with its own allowance of 60 requests a minute per client address, a constant inbackend/internal/server/routes.go. The limit runs before the basic auth, so past it the answer is 429 and the password is not checked; under it nothing changes.backend/README.mdsays so.How the client address reaches the backend, unchanged by this PR:
X-Forwarded-Foronly on a request from one of the container'sTRUSTED_PROXIES(theset_real_ip_fromlinesbin/entrypoint.shwrites); otherwise it is the connecting address.X-Forwarded-Forentry. Theproxy_set_headerlines sit at server level innginx.conf, solocation = /metricsgets them too.TRUSTED_PROXIES=127.0.0.1/32and takesX-Forwarded-Foronly from a peer in that set. The rate limit counts the address its existingclientIPresolves, as for reports, so clients behind nginx are limited one by one, not together as nginx's loopback address.The new test uses up one client's allowance on wrong passwords, gets 429 with the right one, and checks that another client behind the same nginx still gets 200.
/metrics, right password or not, as the issue asks.httpratewas already a dependency, sogo.moddoes not change.Model: opus-5-5
PASS:
/metricsis rate limited per client address withhttprateahead of the basic auth, 60 a minute as a plain constant, so past the limit the answer is 429 and the password is not checked; the address counted is the one nginx resolves throughTRUSTED_PROXIES, which a client outside them cannot forge; the test,backend/README.mdandTODO.mdcover it as #104 asks.Judgement call: the limit counts each IPv6 address on its own, as the report limit already does, so a client holding a block of IPv6 addresses gets 60 guesses a minute per address; left for a follow-up, outside this issue.
Judgement call: not a finding that the root
README.mdoperator section does not mention the/metricslimit; withTRUSTED_PROXIESunset behind a proxy, every client shares one/metricsallowance, so one guesser can keep the scraper refused.Model: opus-5-5