fix(api): /v1/me/peer-pubkey only updates web-managed members
Adds a 409 not_web_member guard to POST /v1/me/peer-pubkey: the endpoint will only rewrite peer_pubkey on members that have dashboard_user_id set. CLI members own their on-disk keypair — overwriting their stored peer_pubkey would break the next WS hello because the signature verification would fail against the new pubkey. In practice this restriction is invisible to the legitimate browser flow: the dashboard always mints its apikey against the web member (dashboard_user_id is non-null by construction in mutations.ts). Guard ensures a misuse (e.g. a CLI-minted apikey being used to call peer-pubkey) gets a clear 409 instead of silently breaking the CLI's auth. Discovered during phase 3.5 smoke when a CLI-minted apikey clobbered the only openclaw member (CLI-owned) and the user's CLI signature would have stopped verifying on the next launch.
This commit is contained in:
@@ -305,12 +305,33 @@ export const v1Router = new Hono<Env>()
|
|||||||
const body = c.req.valid("json");
|
const body = c.req.valid("json");
|
||||||
const newPubkey = body.pubkey.toLowerCase();
|
const newPubkey = body.pubkey.toLowerCase();
|
||||||
const [existing] = await db
|
const [existing] = await db
|
||||||
.select({ peerPubkey: meshMember.peerPubkey })
|
.select({
|
||||||
|
peerPubkey: meshMember.peerPubkey,
|
||||||
|
dashboardUserId: meshMember.dashboardUserId,
|
||||||
|
})
|
||||||
.from(meshMember)
|
.from(meshMember)
|
||||||
.where(eq(meshMember.id, key.issuedByMemberId));
|
.where(eq(meshMember.id, key.issuedByMemberId));
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
return c.json({ error: "member_not_found" }, 404);
|
return c.json({ error: "member_not_found" }, 404);
|
||||||
}
|
}
|
||||||
|
// Safety: only web-managed members (dashboardUserId set) can have
|
||||||
|
// their peer_pubkey rewritten via this endpoint. CLI-created
|
||||||
|
// members hold a real on-disk secret that matches their existing
|
||||||
|
// peer_pubkey; overwriting it would break their next WS hello
|
||||||
|
// (signature verification fails because the stored pubkey no
|
||||||
|
// longer matches the secret they sign with). The browser flow
|
||||||
|
// always mints its apikey against the dashboard member, so this
|
||||||
|
// restriction is invisible to legitimate callers.
|
||||||
|
if (!existing.dashboardUserId) {
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
error: "not_web_member",
|
||||||
|
detail:
|
||||||
|
"this endpoint only updates web-managed members (mesh.member.dashboard_user_id IS NOT NULL); CLI members own their on-disk keypair and can't have peer_pubkey rewritten remotely",
|
||||||
|
},
|
||||||
|
409,
|
||||||
|
);
|
||||||
|
}
|
||||||
const changed = existing.peerPubkey !== newPubkey;
|
const changed = existing.peerPubkey !== newPubkey;
|
||||||
if (changed) {
|
if (changed) {
|
||||||
await db
|
await db
|
||||||
|
|||||||
Reference in New Issue
Block a user