fix(web,gateway): close P3 re-review#3 findings — sanitize command errors, harden turn-lock & caps
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { CommandExecutorService } from './command-executor.service.js';
|
||||
import type { SlashCommandPayload } from '@mosaicstack/types';
|
||||
@@ -258,4 +259,39 @@ describe('CommandExecutorService — P8-012 commands', () => {
|
||||
expect(result.command).toBe('tools');
|
||||
expect(result.message).toContain('tools');
|
||||
});
|
||||
|
||||
// Top-level catch sanitization (P3-4 re-review finding #1): a rejected
|
||||
// Redis `set` inside /provider login is the only reachable path into the
|
||||
// top-level catch in `execute()`. The raw exception must be logged
|
||||
// server-side but never handed back to the socket client.
|
||||
it('sanitizes the top-level command catch, logging the raw exception but never returning it to the client', async () => {
|
||||
const distinctiveRawFailure = 'ECONNREFUSED distinctive-raw-redis-failure-token-9f31';
|
||||
const failingRedis = {
|
||||
set: vi.fn().mockRejectedValue(new Error(distinctiveRawFailure)),
|
||||
get: vi.fn(),
|
||||
del: vi.fn(),
|
||||
};
|
||||
const failingService = buildService(failingRedis as unknown as typeof mockRedis);
|
||||
const loggerErrorSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined);
|
||||
|
||||
const payload: SlashCommandPayload = {
|
||||
command: 'provider',
|
||||
args: 'login anthropic',
|
||||
conversationId,
|
||||
};
|
||||
const result = await failingService.execute(payload, userScope);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.command).toBe('provider');
|
||||
expect(result.message).toBe('Command failed due to an internal error.');
|
||||
expect(result.message).not.toContain(distinctiveRawFailure);
|
||||
expect(result.message).not.toContain('ECONNREFUSED');
|
||||
|
||||
// The real exception is still logged server-side.
|
||||
expect(loggerErrorSpy).toHaveBeenCalled();
|
||||
const loggedText = loggerErrorSpy.mock.calls.map((call) => String(call[0])).join(' ');
|
||||
expect(loggedText).toContain(distinctiveRawFailure);
|
||||
|
||||
loggerErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,7 +160,12 @@ export class CommandExecutorService {
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(`Command /${command} failed: ${err}`);
|
||||
return { command, conversationId, success: false, message: String(err) };
|
||||
return {
|
||||
command,
|
||||
conversationId,
|
||||
success: false,
|
||||
message: 'Command failed due to an internal error.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user