vulnmatter-extension
Version:
VS Code extension for CVE vulnerability analysis using the VulnMatter API with X-API-Key. See CHANGELOG.md for release notes.
321 lines (282 loc) • 10.4 kB
text/typescript
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
// This method is called when your extension is activated
// Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "vulnmatter-cli" is now active!');
// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
const disposable = vscode.commands.registerCommand('vulnmatter-cli.helloWorld', () => {
// The code you place here will be executed every time your command is executed
// Display a message box to the user
vscode.window.showInformationMessage('Hello World from Mi Primera Extension!');
});
// Command to configure API Key
const configureApiKeyDisposable = vscode.commands.registerCommand('vulnmatter-cli.configureApiKey', () => {
createApiKeyConfigPanel(context);
});
// Command to test MCP connection (example usage of stored API key)
const testMcpConnectionDisposable = vscode.commands.registerCommand('vulnmatter-cli.testMcpConnection', async () => {
const apiKey = await getStoredApiKey(context);
if (apiKey) {
vscode.window.showInformationMessage(`MCP Connection Test: API Key found (${apiKey.substring(0, 8)}...)`);
// Here you would implement the actual MCP connection logic
console.log('API Key available for MCP connection:', apiKey.substring(0, 8) + '...');
} else {
const result = await vscode.window.showWarningMessage(
'No API Key configured. Would you like to configure it now?',
'Configure API Key'
);
if (result === 'Configure API Key') {
vscode.commands.executeCommand('vulnmatter-cli.configureApiKey');
}
}
});
context.subscriptions.push(disposable);
context.subscriptions.push(configureApiKeyDisposable);
context.subscriptions.push(testMcpConnectionDisposable);
}
function createApiKeyConfigPanel(context: vscode.ExtensionContext) {
// Create and show a new webview panel
const panel = vscode.window.createWebviewPanel(
'apiKeyConfig', // Identifies the type of the webview
'Configure X-API-Key', // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in
{
// Enable scripts in the webview
enableScripts: true,
// Restrict the webview to only load content from our extension's directory
localResourceRoots: [context.extensionUri]
}
);
// Set the HTML content for the webview with strict CSP and nonce
panel.webview.html = getWebviewContent(panel.webview);
// Handle messages from the webview
panel.webview.onDidReceiveMessage(
async message => {
switch (message.command) {
case 'saveApiKey':
if (message.apiKey && message.apiKey.trim()) {
// Store the API key securely
await context.secrets.store('vulnmatter.x-api-key', message.apiKey.trim());
vscode.window.showInformationMessage('X-API-Key saved successfully!');
panel.dispose();
} else {
vscode.window.showErrorMessage('Please enter a valid API key');
}
break;
case 'loadApiKey':
// Load existing API key (if any) and send it to the webview
const existingKey = await context.secrets.get('vulnmatter.x-api-key');
panel.webview.postMessage({
command: 'displayApiKey',
apiKey: existingKey || ''
});
break;
case 'clearApiKey':
await context.secrets.delete('vulnmatter.x-api-key');
vscode.window.showInformationMessage('X-API-Key cleared successfully!');
panel.webview.postMessage({
command: 'displayApiKey',
apiKey: ''
});
break;
case 'ready':
// Send existing API key to webview when it's ready
const readyKey = await context.secrets.get('vulnmatter.x-api-key');
panel.webview.postMessage({
command: 'displayApiKey',
apiKey: readyKey || ''
});
break;
}
},
undefined,
context.subscriptions
);
}
function getWebviewContent(webview: vscode.Webview): string {
const nonce = getNonce();
const cspSource = webview.cspSource;
return `
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${cspSource} https:; style-src ${cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}';">
<title>Configure X-API-Key</title>
<style>
body {
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
padding: 20px;
max-width: 600px;
}
h1 {
color: var(--vscode-titleBar-activeForeground);
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
input[type="password"], input[type="text"] {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--vscode-input-border);
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border-radius: 3px;
font-size: 14px;
box-sizing: border-box;
}
input[type="password"]:focus, input[type="text"]:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.button-group {
display: flex;
gap: 10px;
margin-top: 20px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
button:hover {
background-color: var(--vscode-button-hoverBackground);
}
.secondary-button {
background-color: var(--vscode-button-secondaryBackground) ;
color: var(--vscode-button-secondaryForeground) ;
}
.secondary-button:hover {
background-color: var(--vscode-button-secondaryHoverBackground) ;
}
.danger-button {
background-color: var(--vscode-inputValidation-errorBackground) ;
color: var(--vscode-inputValidation-errorForeground) ;
}
.info {
background-color: var(--vscode-editorWidget-background);
border: 1px solid var(--vscode-editorWidget-border);
padding: 15px;
margin: 20px 0;
border-radius: 3px;
font-size: 13px;
}
.toggle-container {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.toggle-container input[type="checkbox"] {
width: auto;
}
</style>
</head>
<body>
<h1>🔐 Configure X-API-Key</h1>
<div class="info">
<strong>ℹ️ Información:</strong><br>
Esta clave API se almacenará de forma segura en tu sistema y será utilizada para conectar con servicios MCP (Model Context Protocol).
</div>
<div class="form-group">
<label for="apiKeyInput">X-API-Key:</label>
<input type="password" id="apiKeyInput" placeholder="Ingresa tu clave API..." />
<div class="toggle-container">
<input type="checkbox" id="showPassword" />
<label for="showPassword">Mostrar clave</label>
</div>
</div>
<div class="button-group">
<button id="saveButton">💾 Guardar Clave</button>
<button id="testButton" class="secondary-button">🧪 Probar Conexión</button>
<button id="clearButton" class="danger-button">🗑️ Limpiar</button>
</div>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
const apiKeyInput = document.getElementById('apiKeyInput');
const showPasswordCheckbox = document.getElementById('showPassword');
const saveButton = document.getElementById('saveButton');
const testButton = document.getElementById('testButton');
const clearButton = document.getElementById('clearButton');
// Toggle password visibility
showPasswordCheckbox.addEventListener('change', function() {
apiKeyInput.type = this.checked ? 'text' : 'password';
});
// Save API key
saveButton.addEventListener('click', () => {
const apiKey = apiKeyInput.value;
if (apiKey.trim()) {
vscode.postMessage({ command: 'saveApiKey', apiKey });
} else {
alert('Por favor, ingresa una clave API válida.');
}
});
// Test connection (placeholder for future implementation)
testButton.addEventListener('click', () => {
const apiKey = apiKeyInput.value.trim();
if (apiKey) {
alert('Funcionalidad de prueba: Próximamente se implementará la verificación de conectividad MCP.');
} else {
alert('Por favor, ingresa una clave API para probar.');
}
});
// Clear API key
clearButton.addEventListener('click', () => {
if (confirm('¿Estás seguro de que deseas eliminar la clave API almacenada?')) {
vscode.postMessage({ command: 'clearApiKey' });
}
});
// Listen for messages from the extension
window.addEventListener('message', event => {
const message = event.data;
if (message?.command === 'displayApiKey') {
apiKeyInput.value = message.apiKey || '';
}
});
// Notify extension that webview is ready
vscode.postMessage({ command: 'ready' });
// Allow Enter key to save
apiKeyInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
saveButton.click();
}
});
</script>
</body>
</html>
`;
}
// Helper function to get stored API key (for use in other parts of the extension)
export async function getStoredApiKey(context: vscode.ExtensionContext): Promise<string | undefined> {
return await context.secrets.get('vulnmatter.x-api-key');
}
// This method is called when your extension is deactivated
export function deactivate() {}
// Generate a nonce for Content Security Policy
function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}