Skip to content

Commit 09cb22e

Browse files
committed
fix(webhooks): restrict health route to internal service-binding calls
The public webhook host (webhooks.trycheatcode.com) served /health unauthenticated and unrate-limited, exposing releaseSha and versionId with no public consumer: the only caller is the gateway's release aggregation, which probes https://webhooks.internal/health over the WEBHOOKS service binding. Gate the route on the internal hostname and answer c.notFound() on any other host, indistinguishable from a nonexistent route. Same posture as the preview-proxy public health removal (#103): every public route must have a named external consumer. Gateway is unchanged and its binding fetch already carries the internal hostname, so there is no deploy-order skew.
1 parent 39d1a7c commit 09cb22e

2 files changed

Lines changed: 13 additions & 5 deletions

File tree

apps/webhooks-worker/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ The handler therefore resolves that Composio-project-global ID through the datab
137137
primary key; ownership and toolkit assignment are immutable after insertion,
138138
and terminal status changes atomically reconcile the user's active default.
139139

140-
Production binds one immutable `CHEATCODE_RELEASE_SHA`, exposed by `/health`.
140+
Production binds one immutable `CHEATCODE_RELEASE_SHA`, exposed by `/health`
141+
only to the gateway's service-binding probe (`https://webhooks.internal/health`);
142+
on the public webhook host the route answers as not found.
141143
HTTP, cron, idempotency, deletion, and workflow continuation paths use their
142144
normal durable ownership and idempotency contracts; database migrations retain
143145
their separate target, role, lock, and schema validation.

apps/webhooks-worker/src/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,20 @@ webhooksApp.use(
165165
}),
166166
);
167167

168-
webhooksApp.get("/health", (c) =>
169-
c.json({
168+
webhooksApp.get("/health", (c) => {
169+
// Service-binding-only surface: the gateway probes https://webhooks.internal/health
170+
// for release aggregation. The public webhook host must answer like the route does
171+
// not exist so release metadata never leaks on an unauthenticated endpoint.
172+
if (new URL(c.req.url).hostname !== "webhooks.internal") {
173+
return c.notFound();
174+
}
175+
return c.json({
170176
ok: true,
171177
releaseSha: c.env.CHEATCODE_RELEASE_SHA ?? "development",
172178
versionId: c.env.CF_VERSION_METADATA?.id ?? null,
173179
worker: "webhooks",
174-
}),
175-
);
180+
});
181+
});
176182

177183
webhooksApp.post("/clerk", async (c) => {
178184
const signingSecret = await clerkWebhookSigningSecret(c.env);

0 commit comments

Comments
 (0)