From fb46d6c267b95cf0e0824a539625c11a5b9828f9 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 29 May 2026 20:31:30 +0800 Subject: [PATCH] fix: enforce device pod profile identity --- internal/cloud/access-control.test.ts | 6 ++++++ internal/cloud/access-control.ts | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/cloud/access-control.test.ts b/internal/cloud/access-control.test.ts index a9692e0f..cc877a5d 100644 --- a/internal/cloud/access-control.test.ts +++ b/internal/cloud/access-control.test.ts @@ -55,6 +55,7 @@ test("cloud api access control grants visible device pods and requires device-po name: "71-FREQ", profile: { schemaVersion: 1, + devicePodId: "device-pod-local-spoof", target: { id: "target-71-freq" }, projectWorkspace: { projectPath: "FirmWare/MDK-ARM/app.uvprojx", targetName: "app" }, route: { @@ -66,6 +67,8 @@ test("cloud api access control grants visible device pods and requires device-po } }, adminCookie); assert.equal(podCreate.status, 201); + assert.equal(podCreate.body.devicePod.devicePodId, "device-pod-71-freq"); + assert.notEqual(podCreate.body.devicePod.profileHash, "sha256:a-local-profile-hash"); assert.equal(podCreate.body.devicePod.profile.route.gatewaySessionId, "redacted"); assert.equal(JSON.stringify(podCreate.body).includes("gws_missing"), false); @@ -782,6 +785,7 @@ test("cloud api first-admin setup opens access when bootstrap secret is absent", name: "71-FREQ", profile: { schemaVersion: 1, + devicePodId: "device-pod-seed-spoof", target: { id: "target-71-freq" }, route: { gatewaySessionId: "gws_first_admin_seed", @@ -802,7 +806,9 @@ test("cloud api first-admin setup opens access when bootstrap secret is absent", assert.deepEqual(setup.body.devicePodBootstrap, { requested: 1, initialized: 1 }); assert.equal(setup.body.devicePodsInitialized[0].devicePod.devicePodId, "device-pod-71-freq"); assert.equal(setup.body.devicePodsInitialized[0].grant.userId, setup.body.actor.id); + assert.match(setup.body.devicePodsInitialized[0].devicePod.profileHash, /^sha256:/u); assert.equal(setup.body.devicePodsInitialized[0].devicePod.profile.route.gatewaySessionId, "redacted"); + assert.equal(JSON.stringify(setup.body).includes("device-pod-seed-spoof"), false); assert.equal(JSON.stringify(setup.body).includes("admin-pass"), false); assert.equal(JSON.stringify(setup.body).includes("gws_first_admin_seed"), false); assert.equal(JSON.stringify(setup.body).includes("F:\\Work\\Project"), false); diff --git a/internal/cloud/access-control.ts b/internal/cloud/access-control.ts index e733ef68..08dd33da 100644 --- a/internal/cloud/access-control.ts +++ b/internal/cloud/access-control.ts @@ -1023,7 +1023,7 @@ class MemoryAccessStore { async revokeSessionByTokenHash(tokenHash, now) { for (const session of this.sessions.values()) if (session.tokenHash === tokenHash) session.revokedAt = now; } async upsertDevicePod(input) { const existing = this.devicePods.get(input.id); - const profile = assertDevicePodProfileSafe(normalizeObject(input.profile), "profile"); + const profile = devicePodProfileForAuthority(input.id, normalizeObject(input.profile)); const now = input.now ?? this.now(); const pod = { id: input.id, name: input.name ?? existing?.name ?? input.id, status: input.status ?? existing?.status ?? "active", profile, profileHash: profileHash(profile), createdAt: existing?.createdAt ?? now, updatedAt: now }; this.devicePods.set(pod.id, pod); @@ -1174,6 +1174,7 @@ function assertDevicePodProfileSafe(profile, field = "profile") { } return profile; } +function devicePodProfileForAuthority(devicePodId, profile) { return { ...assertDevicePodProfileSafe(profile, "profile"), devicePodId }; } function collectDevicePodProfileSecretFindings(value, path, findings) { if (findings.length >= 5) return; if (Array.isArray(value)) {