/logs and /metrics: wrong tokens are never counted, so nothing bounds guessing an operator-chosen token #472
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#472
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the order of the checks in the two token-guarded endpoints and the rate-limit tests.Checked again on 2026-10-01 against
mainat0a5cad587: still present.Both views check the bearer token first and spend the per-address allowance (
POSTULO_ENDPOINT_RATE,120/h) only once it has matched. A wrong token is answered401at once and never counted, so the one limit these shared-secret endpoints have bounds the collector and not somebody trying tokens. Unlike API tokens, which Postulo generates with 32 random bytes,POSTULO_LOGS_TOKENandPOSTULO_METRICS_TOKENare whatever the operator typed, and nothing checks their length.core/views_logs.py:82-95(collect:_authorisedbeforethrottle.endpoint("logs", request));core/views_metrics.py:41-56(scrape, same order);core/throttle.py:151-166(endpoint);core/checks.py, which validates the rates but not the tokens.tests/security/test_rate_limits.py(test_a_wrong_token_is_still_refused_before_anything_is_counted) records the order as deliberate: "there is no work to protect behind a bad token"./logswith a short, memorablePOSTULO_LOGS_TOKEN. Anybody who can reach the instance may present tokens as fast as the server answers; every attempt gets401and none is ever refused with429./logs(or/metricswith a token) is enabled and the operator chose a guessable token. A long random token is out of reach either way, which is why this is low.throttle.consume(f"endpoint-refused:{name}", address, Rate(10, 3600))on every401, and once that is spent answer429to the address for the rest of the window whatever it presents. A collector with the right token never spends it, and a stranger's failures cost only the stranger's address. Add acore/checks.pywarning whenPOSTULO_LOGS_TOKENorPOSTULO_METRICS_TOKENis set and shorter than 24 characters, and say in.env.exampleand the wiki how to make one (openssl rand -hex 32). Correct the wiki's What/metricsand/logsshare to say which requests the limit counts.Related to #112, which added
POSTULO_ENDPOINT_RATEfor these endpoints but only for callers already holding the token.Done when
tests/security/test_rate_limits.pysends more wrong tokens than the refusal budget to/logsand gets429withRetry-After; the existing test that five wrong tokens are followed by a200still passes.tests/test_checks.pysetsPOSTULO_LOGS_TOKEN="short"and gets the warning.