feat(security): send x-commands-sync-key when configured (Guild C-2)
syncCommands attaches the FABRIC_COMMANDS_SYNC_KEY header when the operator sets it, so the guild can restrict slash-command catalog writes to this plugin. No-op / backward compatible when unset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
9
dist/fabric/src/fabric-client.js
vendored
9
dist/fabric/src/fabric-client.js
vendored
@@ -23,12 +23,13 @@ export class FabricClient {
|
|||||||
}
|
}
|
||||||
// Generic JSON request (GET/PUT/PATCH/DELETE). Empty 2xx body -> null
|
// Generic JSON request (GET/PUT/PATCH/DELETE). Empty 2xx body -> null
|
||||||
// (Fabric returns an empty body when a channel has no canvas).
|
// (Fabric returns an empty body when a channel has no canvas).
|
||||||
async req(method, url, auth, body) {
|
async req(method, url, auth, body, extraHeaders) {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
||||||
...(auth ? { authorization: `Bearer ${auth}` } : {}),
|
...(auth ? { authorization: `Bearer ${auth}` } : {}),
|
||||||
|
...(extraHeaders ?? {}),
|
||||||
},
|
},
|
||||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||||
});
|
});
|
||||||
@@ -75,7 +76,11 @@ export class FabricClient {
|
|||||||
// full replace). The frontend GETs it for `/` autocomplete; execution
|
// full replace). The frontend GETs it for `/` autocomplete; execution
|
||||||
// still flows as a normal /<cmd> message into OpenClaw's command system.
|
// still flows as a normal /<cmd> message into OpenClaw's command system.
|
||||||
syncCommands(guildEndpoint, guildToken, commands) {
|
syncCommands(guildEndpoint, guildToken, commands) {
|
||||||
return this.req('PUT', `${guildEndpoint}/api/commands`, guildToken, { commands });
|
// Guild C-2: when the operator sets a shared sync key on both sides
|
||||||
|
// (FABRIC_COMMANDS_SYNC_KEY here / FABRIC_BACKEND_GUILD_COMMANDS_SYNC_KEY
|
||||||
|
// on the guild), the catalog write is restricted to this plugin.
|
||||||
|
const key = process.env.FABRIC_COMMANDS_SYNC_KEY;
|
||||||
|
return this.req('PUT', `${guildEndpoint}/api/commands`, guildToken, { commands }, key ? { 'x-commands-sync-key': key } : undefined);
|
||||||
}
|
}
|
||||||
// [{ userId, bypass }] — bypass is true only for discuss/work bypass-list
|
// [{ userId, bypass }] — bypass is true only for discuss/work bypass-list
|
||||||
channelMembers(guildEndpoint, guildToken, channelId) {
|
channelMembers(guildEndpoint, guildToken, channelId) {
|
||||||
|
|||||||
@@ -36,12 +36,14 @@ export class FabricClient {
|
|||||||
url: string,
|
url: string,
|
||||||
auth?: string,
|
auth?: string,
|
||||||
body?: unknown,
|
body?: unknown,
|
||||||
|
extraHeaders?: Record<string, string>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
||||||
...(auth ? { authorization: `Bearer ${auth}` } : {}),
|
...(auth ? { authorization: `Bearer ${auth}` } : {}),
|
||||||
|
...(extraHeaders ?? {}),
|
||||||
},
|
},
|
||||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||||
});
|
});
|
||||||
@@ -123,7 +125,17 @@ export class FabricClient {
|
|||||||
guildToken: string,
|
guildToken: string,
|
||||||
commands: unknown[],
|
commands: unknown[],
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
return this.req('PUT', `${guildEndpoint}/api/commands`, guildToken, { commands });
|
// Guild C-2: when the operator sets a shared sync key on both sides
|
||||||
|
// (FABRIC_COMMANDS_SYNC_KEY here / FABRIC_BACKEND_GUILD_COMMANDS_SYNC_KEY
|
||||||
|
// on the guild), the catalog write is restricted to this plugin.
|
||||||
|
const key = process.env.FABRIC_COMMANDS_SYNC_KEY;
|
||||||
|
return this.req(
|
||||||
|
'PUT',
|
||||||
|
`${guildEndpoint}/api/commands`,
|
||||||
|
guildToken,
|
||||||
|
{ commands },
|
||||||
|
key ? { 'x-commands-sync-key': key } : undefined,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [{ userId, bypass }] — bypass is true only for discuss/work bypass-list
|
// [{ userId, bypass }] — bypass is true only for discuss/work bypass-list
|
||||||
|
|||||||
Reference in New Issue
Block a user