diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs index 5c6c47c..4c6e3f0 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs @@ -1,4 +1,5 @@ using ast_visual_studio_extension.CxExtension.Commands; +using ast_visual_studio_extension.CxExtension.DevAssist.Commands; using log4net; using log4net.Appender; using log4net.Config; @@ -69,6 +70,12 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Command to create Checkmarx extension main window await CxWindowCommand.InitializeAsync(this); + + // Initialize AI Chat Command (Fix with Checkmarx One Assist) + await AIChatCommand.InitializeAsync(this); + + // Set package on CopilotIntegration so ASCA "Fix with AI" can run extension-manager checks + DevAssist.Services.CopilotIntegration.Package = this; } catch (Exception ex) { diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct index 2d6d9c2..8d9e8f8 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct @@ -34,6 +34,14 @@ Checkmarx + @@ -46,6 +54,7 @@ + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Commands/AIChatCommand.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Commands/AIChatCommand.cs new file mode 100644 index 0000000..2f53761 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Commands/AIChatCommand.cs @@ -0,0 +1,143 @@ +using Microsoft.VisualStudio.Shell; +using System; +using System.ComponentModel.Design; +using System.Threading.Tasks; +using ast_visual_studio_extension.CxExtension.DevAssist.Services; +using ast_visual_studio_extension.CxWrapper.Models; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Commands +{ + public class AIChatCommand + { + public const int CommandId = 0x0300; + public static readonly Guid CommandSet = new Guid("e46cd6d8-268d-4e77-9074-071e72a25f39"); + + private readonly AsyncPackage _package; + private readonly CopilotIntegration _copilotService; + private readonly PromptBuilderService _promptBuilder; + + private AIChatCommand(AsyncPackage package) + { + _package = package ?? throw new ArgumentNullException(nameof(package)); + _copilotService = new CopilotIntegration(); + _promptBuilder = new PromptBuilderService(); + } + + public static async Task InitializeAsync(AsyncPackage package) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); + + var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; + if (commandService != null) + { + var command = new AIChatCommand(package); + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new MenuCommand(command.Execute, menuCommandID); + commandService.AddCommand(menuItem); + } + } + + /// + /// Execute AI Chat command with vulnerability data + /// + private async void Execute(object sender, EventArgs e) + { + try + { + // Get vulnerability data from command parameter or context + var vulnerabilityData = GetVulnerabilityDataFromContext(e); + + if (vulnerabilityData == null) + { + System.Windows.MessageBox.Show("No vulnerability data available for AI assistance.", + "AI Chat", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); + return; + } + + // Build appropriate prompt based on vulnerability type + string prompt = BuildPromptForVulnerability(vulnerabilityData); + + // Open Copilot with the prompt + bool success = await _copilotService.OpenCopilotChatAsync(prompt, _package); + + if (!success) + { + System.Windows.MessageBox.Show("Failed to open GitHub Copilot. Please ensure it's installed and try again.", + "AI Chat Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); + } + } + catch (Exception ex) + { + System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}", + "Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); + } + } + + /// + /// Public method to execute AI chat with specific vulnerability data + /// + public async Task ExecuteWithDataAsync(CxAscaDetail ascaData) + { + try + { + string prompt = _promptBuilder.BuildASCAPrompt( + ascaData.RuleName, + ascaData.RemediationAdvise, + ascaData.Severity, + ascaData.FileName, + ascaData.Line + ); + + await _copilotService.OpenCopilotChatAsync(prompt, _package); + } + catch (Exception ex) + { + System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}", + "AI Chat Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); + } + } + + /// + /// Public method to execute AI chat with SCA vulnerability data + /// + public async Task ExecuteWithSCADataAsync(string packageName, string version, string severity, string packageManager = null) + { + try + { + string prompt = _promptBuilder.BuildSCAPrompt(packageName, version, severity, packageManager); + await _copilotService.OpenCopilotChatAsync(prompt, _package); + } + catch (Exception ex) + { + System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}", + "AI Chat Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); + } + } + + private object GetVulnerabilityDataFromContext(EventArgs e) + { + // This would extract vulnerability data from the command context + // Implementation depends on how data is passed to the command + // For now, return null - will be implemented based on UI integration + return null; + } + + private string BuildPromptForVulnerability(object vulnerabilityData) + { + // Build prompt based on vulnerability data type + if (vulnerabilityData is CxAscaDetail ascaData) + { + return _promptBuilder.BuildASCAPrompt( + ascaData.RuleName, + ascaData.RemediationAdvise, + ascaData.Severity, + ascaData.FileName, + ascaData.Line + ); + } + + // Add other vulnerability types as needed + return _promptBuilder.BuildGenericPrompt("Unknown vulnerability", "Unknown"); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Services/CopilotIntegration.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Services/CopilotIntegration.cs new file mode 100644 index 0000000..a85acf6 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Services/CopilotIntegration.cs @@ -0,0 +1,176 @@ +using ast_visual_studio_extension.CxExtension.Utils; +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.ExtensionManager; +using Microsoft.VisualStudio.Shell; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Services +{ + public class CopilotIntegration + { + /// + /// Optional package set by the hosting VS package (e.g. CxWindowPackage) so that + /// OpenCopilotChatAsync can run extension-manager checks when called without an explicit package (e.g. from ASCA marker). + /// + public static AsyncPackage Package { get; set; } + + private DTE2 _dte; + private OutputWindowPane _outputPane; + + public CopilotIntegration() + { + ThreadHelper.ThrowIfNotOnUIThread(); + _dte = Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(EnvDTE.DTE)) as DTE2; + } + + private void Log(string message) + { + try + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (_dte == null) + { + System.Diagnostics.Debug.WriteLine($"{DateTime.Now:HH:mm:ss.fff} [Copilot] {message}"); + return; + } + if (_outputPane == null) + _outputPane = OutputPaneUtils.InitializeOutputPane(_dte.ToolWindows.OutputWindow, CxConstants.EXTENSION_TITLE); + _outputPane?.OutputString($"{DateTime.Now:HH:mm:ss.fff} [Copilot] {message}\n"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[CopilotIntegration] Log failed: {ex.Message}"); + } + } + + /// + /// Detect if Copilot Chat extension is installed (GitHub.CopilotChat VSIX). + /// Returns true if the Copilot Chat extension is in the list of installed extensions. + /// + public async Task IsCopilotChatEnabledAsync(AsyncPackage package) + { + if (package == null) + { + Log("[IsCopilotChatEnabled] package is null -> false"); + return false; + } + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var extensionManager = await package.GetServiceAsync(typeof(SVsExtensionManager)) as IVsExtensionManager; + if (extensionManager == null) + { + Log("[IsCopilotChatEnabled] IVsExtensionManager is null -> false"); + return false; + } + + const string CopilotChatVsixId = "Component.VisualStudio.GitHub.Copilot"; + + bool found = extensionManager.GetInstalledExtensions() + .Any(e => e.Header.Identifier.Equals(CopilotChatVsixId, StringComparison.OrdinalIgnoreCase)); + + Log(found ? "[IsCopilotChatEnabled] Copilot Chat extension installed -> true" : "[IsCopilotChatEnabled] Copilot Chat extension not found -> false"); + return found; + } + + /// + /// Check if GitHub Copilot for Visual Studio is installed and Chat is available (legacy/alias). + + public async Task IsCopilotInstalledAsync(AsyncPackage package) + { + return await IsCopilotChatEnabledAsync(package); + } + + /// Open GitHub Copilot Chat with optional prompt. + public async Task OpenCopilotChatAsync(string prompt = null, AsyncPackage package = null) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + AsyncPackage packageToUse = package ?? Package; + if (packageToUse == null) + Log("[OpenCopilotChatAsync] WARNING: package is null and CopilotIntegration.Package was not set (e.g. CxWindowPackage should set it)"); + + try + { + bool success = await TryOpenCopilotChatAsync(packageToUse); + + if (success && !string.IsNullOrEmpty(prompt)) + { + await Task.Delay(1000); + await SendPromptToCopilotAsync(prompt); + } + else + { + if (string.IsNullOrEmpty(prompt)) Log("[OpenCopilotChatAsync] Skipping prompt: no prompt"); + } + return success; + } + catch (Exception ex) + { + ShowError($"Failed to open GitHub Copilot: {ex.Message}"); + return false; + } + } + private async Task TryOpenCopilotChatAsync(AsyncPackage package) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // default keyboard shortcut (Ctrl+Backslash then C) to open GitHub Copilot Chat + try + { + System.Windows.Forms.SendKeys.SendWait("^\\c"); + Log("[TryOpenCopilotChatAsync] Default shortcut (Ctrl+\\ then C) sent"); + return true; + } + catch (Exception ex) + { + Log($"[TryOpenCopilotChatAsync] Shortcut fallback failed: {ex.Message}"); + } + return false; + } + + /// + /// Enumerates DTE commands whose name contains "Copilot" or "GitHub" and logs them to Output. + /// Use this to find the exact command name that opens Copilot Chat on your machine. + /// + + private async Task SendPromptToCopilotAsync(string prompt) + { + Log("[SendPromptToCopilotAsync] Enter"); + Log($"[SendPromptToCopilotAsync] prompt length={prompt?.Length ?? 0}"); + try + { + Log("[SendPromptToCopilotAsync] Method 1: clipboard + paste"); + Clipboard.SetText(prompt); + Log("[SendPromptToCopilotAsync] Clipboard.SetText done, waiting 100ms"); + await Task.Delay(100); + System.Windows.Forms.SendKeys.SendWait("^v{ENTER}"); + Log("[SendPromptToCopilotAsync] SendKeys ^v{ENTER} sent"); + } + catch (Exception ex) + { + Log($"[SendPromptToCopilotAsync] Method 1 failed: {ex.Message}"); + Log("[SendPromptToCopilotAsync] Method 2: direct SendKeys (fallback)"); + try + { + System.Windows.Forms.SendKeys.SendWait(prompt.Replace("{", "{{").Replace("}", "}}")); + System.Windows.Forms.SendKeys.SendWait("{ENTER}"); + Log("[SendPromptToCopilotAsync] Method 2 completed"); + } + catch (Exception ex2) + { + Log($"[SendPromptToCopilotAsync] Method 2 failed: {ex2.Message}"); + } + } + } + private void ShowError(string message) + { + Log($"[ShowError] {message}"); + MessageBox.Show(message, "Copilot Integration Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Services/PromptBuilderService.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Services/PromptBuilderService.cs new file mode 100644 index 0000000..3ac9035 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Services/PromptBuilderService.cs @@ -0,0 +1,154 @@ +namespace ast_visual_studio_extension.CxExtension.DevAssist.Services +{ + public class PromptBuilderService + { + /// + /// Build prompt for ASCA (SAST) vulnerabilities + /// + public string BuildASCAPrompt(string ruleName, string remediationAdvice, string severity, string filePath = null, int lineNumber = 0) + { + var locationInfo = !string.IsNullOrEmpty(filePath) && lineNumber > 0 + ? $"**Location**: {filePath} (Line {lineNumber})\n" + : ""; + + return $@"You are Checkmarx One Assist, an AI security expert. + +I need help fixing a security vulnerability in my code: + +**Rule**: {ruleName} +**Severity**: {severity} +{locationInfo}**Remediation Advice**: {remediationAdvice} + +Please provide: +1. **Root Cause Analysis**: Explain what makes this code vulnerable +2. **Step-by-Step Fix**: Specific code changes to resolve the issue +3. **Secure Code Example**: Show the corrected implementation +4. **Validation**: How to verify the fix works properly + +Focus on practical, actionable solutions that I can implement immediately."; + } + + /// + /// Build prompt for SCA (package) vulnerabilities + /// + public string BuildSCAPrompt(string packageName, string version, string severity, string packageManager = null) + { + var managerInfo = !string.IsNullOrEmpty(packageManager) + ? $" (Package Manager: {packageManager})" + : ""; + + return $@"You are Checkmarx One Assist, an AI security expert. + +I have a vulnerable dependency in my project: + +**Package**: {packageName} v{version}{managerInfo} +**Severity**: {severity} + +Please help me fix this vulnerability: +1. **Risk Assessment**: What security risks does this vulnerability pose? +2. **Update Strategy**: What version should I upgrade to? +3. **Implementation Steps**: Exact commands/changes needed +4. **Testing**: How to verify the update doesn't break functionality +5. **Alternatives**: If no safe version exists, suggest alternative packages + +Provide specific, actionable remediation steps."; + } + + /// + /// Build prompt for Secrets detection + /// + public string BuildSecretsPrompt(string secretType, string description, string severity, string filePath = null, int lineNumber = 0) + { + var locationInfo = !string.IsNullOrEmpty(filePath) && lineNumber > 0 + ? $"**Location**: {filePath} (Line {lineNumber})\n" + : ""; + + return $@"You are Checkmarx One Assist, an AI security expert. + +A secret has been detected in my code: + +**Type**: {secretType} +**Severity**: {severity} +{locationInfo}**Description**: {description} + +Please help me secure this secret: +1. **Risk Analysis**: Why is this secret exposure dangerous? +2. **Immediate Action**: Steps to remove the secret from code +3. **Secure Storage**: Best practices for storing this type of secret +4. **Code Changes**: Examples of secure implementation +5. **Prevention**: How to avoid similar issues in the future + +Provide specific remediation steps with code examples."; + } + + /// + /// Build prompt for Infrastructure as Code (IaC) issues + /// + public string BuildIaCPrompt(string title, string description, string severity, string expectedValue = null, string actualValue = null) + { + var valueInfo = !string.IsNullOrEmpty(expectedValue) && !string.IsNullOrEmpty(actualValue) + ? $"**Expected Value**: {expectedValue}\n**Actual Value**: {actualValue}\n" + : ""; + + return $@"You are Checkmarx One Assist, an AI security expert. + +I have a security misconfiguration in my Infrastructure as Code: + +**Issue**: {title} +**Severity**: {severity} +{valueInfo}**Description**: {description} + +Please help me fix this configuration issue: +1. **Security Impact**: What risks does this misconfiguration create? +2. **Configuration Fix**: Exact changes needed in the IaC template +3. **Best Practices**: Secure configuration recommendations +4. **Validation**: How to test the fix works correctly + +Provide specific configuration examples and remediation steps."; + } + + /// + /// Build prompt for Container security issues + /// + public string BuildContainersPrompt(string imageName, string imageTag, string severity, string issueType) + { + return $@"You are Checkmarx One Assist, an AI security expert. + +I have a security issue in my container configuration: + +**Container Image**: {imageName}:{imageTag} +**Issue Type**: {issueType} +**Severity**: {severity} + +Please help me fix this container security issue: +1. **Vulnerability Analysis**: What security risks does this create? +2. **Image Fix**: Steps to resolve the issue (update, rebuild, etc.) +3. **Dockerfile Changes**: Any needed changes to container configuration +4. **Security Hardening**: Additional container security best practices +5. **Validation**: How to verify the container is now secure + +Provide specific remediation steps for container security."; + } + + /// + /// Build generic prompt when vulnerability type is unknown + /// + public string BuildGenericPrompt(string description, string severity) + { + return $@"You are Checkmarx One Assist, an AI security expert. + +I need help with a security issue: + +**Issue**: {description} +**Severity**: {severity} + +Please help me understand and fix this security vulnerability: +1. **Analysis**: What type of security issue is this? +2. **Risk Assessment**: What are the potential impacts? +3. **Remediation**: Step-by-step fix instructions +4. **Best Practices**: How to prevent similar issues + +Provide specific, actionable guidance to resolve this security concern."; + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/Services/ASCAUIManager.cs b/ast-visual-studio-extension/CxExtension/Services/ASCAUIManager.cs index 2bb89f2..ceb374e 100644 --- a/ast-visual-studio-extension/CxExtension/Services/ASCAUIManager.cs +++ b/ast-visual-studio-extension/CxExtension/Services/ASCAUIManager.cs @@ -1,4 +1,5 @@ using ast_visual_studio_extension.CxWrapper.Models; +using ast_visual_studio_extension.CxExtension.DevAssist.Services; using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio.Shell; @@ -315,18 +316,57 @@ public void OnBeforeBufferClose() public int GetMarkerCommandInfo(IVsTextMarker pMarker, int iItem, string[] pbstrText, uint[] pcmdf) { - if (pbstrText != null && pbstrText.Length > 0) - pbstrText[0] = string.Empty; - if (pcmdf != null && pcmdf.Length > 0) - pcmdf[0] = 0; + const uint OLECMDF_ENABLED = 0x02; + const uint OLECMDF_SUPPORTED = 0x01; + uint enabledSupported = OLECMDF_ENABLED | OLECMDF_SUPPORTED; + + switch (iItem) + { + case 0: + if (pbstrText != null && pbstrText.Length > 0) + pbstrText[0] = "Fix with Checkmarx One Assist"; + if (pcmdf != null && pcmdf.Length > 0) + pcmdf[0] = enabledSupported; + return VSConstants.S_OK; + case 1: + if (pbstrText != null && pbstrText.Length > 0) + pbstrText[0] = string.Empty; + if (pcmdf != null && pcmdf.Length > 0) + pcmdf[0] = 0; + return VSConstants.S_OK; + } return VSConstants.E_NOTIMPL; } public int ExecMarkerCommand(IVsTextMarker pMarker, int iItem) { + switch (iItem) + { + case 0: // Fix with AI + _ = ThreadHelper.JoinableTaskFactory.RunAsync(async () => await ExecuteFixWithAIAsync()); + return VSConstants.S_OK; + } return VSConstants.E_NOTIMPL; } + private async Task ExecuteFixWithAIAsync() + { + try + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var copilotService = new CopilotIntegration(); + var promptBuilder = new PromptBuilderService(); + string prompt = promptBuilder.BuildASCAPrompt(_ruleName, _remediation, "", "",0); + await copilotService.OpenCopilotChatAsync(prompt); + } + catch (Exception ex) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + System.Windows.MessageBox.Show($"Error opening AI Chat: {ex.Message}", "AI Chat Error", + System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning); + } + } + /// /// Called after the span is reloaded. No action needed in our implementation. /// diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index e15da91..ff85e52 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -1,4 +1,4 @@ - + 17.0 @@ -8,7 +8,7 @@ v4.7.2 true - true + false @@ -20,9 +20,11 @@ false + bin\Debug\ false + bin\Release\ @@ -82,6 +84,9 @@ + + + @@ -190,6 +195,7 @@ 19.225.1 + runtime; build; native; contentfiles; analyzers; buildtransitive all