Skip to content

Commit 868fe48

Browse files
Nachx639mbelinky
andauthored
fix(gateway): allow health method for all authenticated roles (#19699)
Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: b976443 Co-authored-by: Nachx639 <[email protected]> Co-authored-by: mbelinky <[email protected]> Reviewed-by: @mbelinky
1 parent c8ee33c commit 868fe48

File tree

4 files changed

+30
-7
lines changed

4 files changed

+30
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
1313

1414
### Fixes
1515

16+
- Gateway/Auth: allow authenticated clients across roles/scopes to call `health` while preserving role and scope enforcement for non-health methods. (#19699) thanks @Nachx639.
1617
- Gateway/Hooks: include transform export name in hook-transform cache keys so distinct exports from the same module do not reuse the wrong cached transform function. (#13855) thanks @mcaxtr.
1718
- Gateway/Control UI: return 404 for missing static-asset paths instead of serving SPA fallback HTML, while preserving client-route fallback behavior for extensionless and non-asset dotted paths. (#12060) thanks @mcaxtr.
1819
- Gateway/Pairing: prevent device-token rotate scope escalation by enforcing an approved-scope baseline, preserving approved scopes across metadata updates, and rejecting rotate requests that exceed approved role scope implications. (#20703) thanks @coygeek.

src/gateway/server-methods.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ function authorizeGatewayMethod(method: string, client: GatewayRequestOptions["c
3939
if (!client?.connect) {
4040
return null;
4141
}
42+
if (method === "health") {
43+
return null;
44+
}
4245
const role = client.connect.role ?? "operator";
4346
const scopes = client.connect.scopes ?? [];
4447
if (isNodeRoleMethod(method)) {

src/gateway/server.auth.e2e.test.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ async function expectMissingScopeAfterConnect(
111111
try {
112112
const res = await connectReq(ws, opts);
113113
expect(res.ok).toBe(true);
114-
const health = await rpcReq(ws, "health");
115-
expect(health.ok).toBe(false);
116-
expect(health.error?.message).toContain("missing scope");
114+
const status = await rpcReq(ws, "status");
115+
expect(status.ok).toBe(false);
116+
expect(status.error?.message).toContain("missing scope");
117117
} finally {
118118
ws.close();
119119
}
@@ -363,6 +363,18 @@ describe("gateway server auth/connect", () => {
363363
await expectMissingScopeAfterConnect(port, { device: null });
364364
});
365365

366+
test("allows health when scopes are empty", async () => {
367+
const ws = await openWs(port);
368+
try {
369+
const res = await connectReq(ws, { scopes: [] });
370+
expect(res.ok).toBe(true);
371+
const health = await rpcReq(ws, "health");
372+
expect(health.ok).toBe(true);
373+
} finally {
374+
ws.close();
375+
}
376+
});
377+
366378
test("does not grant admin when scopes are omitted", async () => {
367379
const ws = await openWs(port);
368380
const token = resolveGatewayTokenOrEnv();
@@ -400,9 +412,11 @@ describe("gateway server auth/connect", () => {
400412
expect(presenceScopes).toEqual([]);
401413
expect(presenceScopes).not.toContain("operator.admin");
402414

415+
const status = await rpcReq(ws, "status");
416+
expect(status.ok).toBe(false);
417+
expect(status.error?.message).toContain("missing scope");
403418
const health = await rpcReq(ws, "health");
404-
expect(health.ok).toBe(false);
405-
expect(health.error?.message).toContain("missing scope");
419+
expect(health.ok).toBe(true);
406420

407421
ws.close();
408422
});
@@ -680,9 +694,11 @@ describe("gateway server auth/connect", () => {
680694
const ws = await openTailscaleWs(port);
681695
const res = await connectReq(ws, { token: "secret", device: null });
682696
expect(res.ok).toBe(true);
697+
const status = await rpcReq(ws, "status");
698+
expect(status.ok).toBe(false);
699+
expect(status.error?.message).toContain("missing scope");
683700
const health = await rpcReq(ws, "health");
684-
expect(health.ok).toBe(false);
685-
expect(health.error?.message).toContain("missing scope");
701+
expect(health.ok).toBe(true);
686702
ws.close();
687703
});
688704
});

src/gateway/server.roles-allowlist-update.e2e.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ describe("gateway role enforcement", () => {
9696
const statusRes = await rpcReq(nodeWs, "status", {});
9797
expect(statusRes.ok).toBe(false);
9898
expect(statusRes.error?.message ?? "").toContain("unauthorized role");
99+
100+
const healthRes = await rpcReq(nodeWs, "health", {});
101+
expect(healthRes.ok).toBe(true);
99102
} finally {
100103
nodeWs.close();
101104
}

0 commit comments

Comments
 (0)