UNPKG

ludus-mcp

Version:

MCP server for managing Ludus cybersecurity training environments through natural language commands

277 lines (269 loc) 9.9 kB
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