mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-07-15 19:06:18 +08:00
fcee39ea14
Add '/start' back to the host FILTERED set (a Telegram fix from 866b791
was silently undone when host gating was added) so it is dropped instead
of reaching the agent as a normal message. Replace the inline isAdmin
query and its hasTable('user_roles') fail-open guard with a call to
hasAdminPrivilege; user_roles always exists (core migration 001-initial),
so the guard only ever masked a missing check. Add a focused command-gate
test covering both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
1.8 KiB
TypeScript
53 lines
1.8 KiB
TypeScript
/**
|
|
* Host-side command gate. Classifies inbound slash commands and gates
|
|
* them before they reach the container.
|
|
*
|
|
* - Filtered commands: dropped silently (never reach the container)
|
|
* - Admin commands: checked against user_roles; denied senders get a
|
|
* "Permission denied" response written directly to messages_out
|
|
* - Normal messages: pass through unchanged
|
|
*/
|
|
import { hasAdminPrivilege } from './modules/permissions/db/user-roles.js';
|
|
|
|
export type GateResult = { action: 'pass' } | { action: 'filter' } | { action: 'deny'; command: string };
|
|
|
|
const FILTERED_COMMANDS = new Set(['/start', '/help', '/login', '/logout', '/doctor', '/config', '/remote-control']);
|
|
const ADMIN_COMMANDS = new Set(['/clear', '/compact', '/context', '/cost', '/files', '/upload-trace']);
|
|
|
|
/**
|
|
* Classify a message and decide whether it should reach the container.
|
|
* Returns 'pass' for normal messages and authorized admin commands,
|
|
* 'filter' for silently-dropped commands, 'deny' for unauthorized
|
|
* admin commands.
|
|
*/
|
|
export function gateCommand(content: string, userId: string | null, agentGroupId: string): GateResult {
|
|
let text: string;
|
|
try {
|
|
const parsed = JSON.parse(content);
|
|
text = (parsed.text || '').trim();
|
|
} catch {
|
|
text = content.trim();
|
|
}
|
|
|
|
if (!text.startsWith('/')) return { action: 'pass' };
|
|
|
|
const command = text.split(/\s/)[0].toLowerCase();
|
|
|
|
if (FILTERED_COMMANDS.has(command)) return { action: 'filter' };
|
|
|
|
if (ADMIN_COMMANDS.has(command)) {
|
|
if (isAdmin(userId, agentGroupId)) {
|
|
return { action: 'pass' };
|
|
}
|
|
return { action: 'deny', command };
|
|
}
|
|
|
|
// Unknown slash commands pass through (the agent/SDK handles them)
|
|
return { action: 'pass' };
|
|
}
|
|
|
|
function isAdmin(userId: string | null, agentGroupId: string): boolean {
|
|
if (!userId) return false;
|
|
return hasAdminPrivilege(userId, agentGroupId);
|
|
}
|