Files
nanoclaw/src/command-gate.test.ts
T
gavrielc fcee39ea14 command-gate: restore the /start filter and remove the fail-open admin check
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>
2026-07-04 16:04:58 +03:00

84 lines
2.8 KiB
TypeScript

/**
* Tests for the host-side command gate — filtered commands are dropped
* before reaching the container, and admin commands are gated against
* the user_roles table.
*/
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { gateCommand } from './command-gate.js';
import { closeDb, createAgentGroup, initTestDb, runMigrations } from './db/index.js';
import { createUser } from './modules/permissions/db/users.js';
import { grantRole } from './modules/permissions/db/user-roles.js';
function now(): string {
return new Date().toISOString();
}
function seedAgentGroup(id: string): void {
createAgentGroup({ id, name: id.toUpperCase(), folder: id, agent_provider: null, created_at: now() });
}
function seedUser(id: string): void {
createUser({ id, kind: 'telegram', display_name: null, created_at: now() });
}
beforeEach(() => {
const db = initTestDb();
runMigrations(db);
seedAgentGroup('ag-1');
seedAgentGroup('ag-2');
});
afterEach(() => {
closeDb();
});
describe('filtered commands', () => {
it('drops /start before it reaches the container', () => {
expect(gateCommand('/start', 'telegram:1', 'ag-1')).toEqual({ action: 'filter' });
});
it('drops /start regardless of sender', () => {
expect(gateCommand('/start', null, 'ag-1')).toEqual({ action: 'filter' });
});
});
describe('admin gating goes through roles', () => {
it('denies an admin command from a non-admin user', () => {
expect(gateCommand('/clear', 'telegram:nobody', 'ag-1')).toEqual({ action: 'deny', command: '/clear' });
});
it('denies an admin command with no sender', () => {
expect(gateCommand('/clear', null, 'ag-1')).toEqual({ action: 'deny', command: '/clear' });
});
it('allows an admin command from an owner', () => {
seedUser('telegram:owner');
grantRole({ user_id: 'telegram:owner', role: 'owner', agent_group_id: null, granted_by: null, granted_at: now() });
expect(gateCommand('/clear', 'telegram:owner', 'ag-1')).toEqual({ action: 'pass' });
});
it('allows an admin command from a scoped admin of the group', () => {
seedUser('telegram:admin');
grantRole({
user_id: 'telegram:admin',
role: 'admin',
agent_group_id: 'ag-1',
granted_by: null,
granted_at: now(),
});
expect(gateCommand('/clear', 'telegram:admin', 'ag-1')).toEqual({ action: 'pass' });
expect(gateCommand('/clear', 'telegram:admin', 'ag-2')).toEqual({ action: 'deny', command: '/clear' });
});
});
describe('normal messages pass through', () => {
it('passes a plain message', () => {
expect(gateCommand('hello there', 'telegram:1', 'ag-1')).toEqual({ action: 'pass' });
});
it('passes an unknown slash command', () => {
expect(gateCommand('/whatever', 'telegram:1', 'ag-1')).toEqual({ action: 'pass' });
});
});