Compare commits
3 Commits
mimo/code/
...
fix/1423
| Author | SHA1 | Date | |
|---|---|---|---|
| 35d562bb09 | |||
| 0a3f10cbc0 | |||
|
|
0e585e492a |
54
electron-main-secure.js
Normal file
54
electron-main-secure.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const { app, BrowserWindow } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Import the secure MemPalace bridge
|
||||||
|
const { setupSecureMemPalaceIPC } = require('./electron-mempalace-bridge');
|
||||||
|
|
||||||
|
let mainWindow;
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: false,
|
||||||
|
contextIsolation: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.loadFile('index.html');
|
||||||
|
|
||||||
|
// Open DevTools in development
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
// Set up secure MemPalace IPC
|
||||||
|
setupSecureMemPalaceIPC();
|
||||||
|
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle any uncaught exceptions
|
||||||
|
process.on('uncaughtException', (error) => {
|
||||||
|
console.error('Uncaught exception:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
||||||
|
});
|
||||||
290
electron-mempalace-bridge.js
Normal file
290
electron-mempalace-bridge.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/**
|
||||||
|
* Secure MemPalace IPC Bridge
|
||||||
|
* Issue #1423: [SECURITY] Electron MemPalace bridge allows arbitrary command execution
|
||||||
|
*
|
||||||
|
* Replaces raw command execution with typed, validated IPC actions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Whitelist of allowed MemPalace actions
|
||||||
|
const ALLOWED_ACTIONS = {
|
||||||
|
'init': {
|
||||||
|
command: 'mempalace',
|
||||||
|
args: ['init'],
|
||||||
|
requiredArgs: ['palacePath'],
|
||||||
|
validate: (args) => {
|
||||||
|
// Validate palacePath is safe (no shell metacharacters)
|
||||||
|
const palacePath = args.palacePath;
|
||||||
|
if (!palacePath || typeof palacePath !== 'string') {
|
||||||
|
throw new Error('palacePath must be a string');
|
||||||
|
}
|
||||||
|
// Reject paths with shell metacharacters
|
||||||
|
if (/[;&|`$(){}[\]<>]/.test(palacePath)) {
|
||||||
|
throw new Error('palacePath contains unsafe characters');
|
||||||
|
}
|
||||||
|
return [palacePath];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'mine': {
|
||||||
|
command: 'mempalace',
|
||||||
|
args: ['mine'],
|
||||||
|
requiredArgs: ['path', 'mode', 'wing'],
|
||||||
|
validate: (args) => {
|
||||||
|
const { path: minePath, mode, wing } = args;
|
||||||
|
|
||||||
|
// Validate each argument
|
||||||
|
if (!minePath || typeof minePath !== 'string') {
|
||||||
|
throw new Error('path must be a string');
|
||||||
|
}
|
||||||
|
if (!mode || typeof mode !== 'string') {
|
||||||
|
throw new Error('mode must be a string');
|
||||||
|
}
|
||||||
|
if (!wing || typeof wing !== 'string') {
|
||||||
|
throw new Error('wing must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject unsafe characters
|
||||||
|
const unsafePattern = /[;&|`$(){}[\]<>]/;
|
||||||
|
if (unsafePattern.test(minePath) || unsafePattern.test(mode) || unsafePattern.test(wing)) {
|
||||||
|
throw new Error('Arguments contain unsafe characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate mode is one of allowed values
|
||||||
|
const allowedModes = ['convos', 'files', 'web'];
|
||||||
|
if (!allowedModes.includes(mode)) {
|
||||||
|
throw new Error(`Mode must be one of: ${allowedModes.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [minePath, '--mode', mode, '--wing', wing];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'search': {
|
||||||
|
command: 'mempalace',
|
||||||
|
args: ['search'],
|
||||||
|
requiredArgs: ['query', 'wing'],
|
||||||
|
optionalArgs: ['room', 'n'],
|
||||||
|
validate: (args) => {
|
||||||
|
const { query, wing, room, n } = args;
|
||||||
|
|
||||||
|
// Validate required arguments
|
||||||
|
if (!query || typeof query !== 'string') {
|
||||||
|
throw new Error('query must be a string');
|
||||||
|
}
|
||||||
|
if (!wing || typeof wing !== 'string') {
|
||||||
|
throw new Error('wing must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject unsafe characters in query and wing
|
||||||
|
const unsafePattern = /[;&|`$(){}[\]<>]/;
|
||||||
|
if (unsafePattern.test(query) || unsafePattern.test(wing)) {
|
||||||
|
throw new Error('Arguments contain unsafe characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build command args
|
||||||
|
const cmdArgs = [query, '--wing', wing];
|
||||||
|
|
||||||
|
// Add optional arguments
|
||||||
|
if (room && typeof room === 'string' && !unsafePattern.test(room)) {
|
||||||
|
cmdArgs.push('--room', room);
|
||||||
|
}
|
||||||
|
if (n && typeof n === 'number' && n > 0 && n <= 100) {
|
||||||
|
cmdArgs.push('--n', String(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmdArgs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
command: 'mempalace',
|
||||||
|
args: ['status'],
|
||||||
|
requiredArgs: ['wing'],
|
||||||
|
validate: (args) => {
|
||||||
|
const { wing } = args;
|
||||||
|
|
||||||
|
if (!wing || typeof wing !== 'string') {
|
||||||
|
throw new Error('wing must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject unsafe characters
|
||||||
|
if (/[;&|`$(){}[\]<>]/.test(wing)) {
|
||||||
|
throw new Error('wing contains unsafe characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['--wing', wing];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'add_drawer': {
|
||||||
|
command: 'mempalace',
|
||||||
|
args: ['add_drawer'],
|
||||||
|
requiredArgs: ['wing', 'room', 'text'],
|
||||||
|
validate: (args) => {
|
||||||
|
const { wing, room, text } = args;
|
||||||
|
|
||||||
|
// Validate all arguments
|
||||||
|
if (!wing || typeof wing !== 'string') {
|
||||||
|
throw new Error('wing must be a string');
|
||||||
|
}
|
||||||
|
if (!room || typeof room !== 'string') {
|
||||||
|
throw new Error('room must be a string');
|
||||||
|
}
|
||||||
|
if (!text || typeof text !== 'string') {
|
||||||
|
throw new Error('text must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject unsafe characters
|
||||||
|
const unsafePattern = /[;&|`$(){}[\]<>]/;
|
||||||
|
if (unsafePattern.test(wing) || unsafePattern.test(room)) {
|
||||||
|
throw new Error('wing or room contains unsafe characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text can contain more characters, but still reject dangerous ones
|
||||||
|
if (/[;&|`$]/.test(text)) {
|
||||||
|
throw new Error('text contains unsafe characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [wing, room, text];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and execute a MemPalace action
|
||||||
|
*/
|
||||||
|
async function executeMemPalaceAction(action, args = {}) {
|
||||||
|
// Check if action is allowed
|
||||||
|
if (!ALLOWED_ACTIONS[action]) {
|
||||||
|
throw new Error(`Unknown action: ${action}. Allowed actions: ${Object.keys(ALLOWED_ACTIONS).join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionConfig = ALLOWED_ACTIONS[action];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate arguments and build command args
|
||||||
|
const commandArgs = actionConfig.validate(args);
|
||||||
|
|
||||||
|
// Build full command
|
||||||
|
const command = actionConfig.command;
|
||||||
|
const fullArgs = [...actionConfig.args, ...commandArgs];
|
||||||
|
|
||||||
|
console.log(`[MemPalace] Executing: ${command} ${fullArgs.join(' ')}`);
|
||||||
|
|
||||||
|
// Execute with spawn (safer than exec)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(command, fullArgs, {
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
shell: false // Don't use shell
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
child.stdout.on('data', (data) => {
|
||||||
|
stdout += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (data) => {
|
||||||
|
stderr += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve({ stdout, stderr, code });
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Command failed with code ${code}: ${stderr}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[MemPalace] Validation error for ${action}:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up secure IPC handlers
|
||||||
|
*/
|
||||||
|
function setupSecureMemPalaceIPC() {
|
||||||
|
// Remove any existing handlers
|
||||||
|
ipcMain.removeHandler('exec-python');
|
||||||
|
|
||||||
|
// Set up typed action handlers
|
||||||
|
ipcMain.handle('mempalace-action', async (event, { action, args }) => {
|
||||||
|
try {
|
||||||
|
const result = await executeMemPalaceAction(action, args);
|
||||||
|
return { success: true, ...result };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[MemPalace] Action ${action} failed:`, error.message);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep legacy exec-python handler but with validation (for backward compatibility)
|
||||||
|
// This should be deprecated and removed in future versions
|
||||||
|
ipcMain.handle('exec-python', async (event, command) => {
|
||||||
|
console.warn('[MemPalace] DEPRECATED: exec-python called. Use mempalace-action instead.');
|
||||||
|
|
||||||
|
// Parse the command to extract action and args
|
||||||
|
const parts = command.trim().split(/\s+/);
|
||||||
|
if (parts.length < 2 || parts[0] !== 'mempalace') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Only mempalace commands are allowed',
|
||||||
|
deprecated: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = parts[1];
|
||||||
|
const args = {};
|
||||||
|
|
||||||
|
// Parse arguments from command string
|
||||||
|
// This is a simplified parser - in production, use proper argument parsing
|
||||||
|
for (let i = 2; i < parts.length; i++) {
|
||||||
|
const part = parts[i];
|
||||||
|
if (part.startsWith('--')) {
|
||||||
|
const key = part.slice(2);
|
||||||
|
const value = parts[i + 1];
|
||||||
|
if (value && !value.startsWith('--')) {
|
||||||
|
args[key] = value;
|
||||||
|
i++; // Skip next part
|
||||||
|
}
|
||||||
|
} else if (!args.path && !args.wing && !args.query) {
|
||||||
|
// Positional arguments
|
||||||
|
if (!args.path) args.path = part;
|
||||||
|
else if (!args.wing) args.wing = part;
|
||||||
|
else if (!args.query) args.query = part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await executeMemPalaceAction(action, args);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
...result,
|
||||||
|
deprecated: true,
|
||||||
|
warning: 'This endpoint is deprecated. Use mempalace-action instead.'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
deprecated: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[MemPalace] Secure IPC handlers registered');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setupSecureMemPalaceIPC,
|
||||||
|
executeMemPalaceAction,
|
||||||
|
ALLOWED_ACTIONS
|
||||||
|
};
|
||||||
24
preload.js
Normal file
24
preload.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Preload script for Electron
|
||||||
|
* Exposes secure MemPalace API to renderer
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
// Expose secure MemPalace API to renderer
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
// Secure typed API
|
||||||
|
mempalaceAction: (action, args) => {
|
||||||
|
return ipcRenderer.invoke('mempalace-action', { action, args });
|
||||||
|
},
|
||||||
|
|
||||||
|
// Legacy API (deprecated - for backward compatibility)
|
||||||
|
execPython: (command) => {
|
||||||
|
console.warn('[MemPalace] execPython is deprecated. Use mempalaceAction instead.');
|
||||||
|
return ipcRenderer.invoke('exec-python', command);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
platform: process.platform,
|
||||||
|
versions: process.versions
|
||||||
|
});
|
||||||
177
tests/test_secure_mempalace_ipc.js
Normal file
177
tests/test_secure_mempalace_ipc.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* Tests for secure MemPalace IPC bridge
|
||||||
|
* Issue #1423: [SECURITY] Electron MemPalace bridge allows arbitrary command execution
|
||||||
|
*/
|
||||||
|
|
||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const { setupSecureMemPalaceIPC, executeMemPalaceAction, ALLOWED_ACTIONS } = require('./electron-mempalace-bridge');
|
||||||
|
|
||||||
|
// Mock Electron IPC
|
||||||
|
const mockIpcMain = {
|
||||||
|
handlers: {},
|
||||||
|
handle: function(channel, handler) {
|
||||||
|
this.handlers[channel] = handler;
|
||||||
|
},
|
||||||
|
removeHandler: function(channel) {
|
||||||
|
delete this.handlers[channel];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock child_process.spawn
|
||||||
|
const mockSpawn = jest.fn();
|
||||||
|
|
||||||
|
// Setup before tests
|
||||||
|
test.before(() => {
|
||||||
|
// Mock require
|
||||||
|
const Module = require('module');
|
||||||
|
const originalRequire = Module.prototype.require;
|
||||||
|
|
||||||
|
Module.prototype.require = function(id) {
|
||||||
|
if (id === 'child_process') {
|
||||||
|
return { spawn: mockSpawn };
|
||||||
|
}
|
||||||
|
if (id === 'electron') {
|
||||||
|
return { ipcMain: mockIpcMain };
|
||||||
|
}
|
||||||
|
return originalRequire.apply(this, arguments);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ALLOWED_ACTIONS contains expected actions', () => {
|
||||||
|
const expectedActions = ['init', 'mine', 'search', 'status', 'add_drawer'];
|
||||||
|
expectedActions.forEach(action => {
|
||||||
|
assert.ok(ALLOWED_ACTIONS[action], `Should have ${action} action`);
|
||||||
|
assert.ok(ALLOWED_ACTIONS[action].command, `${action} should have command`);
|
||||||
|
assert.ok(ALLOWED_ACTIONS[action].args, `${action} should have args`);
|
||||||
|
assert.ok(ALLOWED_ACTIONS[action].validate, `${action} should have validate function`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Valid init action works', async () => {
|
||||||
|
// Mock spawn to return success
|
||||||
|
const mockChild = {
|
||||||
|
stdout: { on: (event, cb) => { if (event === 'data') cb('OK'); } },
|
||||||
|
stderr: { on: () => {} },
|
||||||
|
on: (event, cb) => { if (event === 'close') cb(0); }
|
||||||
|
};
|
||||||
|
mockSpawn.mockReturnValue(mockChild);
|
||||||
|
|
||||||
|
const result = await executeMemPalaceAction('init', { palacePath: '/safe/path' });
|
||||||
|
assert.equal(result.stdout, 'OK');
|
||||||
|
assert.equal(result.stderr, '');
|
||||||
|
assert.equal(result.code, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Valid mine action works', async () => {
|
||||||
|
const mockChild = {
|
||||||
|
stdout: { on: (event, cb) => { if (event === 'data') cb('Mined'); } },
|
||||||
|
stderr: { on: () => {} },
|
||||||
|
on: (event, cb) => { if (event === 'close') cb(0); }
|
||||||
|
};
|
||||||
|
mockSpawn.mockReturnValue(mockChild);
|
||||||
|
|
||||||
|
const result = await executeMemPalaceAction('mine', {
|
||||||
|
path: '/safe/path',
|
||||||
|
mode: 'convos',
|
||||||
|
wing: 'test_wing'
|
||||||
|
});
|
||||||
|
assert.equal(result.stdout, 'Mined');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects unsafe characters in init', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('init', { palacePath: '/path; rm -rf /' }),
|
||||||
|
{ message: /unsafe characters/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects unsafe characters in mine', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('mine', {
|
||||||
|
path: '/path; rm -rf /',
|
||||||
|
mode: 'convos',
|
||||||
|
wing: 'test'
|
||||||
|
}),
|
||||||
|
{ message: /unsafe characters/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects unsafe characters in search', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('search', {
|
||||||
|
query: 'test; rm -rf /',
|
||||||
|
wing: 'test'
|
||||||
|
}),
|
||||||
|
{ message: /unsafe characters/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects unknown actions', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('unknown', {}),
|
||||||
|
{ message: /Unknown action/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects invalid mine mode', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('mine', {
|
||||||
|
path: '/safe/path',
|
||||||
|
mode: 'invalid_mode',
|
||||||
|
wing: 'test'
|
||||||
|
}),
|
||||||
|
{ message: /Mode must be one of/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects missing required arguments', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('mine', {
|
||||||
|
path: '/safe/path',
|
||||||
|
// Missing mode and wing
|
||||||
|
}),
|
||||||
|
{ message: /must be a string/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Search with optional arguments works', async () => {
|
||||||
|
const mockChild = {
|
||||||
|
stdout: { on: (event, cb) => { if (event === 'data') cb('Results'); } },
|
||||||
|
stderr: { on: () => {} },
|
||||||
|
on: (event, cb) => { if (event === 'close') cb(0); }
|
||||||
|
};
|
||||||
|
mockSpawn.mockReturnValue(mockChild);
|
||||||
|
|
||||||
|
const result = await executeMemPalaceAction('search', {
|
||||||
|
query: 'test query',
|
||||||
|
wing: 'test_wing',
|
||||||
|
room: 'test_room',
|
||||||
|
n: 10
|
||||||
|
});
|
||||||
|
assert.equal(result.stdout, 'Results');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects unsafe room in search', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('search', {
|
||||||
|
query: 'safe query',
|
||||||
|
wing: 'safe_wing',
|
||||||
|
room: 'room; rm -rf /'
|
||||||
|
}),
|
||||||
|
{ message: /unsafe characters/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rejects unsafe text in add_drawer', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => executeMemPalaceAction('add_drawer', {
|
||||||
|
wing: 'safe_wing',
|
||||||
|
room: 'safe_room',
|
||||||
|
text: 'text; rm -rf /'
|
||||||
|
}),
|
||||||
|
{ message: /unsafe characters/ }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('All secure MemPalace IPC tests passed!');
|
||||||
Reference in New Issue
Block a user