test: cover device pairing token hardening

This commit is contained in:
Gustavo Madeira Santana 2026-02-14 15:40:15 -05:00
parent 37c4db02ab
commit 2e5f365e43
2 changed files with 47 additions and 0 deletions

View File

@ -192,6 +192,7 @@ Docs: https://docs.openclaw.ai
- Clawdock: avoid Zsh readonly variable collisions in helper scripts. (#15501) Thanks @nkelner.
- Memory: switch default local embedding model to the QAT `embeddinggemma-300m-qat-Q8_0` variant for better quality at the same footprint. (#15429) Thanks @azade-c.
- Docs/Mermaid: remove hardcoded Mermaid init theme blocks from four docs diagrams so dark mode inherits readable theme defaults. (#15157) Thanks @heytulsiprasad.
- Security/Device pairing: generate 256-bit base64url device tokens and use byte-safe constant-time verification to avoid token-compare edge-case failures.
## 2026.2.12

View File

@ -11,6 +11,25 @@ import {
} from "./device-pairing.js";
describe("device pairing tokens", () => {
test("generates base64url device tokens with 256-bit entropy output length", async () => {
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
const request = await requestDevicePairing(
{
deviceId: "device-1",
publicKey: "public-key-1",
role: "operator",
scopes: ["operator.admin"],
},
baseDir,
);
await approveDevicePairing(request.request.requestId, baseDir);
const paired = await getPairedDevice("device-1", baseDir);
const token = paired?.tokens?.operator?.token;
expect(token).toBeTruthy();
expect(token).toMatch(/^[A-Za-z0-9_-]{43}$/);
});
test("preserves existing token scopes when rotating without scopes", async () => {
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
const request = await requestDevicePairing(
@ -78,4 +97,31 @@ describe("device pairing tokens", () => {
expect(mismatch.ok).toBe(false);
expect(mismatch.reason).toBe("token-mismatch");
});
test("treats multibyte same-length token input as mismatch without throwing", async () => {
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
const request = await requestDevicePairing(
{
deviceId: "device-1",
publicKey: "public-key-1",
role: "operator",
scopes: ["operator.read"],
},
baseDir,
);
await approveDevicePairing(request.request.requestId, baseDir);
const paired = await getPairedDevice("device-1", baseDir);
const token = paired?.tokens?.operator?.token ?? "";
const mismatch = await verifyDeviceToken({
deviceId: "device-1",
token: "é".repeat(token.length),
role: "operator",
scopes: ["operator.read"],
baseDir,
});
expect(mismatch.ok).toBe(false);
expect(mismatch.reason).toBe("token-mismatch");
});
});