ludus-mcp
Version:
MCP server for managing Ludus cybersecurity training environments through natural language commands
277 lines (269 loc) • 9.9 kB
JavaScript
import { execSync } from 'child_process';
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
export class SecretDialog {
constructor(logger) {
this.logger = logger;
}
/**
* Show platform-specific secret input dialog
*/
async showSecretDialog(options) {
const { secretName, description, isPassword = true, title = 'Enter Secret' } = options;
this.logger.info('Showing secret dialog', {
secretName,
platform: process.platform,
isPassword
});
try {
const platform = process.platform;
if (platform === 'win32') {
return await this.showWindowsDialog(title, description, isPassword);
}
else if (platform === 'darwin') {
return await this.showMacDialog(title, description, isPassword);
}
else {
return await this.showLinuxDialog(title, description, isPassword);
}
}
catch (error) {
this.logger.error('Failed to show secret dialog', { error: error.message });
return {
success: false,
error: error.message
};
}
}
/**
* Windows PowerShell dialog using Windows Forms
*/
async showWindowsDialog(title, description, isPassword) {
const passwordChar = isPassword ? '$textBox.UseSystemPasswordChar = $true' : '';
// Escape strings for PowerShell
const escapedTitle = title.replace(/'/g, "''");
const escapedDescription = description.replace(/'/g, "''");
// Try simpler approach first for debugging
if (process.env.LUDUS_DEBUG_DIALOG) {
return this.showSimpleWindowsDialog(escapedTitle, escapedDescription, isPassword);
}
const psScript = `
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = '${escapedTitle}'
$form.Size = New-Object System.Drawing.Size(400, 200)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
[void]($form.MaximizeBox = $false)
[void]($form.MinimizeBox = $false)
[void]($form.TopMost = $true)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10, 20)
$label.Size = New-Object System.Drawing.Size(370, 40)
$label.Text = '${escapedDescription}'
[void]$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10, 70)
$textBox.Size = New-Object System.Drawing.Size(360, 20)
${passwordChar}
[void]$form.Controls.Add($textBox)
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(225, 120)
$okButton.Size = New-Object System.Drawing.Size(75, 23)
$okButton.Text = "OK"
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $okButton
[void]$form.Controls.Add($okButton)
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Point(310, 120)
$cancelButton.Size = New-Object System.Drawing.Size(75, 23)
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $cancelButton
[void]$form.Controls.Add($cancelButton)
[void]$textBox.Focus()
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
Write-Output $textBox.Text
} else {
Write-Output "__CANCELLED__"
}
$form.Dispose()
`;
try {
// Use temporary file approach to avoid quote escaping issues
const tempDir = os.tmpdir();
const tempFile = path.join(tempDir, `ludus-dialog-${Date.now()}.ps1`);
fs.writeFileSync(tempFile, psScript, 'utf8');
const output = execSync(`powershell -ExecutionPolicy Bypass -File "${tempFile}"`, {
encoding: 'utf8',
timeout: 120000
}).trim();
// Clean up temp file
try {
fs.unlinkSync(tempFile);
}
catch (e) {
// Ignore cleanup errors
}
if (output === '__CANCELLED__') {
return { success: false, cancelled: true };
}
if (!output || output.length === 0) {
return { success: false, error: 'No input received from dialog' };
}
return { success: true, secret: output };
}
catch (error) {
if (error.signal === 'SIGTERM') {
return { success: false, cancelled: true };
}
throw error;
}
}
/**
* Simple Windows dialog for debugging
*/
async showSimpleWindowsDialog(title, description, isPassword) {
const passwordFlag = isPassword ? ' -AsSecureString' : '';
const simpleScript = `
$credential = Read-Host -Prompt '${description}'${passwordFlag}
if ($credential) {
if ($credential.GetType().Name -eq 'SecureString') {
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential)
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
Write-Output $PlainPassword
} else {
Write-Output $credential
}
} else {
Write-Output "__CANCELLED__"
}
`;
try {
const output = execSync(`powershell -ExecutionPolicy Bypass -Command "${simpleScript.replace(/"/g, '""')}"`, {
encoding: 'utf8',
timeout: 120000
}).trim();
if (output === '__CANCELLED__' || !output) {
return { success: false, cancelled: true };
}
return { success: true, secret: output };
}
catch (error) {
if (error.signal === 'SIGTERM') {
return { success: false, cancelled: true };
}
throw error;
}
}
/**
* macOS AppleScript dialog
*/
async showMacDialog(title, description, isPassword) {
const dialogType = isPassword ? 'with hidden answer' : '';
const script = `osascript -e 'display dialog "${description}" default answer "" ${dialogType} with title "${title}" buttons {"Cancel", "OK"} default button "OK"' 2>/dev/null || echo "__CANCELLED__"`;
try {
const output = execSync(script, {
encoding: 'utf8',
timeout: 120000
}).trim();
if (output === '__CANCELLED__' || output.includes('User canceled')) {
return { success: false, cancelled: true };
}
// AppleScript output format: "button returned:OK, text returned:SECRET"
const match = output.match(/text returned:(.*)$/);
if (match) {
return { success: true, secret: match[1] };
}
throw new Error('Failed to parse AppleScript output');
}
catch (error) {
if (error.status === 1) {
return { success: false, cancelled: true };
}
throw error;
}
}
/**
* Linux dialog using zenity or kdialog
*/
async showLinuxDialog(title, description, isPassword) {
// Try zenity first
if (this.hasCommand('zenity')) {
return this.showZenityDialog(title, description, isPassword);
}
// Fallback to kdialog
if (this.hasCommand('kdialog')) {
return this.showKDialogDialog(title, description, isPassword);
}
throw new Error('No GUI dialog tool available (zenity or kdialog required on Linux)');
}
async showZenityDialog(title, description, isPassword) {
const entryType = isPassword ? '--password' : '--entry';
const command = `zenity ${entryType} --title="${title}" --text="${description}" --width=400`;
try {
const output = execSync(command, {
encoding: 'utf8',
timeout: 120000
}).trim();
return { success: true, secret: output };
}
catch (error) {
if (error.status === 1) {
return { success: false, cancelled: true };
}
throw error;
}
}
async showKDialogDialog(title, description, isPassword) {
const dialogType = isPassword ? '--password' : '--inputbox';
const command = `kdialog ${dialogType} "${description}" --title "${title}"`;
try {
const output = execSync(command, {
encoding: 'utf8',
timeout: 120000
}).trim();
return { success: true, secret: output };
}
catch (error) {
if (error.status === 1) {
return { success: false, cancelled: true };
}
throw error;
}
}
/**
* Check if a command is available
*/
hasCommand(command) {
try {
execSync(`which ${command}`, { stdio: 'ignore' });
return true;
}
catch {
return false;
}
}
/**
* Check if GUI is available on the current system
*/
static hasGUI() {
const platform = process.platform;
if (platform === 'win32') {
// Windows almost always has GUI available
return true;
}
else if (platform === 'darwin') {
// macOS always has GUI available
return true;
}
else {
// Linux - check for display
return !!process.env.DISPLAY || !!process.env.WAYLAND_DISPLAY;
}
}
}
//# sourceMappingURL=secretDialog.js.map