Question
Both /metrics endpoints are publicly reachable and unauthenticated in production right now. Found while scoping postguard-ops#64.
cryptify — a guard exists and was never switched on
https://storage.postguard.eu/metrics serves the full exposition to anyone. The container binds 127.0.0.1:8002:8000, which alone would be host-local, but the nginx vhost proxies location / with no path exclusions (procolix/main.tf:97-103), so it is exposed.
The fix needs no code: cryptify/src/main.rs:140-159 already requires Authorization: Bearer <token>, constant-time compared, when metrics_token is configured — shipped in cryptify#177 and present in prod's 0.1.29. It is simply never set: absent from the rendered config.toml heredoc (procolix/main.tf:20-37), absent from procolix/variables.tf, and no ROCKET_METRICS_TOKEN in the env file. So prod logs the warning the code emits for exactly this case:
metrics_token is not set — /metrics is publicly accessible without authentication.
The k8s cryptify (dev) has the same gap (main.tf:418-445).
pg-pkg — no guard exists at all
https://pkg.postguard.eu/metrics is public. /metrics is registered on the root App outside the /v2 scope (pg-pkg/src/server.rs:368-370), so the Governor rate limiter and Auth middleware — both wrapped on /v2 only (server.rs:265, 341) — never see it. The ingress rule is path: "/", Prefix (main.tf:1231-1250), and the NetworkPolicy that would restrict it is count = !var.deploy_pkg_ingress ? 1 : 0 (main.tf:1370), i.e. not created in either environment, since both set deploy_pkg_ingress = true.
So closing pg-pkg's needs either an ingress path rule / server-snippet denying /metrics, a second listener, or a code change. Unlike cryptify, there is no knob to set.
Why it is this shape
cryptify#102 opened as a draft on this exact question — "/metrics is unauthenticated; restrict at the firewall / reverse proxy. Confirm this fits the Scaleway / Procolix network policy before merging." The confirmation never arrived and it merged anyway. This is the residue.
Sequencing — deliberately after the scraper, not before
Locking down first is the tempting order and the wrong one. Under #64's decision the collector is push-based: Alloy scrapes 127.0.0.1:8002 on the Procolix host itself, so closing the public path does not break it. Do it in the other order and you create the network dependency the push design exists to avoid.
Scope
- Add a
sensitive cryptify_metrics_token variable, thread it into the config, set it in both deployments.
- Decide and implement pg-pkg's mechanism (ingress rule vs code change).
- Confirm from outside the network that both endpoints 401 afterwards, and that the collector still reports.
Note on severity
The exposure is metrics, not message content or key material — upload counts, storage totals, client versions, process stats. Real, but calibrate it against GHSA-5rhx-xgvv-h78h, which was per-tenant usage for an arbitrary address. What makes it worth its own ticket is that one half is a config value that was shipped and never set.
Part of #247 (workstream D).
Question
Both
/metricsendpoints are publicly reachable and unauthenticated in production right now. Found while scoping postguard-ops#64.cryptify — a guard exists and was never switched on
https://storage.postguard.eu/metricsserves the full exposition to anyone. The container binds127.0.0.1:8002:8000, which alone would be host-local, but the nginx vhost proxieslocation /with no path exclusions (procolix/main.tf:97-103), so it is exposed.The fix needs no code:
cryptify/src/main.rs:140-159already requiresAuthorization: Bearer <token>, constant-time compared, whenmetrics_tokenis configured — shipped in cryptify#177 and present in prod's0.1.29. It is simply never set: absent from the renderedconfig.tomlheredoc (procolix/main.tf:20-37), absent fromprocolix/variables.tf, and noROCKET_METRICS_TOKENin the env file. So prod logs the warning the code emits for exactly this case:The k8s cryptify (dev) has the same gap (
main.tf:418-445).pg-pkg — no guard exists at all
https://pkg.postguard.eu/metricsis public./metricsis registered on the root App outside the/v2scope (pg-pkg/src/server.rs:368-370), so theGovernorrate limiter andAuthmiddleware — both wrapped on/v2only (server.rs:265, 341) — never see it. The ingress rule ispath: "/",Prefix(main.tf:1231-1250), and the NetworkPolicy that would restrict it iscount = !var.deploy_pkg_ingress ? 1 : 0(main.tf:1370), i.e. not created in either environment, since both setdeploy_pkg_ingress = true.So closing pg-pkg's needs either an ingress path rule /
server-snippetdenying/metrics, a second listener, or a code change. Unlike cryptify, there is no knob to set.Why it is this shape
cryptify#102 opened as a draft on this exact question — "
/metricsis unauthenticated; restrict at the firewall / reverse proxy. Confirm this fits the Scaleway / Procolix network policy before merging." The confirmation never arrived and it merged anyway. This is the residue.Sequencing — deliberately after the scraper, not before
Locking down first is the tempting order and the wrong one. Under #64's decision the collector is push-based: Alloy scrapes
127.0.0.1:8002on the Procolix host itself, so closing the public path does not break it. Do it in the other order and you create the network dependency the push design exists to avoid.Scope
sensitivecryptify_metrics_tokenvariable, thread it into the config, set it in both deployments.Note on severity
The exposure is metrics, not message content or key material — upload counts, storage totals, client versions, process stats. Real, but calibrate it against GHSA-5rhx-xgvv-h78h, which was per-tenant usage for an arbitrary address. What makes it worth its own ticket is that one half is a config value that was shipped and never set.
Part of #247 (workstream D).