fix: health endpoint fails closed on remote bind when KEEPER_REGISTER_SECRET is unset - #359
fix: health endpoint fails closed on remote bind when KEEPER_REGISTER_SECRET is unset#359Ayomisco wants to merge 1 commit into
Conversation
…_SECRET is unset
Previously, /health, /pause-status, and /shadow/report would respond to any
unauthenticated client when KEEPER_HEALTH_BIND_ADDR was non-loopback and
KEEPER_REGISTER_SECRET was unset. The response included keeperWallet.solBalance,
budget circuit-breaker state, and stale-oracle market lists — enough to calibrate
targeted griefing attacks.
Fail closed: return 503 on remote bind when no secret is configured rather than
serving sensitive data. Add GET /health?probe=liveness as an unauthenticated
liveness-only endpoint (returns {alive:true}, no operational data) for Railway
health checks that cannot send the x-shared-secret header.
|
Warning Review limit reached
More reviews will be available in 54 minutes and 37 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Independent verification — not an approval (QA/Security own that). The security posture is right. One operational risk in the escape hatch is worth fixing before merge. The posture is correctFailing closed on a remote bind regardless of whether a secret is set is the right call. The previous fail-open behaviour exposed Placing the liveness probe before the auth gate is also right, and it only returns The risk: the probe is an exact string matchif (req.url === "/health?probe=liveness" && req.method === "GET")
Why this matters more than usual here: this probe is the only escape hatch the fail-closed change leaves for a remote-bound keeper. If the health checker's URL differs by so much as an appended cache-buster, the request falls through to the authenticated branch, gets a 503, the platform marks the container unhealthy and restarts it — repeatedly. A hardening change would turn into a restart loop, and the symptom (keeper flapping) looks nothing like the cause. Parsing instead makes all five spellings work: const u = new URL(req.url ?? "", "http://localhost");
if (u.pathname.replace(/\/$/, "") === "/health"
&& u.searchParams.get("probe") === "liveness"
&& req.method === "GET") {No tests
The four that would pin it:
Without these, a later refactor could restore the fail-open branch and the suite would stay green. Not blocking on the posture — that part I'd take as-is. The URL matching is what I'd want changed first. |
Closes #358
What
Add an unauthenticated
GET /health?probe=livenessendpoint and change the no-secret code path to fail closed.Why
Railway's liveness probe cannot send a custom
x-shared-secretheader. Without an unauthenticated probe path, the container restarts in a loop.The no-secret fallback was fail open: when
KEEPER_SECRETis unset and the server is bound to a non-loopback address, any HTTP client received the full health payload. That posture was noted in a comment but not corrected.Changes
src/index.ts: addedGET /health?probe=livenessbefore the auth middleware. Returns{alive:true}only, no operational data.src/index.tslines 397-405: changed the no-secret branch from serving the full health response to returning 503. Loopback-only bind is still allowed without a secret (development use).