mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-10 00:31:22 +00:00
fix: finalize device-pair scope hardening (#55996) (thanks @coygeek)
This commit is contained in:
@@ -84,6 +84,7 @@ Docs: https://docs.openclaw.ai
|
||||
- MiniMax/pricing: keep bundled MiniMax highspeed pricing distinct in provider catalogs and preserve the lower M2.5 cache-read pricing when onboarding older MiniMax models. (#54214) Thanks @octo-patch.
|
||||
- Agents/cache: preserve the full 3-turn prompt-cache image window across tool loops, keep colliding bundled MCP tool definitions deterministic, and reapply Anthropic Vertex cache shaping after payload hook replacements so KV/cache reuse stays stable. Thanks @vincentkoc.
|
||||
- Device pairing: reject rotating device tokens into roles that were never approved during pairing, and keep reconnect role checks bounded to the paired device's approved role set. (#60462) Thanks @eleqtrizit.
|
||||
- Mobile pairing/security: fail closed for internal `/pair` setup-code issuance, cleanup, and approval paths when gateway pairing scopes are missing, and keep approval-time requested-scope enforcement on the internal command path. (#55996) Thanks @coygeek.
|
||||
|
||||
## 2026.4.2
|
||||
|
||||
|
||||
@@ -411,6 +411,7 @@ describe("device-pair /pair qr", () => {
|
||||
const command = registerPairCommand();
|
||||
const result = await command?.handler(
|
||||
createCommandContext({
|
||||
channel: "telegram",
|
||||
args: "cleanup",
|
||||
commandBody: "/pair cleanup",
|
||||
}),
|
||||
@@ -560,6 +561,10 @@ describe("device-pair notify pending formatting", () => {
|
||||
});
|
||||
|
||||
describe("device-pair /pair approve", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("rejects internal gateway callers without operator.pairing", async () => {
|
||||
vi.mocked(listDevicePairing).mockResolvedValueOnce({
|
||||
pending: [
|
||||
@@ -713,10 +718,6 @@ describe("device-pair /pair approve", () => {
|
||||
],
|
||||
paired: [],
|
||||
});
|
||||
vi.mocked(approveDevicePairing).mockResolvedValueOnce({
|
||||
status: "forbidden",
|
||||
missingScope: "operator.admin",
|
||||
});
|
||||
|
||||
const command = registerPairCommand();
|
||||
const result = await command.handler(
|
||||
@@ -728,11 +729,9 @@ describe("device-pair /pair approve", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1", {
|
||||
callerScopes: [],
|
||||
});
|
||||
expect(vi.mocked(approveDevicePairing)).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
text: "⚠️ This command requires operator.admin to approve this pairing request.",
|
||||
text: "⚠️ This command requires operator.pairing for internal gateway callers.",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -773,43 +772,6 @@ describe("device-pair /pair approve", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("fails closed for internal gateway callers when scopes are absent", async () => {
|
||||
vi.mocked(listDevicePairing).mockResolvedValueOnce({
|
||||
pending: [
|
||||
{
|
||||
requestId: "req-1",
|
||||
deviceId: "victim-phone",
|
||||
publicKey: "victim-public-key",
|
||||
displayName: "Victim Phone",
|
||||
platform: "ios",
|
||||
ts: Date.now(),
|
||||
},
|
||||
],
|
||||
paired: [],
|
||||
});
|
||||
vi.mocked(approveDevicePairing).mockImplementationOnce(async () => ({
|
||||
status: "forbidden",
|
||||
missingScope: "operator.admin",
|
||||
}));
|
||||
|
||||
const command = registerPairCommand();
|
||||
const result = await command.handler(
|
||||
createCommandContext({
|
||||
channel: "webchat",
|
||||
args: "approve latest",
|
||||
commandBody: "/pair approve latest",
|
||||
gatewayClientScopes: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(vi.mocked(approveDevicePairing)).toHaveBeenCalledWith("req-1", {
|
||||
callerScopes: [],
|
||||
});
|
||||
expect(result).toEqual({
|
||||
text: "⚠️ This command requires operator.admin to approve this pairing request.",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves approvals for non-gateway command surfaces", async () => {
|
||||
vi.mocked(listDevicePairing).mockResolvedValueOnce({
|
||||
pending: [
|
||||
|
||||
Reference in New Issue
Block a user