fix(security): harden hooks module loading

This commit is contained in:
Peter Steinberger
2026-02-14 14:04:29 +01:00
parent 3d0a41b584
commit 35c0e66ed0
11 changed files with 145 additions and 20 deletions

View File

@@ -217,7 +217,6 @@ describe("hooks mapping", () => {
expect("skipped" in result).toBe(true);
}
});
it("treats null transform as a handled skip", async () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-config-skip-"));
const transformsRoot = path.join(configDir, "hooks", "transforms");

View File

@@ -144,6 +144,18 @@ describe("gateway server auth/connect", () => {
signedAtMs,
token: token ?? null,
});
test("ignores requested scopes when device identity is omitted", async () => {
const ws = await openWs(port);
const res = await connectReq(ws, { device: null });
expect(res.ok).toBe(true);
const health = await rpcReq(ws, "health");
expect(health.ok).toBe(false);
expect(health.error?.message).toContain("missing scope");
ws.close();
});
const device = {
id: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
@@ -493,6 +505,9 @@ describe("gateway server auth/connect", () => {
const ws = await openTailscaleWs(port);
const res = await connectReq(ws, { token: "secret", device: null });
expect(res.ok).toBe(true);
const health = await rpcReq(ws, "health");
expect(health.ok).toBe(false);
expect(health.error?.message).toContain("missing scope");
ws.close();
});
});

View File

@@ -298,7 +298,9 @@ export function attachGatewayWsMessageHandler(params: {
return;
}
// Default-deny: scopes must be explicit. Empty/missing scopes means no permissions.
const scopes = Array.isArray(connectParams.scopes) ? connectParams.scopes : [];
// Note: If the client does not present a device identity, we can't bind scopes to a paired
// device/token, so we will clear scopes after auth to avoid self-declared permissions.
let scopes = Array.isArray(connectParams.scopes) ? connectParams.scopes : [];
connectParams.role = role;
connectParams.scopes = scopes;
@@ -428,6 +430,10 @@ export function attachGatewayWsMessageHandler(params: {
close(1008, truncateCloseReason(authMessage));
};
if (!device) {
if (scopes.length > 0) {
scopes = [];
connectParams.scopes = scopes;
}
const canSkipDevice = sharedAuthOk;
if (isControlUi && !allowControlUiBypass) {