From ce99da1f23c739bcc12bc81d15fe78373c736287 Mon Sep 17 00:00:00 2001 From: jcamilomolinar Date: Tue, 7 Jul 2026 13:57:03 -0500 Subject: [PATCH 1/3] refactor: button to fix all vulns in current scan --- ide_extension/vscode/devsecops/README.md | 15 +- ide_extension/vscode/devsecops/package.json | 9 -- .../commands/vulnerabilityCopilotCommands.ts | 151 +++++------------- .../domain/model/metrics/IAiMetricsData.ts | 3 +- .../vscode/devsecops/src/extension.ts | 4 +- .../src/tree/ResultsTreeDataProvider.ts | 6 +- .../src/tree/results/finding/FindingDetail.ts | 23 +-- .../src/tree/results/finding/FindingItem.ts | 5 +- .../tree/results/finding/FindingWebview.ts | 18 +-- 9 files changed, 62 insertions(+), 172 deletions(-) diff --git a/ide_extension/vscode/devsecops/README.md b/ide_extension/vscode/devsecops/README.md index 35b179bc5..035e76d15 100644 --- a/ide_extension/vscode/devsecops/README.md +++ b/ide_extension/vscode/devsecops/README.md @@ -76,20 +76,11 @@ Take advantage of the integration with **GitHub Copilot** for an intelligent, as - **Icon:** 📦 - **Function:** - Specific for dependency vulnerabilities - - Generates CLI commands to update affected libraries - - Shows which direct dependencies need upgrading + - Targets **all** dependency vulnerabilities found in that scan, not just the one you clicked + - Includes a compact CVE + fixed version list per finding (to keep the prompt short) instead of full details for each one + - Generates a single consolidated set of CLI commands to update all affected libraries - Suggests `npm`, `yarn`, or `maven` commands based on project type -### 4. 🤖 Auto-Fix with Agent *(Dependencies Only)* -- **Color:** Green -- **Icon:** 🤖 -- **Function:** - - Requires GitHub Copilot Agent mode - - Tries to apply fixes semi-automatically - - Scans files like `package.json`, `pom.xml`, etc. - - Generates a complete prompt with contextual details - - Suggests direct file changes - --- ## 📥 Installation diff --git a/ide_extension/vscode/devsecops/package.json b/ide_extension/vscode/devsecops/package.json index 6a95aa8a7..13f08d8ee 100755 --- a/ide_extension/vscode/devsecops/package.json +++ b/ide_extension/vscode/devsecops/package.json @@ -237,10 +237,6 @@ "command": "devsecops.generateDependencyUpdate", "title": "Generate Dependency Update Solution" }, - { - "command": "devsecops.autoFixDependenciesWithAgent", - "title": "Auto-Fix Dependencies with Copilot Agent" - }, { "command": "devsecops.deleteScanResult", "title": "Delete Scan Result" @@ -320,11 +316,6 @@ "when": "view == devsecops-results && viewItem == findingItem-dependencies", "group": "copilot@3" }, - { - "command": "devsecops.autoFixDependenciesWithAgent", - "when": "view == devsecops-results && viewItem == findingItem-dependencies", - "group": "copilot@4" - }, { "command": "devsecops.showGeneralFindingWebview", "when": "view == devsecops-results && viewItem == engine_secrets", diff --git a/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts b/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts index 7c38d07c2..dcee9f708 100644 --- a/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts +++ b/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts @@ -48,40 +48,17 @@ export function registerVulnerabilityCopilotCommands(context: vscode.ExtensionCo } const finding: Finding = findingItem.finding; - - AiMetricsService.track('generate_dependency_update', finding, 'tree_context_menu'); - await generateDependencyUpdateSolution(finding); - } - ); - - // Command to auto-fix dependencies with Copilot Agent - const autoFixDependenciesCommand = vscode.commands.registerCommand( - 'devsecops.autoFixDependenciesWithAgent', - async (findingItem: any) => { - if (!findingItem || !findingItem.finding) { - void vscode.window.showErrorMessage('No vulnerability selected'); - return; - } - - const finding: Finding = findingItem.finding; - - AiMetricsService.track('auto_fix_with_agent', finding, 'tree_context_menu'); + const allFindings: Finding[] = findingItem.allFindings || []; - // Show info message about Agent mode requirement - void vscode.window.showInformationMessage( - '🤖 This feature requires Copilot Chat in AGENT MODE to automatically edit files. Please switch to Agent mode when the chat opens.', - { modal: false } - ); - - await autoFixDependenciesWithAgent(finding); + AiMetricsService.track('generate_dependency_update', finding, 'tree_context_menu'); + await generateDependencyUpdateSolution(finding, allFindings); } ); context.subscriptions.push( fixVulnerabilityCommand, explainVulnerabilityCommand, - generateDependencyUpdateCommand, - autoFixDependenciesCommand + generateDependencyUpdateCommand ); } @@ -199,13 +176,8 @@ async function executeVulnerabilityExplanationWithCopilot(finding: Finding, sour await openCopilotChatWithPrompt(prompt); } -async function generateDependencyUpdateSolution(finding: Finding): Promise { - const prompt = generateDependencyUpdatePrompt(finding); - await openCopilotChatWithPrompt(prompt); -} - -async function autoFixDependenciesWithAgent(finding: Finding): Promise { - const prompt = generateAutoFixAgentPrompt(finding); +async function generateDependencyUpdateSolution(finding: Finding, allFindings: Finding[] = []): Promise { + const prompt = generateDependencyUpdatePrompt(finding, allFindings); await openCopilotChatWithPrompt(prompt); } @@ -324,94 +296,47 @@ Provide a clear, educational explanation suitable for developers and security te return basePrompt; } -function generateDependencyUpdatePrompt(finding: Finding): string { - const vulnerabilityId = finding.getId() || 'Unknown'; - const description = finding.getDescription() || 'No description available'; - const severity = finding.getSeverity() || 'Unknown'; +function generateDependencyUpdatePrompt(finding: Finding, allFindings: Finding[] = []): string { + // Scope to dependency findings from this same scan; fall back to the single selected finding + const dependencyFindings = allFindings.filter((f) => f.getModule() === 'engine_dependencies'); + const findingsToFix = dependencyFindings.length > 0 ? dependencyFindings : [finding]; + const count = findingsToFix.length; - let basePrompt = `Dependency Update Strategy + // Keep each entry to the essentials (CVE + fixed version) so the prompt stays short even with many findings + const vulnerabilityList = findingsToFix + .map((f) => { + const cve = f.getId() || 'Unknown'; + const packageName = f.getAdditionalField('package_name') || 'Unknown package'; + const fixedVersion = f.getAdditionalField('fixed_version') || 'No fix available'; + return `- ${cve} | ${packageName} → fix: ${fixedVersion}`; + }) + .join('\n'); -**Vulnerability Details:** -- ID: ${vulnerabilityId} -- Severity: ${severity} -- Description: ${description}`; + return `Dependency Update Strategy - Fix All Vulnerabilities in This Scan - // Add impact paths with formatting - const impactPathsPrompt = finding.getAdditionalField('impact_paths_prompt'); - if (impactPathsPrompt) { - basePrompt += `\n\n**Dependency Chain Analysis:**\n${impactPathsPrompt}`; - } +**Scan Summary:** ${count} dependency vulnerabilit${count === 1 ? 'y was' : 'ies were'} found in this scan and must ALL be remediated together. - basePrompt += ` +**Vulnerabilities (CVE | Package → Fixed Version):** +${vulnerabilityList} **Request:** -Based on the dependency vulnerability and impact paths above, please provide: - -1. **Immediate Actions:** - - Which exact dependencies need to be updated - - Specific version ranges to target - - Command-line instructions for updates - -2. **Update Strategy:** - - Step-by-step update process - - Compatibility considerations - - Testing recommendations - -3. **Package Manager Commands:** - - npm/yarn/pip/maven/gradle specific commands - - Lock file considerations - - Version pinning strategies - -4. **Risk Assessment:** - - Breaking change potential - - Alternative solutions if direct update isn't possible - - Temporary mitigation strategies - -5. **Prevention:** - - Dependency scanning tools - - Update policies - - Security monitoring setup - -Please provide specific, actionable commands and configurations.`; - - return basePrompt; -} - -function generateAutoFixAgentPrompt(finding: Finding): string { - const vulnerabilityId = finding.getId() || 'Unknown'; - const description = finding.getDescription() || 'No description available'; - const severity = finding.getSeverity() || 'Unknown'; - - let prompt = `I need you to automatically fix a security vulnerability in the project dependencies. - -**Vulnerability Details:** -- ID: ${vulnerabilityId} -- Severity: ${severity} -- Description: ${description}`; - - // Add impact paths if available (using clean text format for prompts) - const impactPathsPrompt = finding.getAdditionalField('impact_paths_prompt'); - if (impactPathsPrompt && impactPathsPrompt.trim() !== '') { - prompt += `\n\n**Impact Paths:**\n${impactPathsPrompt}`; - } +Your goal is to resolve **all ${count} vulnerabilit${count === 1 ? 'y' : 'ies'} listed above** with a single, consolidated update plan for this project (not just the vulnerability that was clicked). Please provide: - prompt += ` +1. **Consolidated Update Plan:** + - The exact target version to use for each package (if a package appears more than once, use the highest fixed version required) + - Group the updates by package manager/manifest file (package.json, requirements.txt, pom.xml, go.mod, etc.) -**Instructions:** -1. **Analyze** the vulnerability and identify which exact dependencies need to be updated -2. **Locate** the dependency files in the workspace (package.json, pom.xml, build.gradle, requirements.txt, etc.) -3. **Update** the dependency files automatically to fix the security issue -4. **Ensure** compatibility by checking version constraints -5. **Update** lock files if they exist -6. **Provide** a summary of changes made +2. **Commands:** + - Ready-to-run package manager commands (npm/yarn/pnpm/pip/maven/gradle/go) that apply all the updates at once + - Lock file regeneration steps -**Requirements:** -- Update to the minimum safe version that fixes the vulnerability -- Maintain backward compatibility where possible -- Provide clear explanations of changes -- Test the changes to ensure nothing breaks +3. **Compatibility & Risk:** + - Potential breaking changes introduced by these specific updates + - Any conflicts between the required fixed versions of the listed packages + - Testing steps to validate the project after updating everything together -Please proceed to fix the vulnerability by updating the appropriate dependency files.`; +4. **If a direct update isn't possible for any package:** + - Suggest safe alternatives or temporary mitigations for that specific CVE - return prompt; +Provide one unified, actionable solution that addresses every vulnerability in the list above.`; } diff --git a/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts b/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts index 72cf04cd0..631bc8af0 100644 --- a/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts +++ b/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts @@ -1,8 +1,7 @@ export type AiAction = | 'fix_with_copilot' | 'explain_with_copilot' - | 'generate_dependency_update' - | 'auto_fix_with_agent'; + | 'generate_dependency_update'; export type AiTriggerOrigin = 'webview' | 'code_action' | 'tree_context_menu'; diff --git a/ide_extension/vscode/devsecops/src/extension.ts b/ide_extension/vscode/devsecops/src/extension.ts index e6b031593..d2a723bad 100644 --- a/ide_extension/vscode/devsecops/src/extension.ts +++ b/ide_extension/vscode/devsecops/src/extension.ts @@ -58,8 +58,8 @@ export function activate(context: vscode.ExtensionContext): void { const showVulnContextDisposable = vscode.commands.registerCommand( "devsecops.showVulnContext", - (finding: Finding) => { - showVulnContextWebview(finding); + (finding: Finding, sourceType?: string, allFindings?: Finding[]) => { + showVulnContextWebview(finding, sourceType, allFindings); } ); diff --git a/ide_extension/vscode/devsecops/src/tree/ResultsTreeDataProvider.ts b/ide_extension/vscode/devsecops/src/tree/ResultsTreeDataProvider.ts index bb50a177a..77f2e8025 100644 --- a/ide_extension/vscode/devsecops/src/tree/ResultsTreeDataProvider.ts +++ b/ide_extension/vscode/devsecops/src/tree/ResultsTreeDataProvider.ts @@ -69,7 +69,7 @@ export class ResultsTreeDataProvider implements vscode.TreeDataProvider new FindingItem(f, scanPath, sourceType)); + findingItems = findings.map((f) => new FindingItem(f, scanPath, sourceType, findings)); } const scanItem = new ScanResultItem(label, sourceType, timestamp, findingItems, false, undefined, outputChannel); @@ -140,7 +140,7 @@ export class ResultsTreeDataProvider implements vscode.TreeDataProvider new FindingItem(f, scanPath, sourceType)); + findingItems = findings.map((f) => new FindingItem(f, scanPath, sourceType, findings)); } // Update the scan item with the findings @@ -268,7 +268,7 @@ export class ResultsTreeDataProvider implements vscode.TreeDataProvider new FindingItem(f, scanPath, sourceType)); + const findingItems = newFindings.map((f) => new FindingItem(f, scanPath, sourceType, newFindings)); return { items: findingItems, diff --git a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts index f0a657b88..e67729cec 100644 --- a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts +++ b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts @@ -1,6 +1,6 @@ import { Finding } from "../../../domain/model/Finding"; -export function findingDetailWebview(finding: Finding, sourceType?: string): string { +export function findingDetailWebview(finding: Finding, sourceType?: string, allFindings: Finding[] = []): string { const severity = (finding.getSeverity() || "unknown").toLowerCase(); let codicon = "codicon-warning"; let color = "#cca700"; @@ -47,14 +47,14 @@ export function findingDetailWebview(finding: Finding, sourceType?: string): str `; if (sourceType === 'dependencies') { + const dependencyFindingsCount = Math.max( + allFindings.filter(f => f.getModule() === "engine_dependencies").length, + 1 + ); buttons += ` - - `; } @@ -244,11 +244,6 @@ export function findingDetailWebview(finding: Finding, sourceType?: string): str color: #ffffff; border-color: #e67300; } - .agent-button { - background: linear-gradient(135deg, #228b22, #1e7e1e); - color: #ffffff; - border-color: #1e7e1e; - } /* Links - Unified Style */ a { @@ -353,12 +348,6 @@ export function findingDetailWebview(finding: Finding, sourceType?: string): str command: 'generateDependencyUpdate' }); } - - window.autoFixWithAgent = function() { - vscode.postMessage({ - command: 'autoFixDependenciesWithAgent' - }); - } diff --git a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts index c8c82361a..63da623bc 100644 --- a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts +++ b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts @@ -7,7 +7,8 @@ export class FindingItem extends vscode.TreeItem { constructor( public readonly finding: Finding, private readonly scanPath?: string, - private readonly sourceType?: "iac" | "image" | "dependencies" + private readonly sourceType?: "iac" | "image" | "dependencies", + public readonly allFindings: Finding[] = [] ) { super(finding.getDescription() || "Unknown Issue", vscode.TreeItemCollapsibleState.None); @@ -51,7 +52,7 @@ export class FindingItem extends vscode.TreeItem { this.command = { command: "devsecops.showVulnContext", title: "Show Vulnerability Context", - arguments: [finding, sourceType], // Pass the full finding/context object and sourceType + arguments: [finding, sourceType, allFindings], // Pass the full finding/context object, sourceType and all findings of this scan }; const fileInfo = this.extractFileInfo(finding.getWhere()); diff --git a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts index 43db5a5e8..bdcb87a09 100644 --- a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts +++ b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts @@ -5,7 +5,7 @@ import { AiMetricsService } from '../../../infrastructure/services/AiMetricsServ let vulnPanels: Map = new Map(); -export function showVulnContextWebview(finding: Finding, sourceType?: string): void { +export function showVulnContextWebview(finding: Finding, sourceType?: string, allFindings: Finding[] = []): void { const panelId = `vulnContext-${finding.getId()}-${new Date().getTime()}`; if (vulnPanels.has(panelId)) { @@ -45,10 +45,10 @@ export function showVulnContextWebview(finding: Finding, sourceType?: string): v // Determine sourceType from finding module if not provided const actualSourceType = sourceType || getSourceTypeFromModule(finding.getModule()); - vulnPanel.webview.html = findingDetailWebview(finding, actualSourceType); + vulnPanel.webview.html = findingDetailWebview(finding, actualSourceType, allFindings); // Setup message handler for Copilot buttons - setupMessageHandler(vulnPanel, finding); + setupMessageHandler(vulnPanel, finding, allFindings); vulnPanel.onDidDispose(() => { vulnPanels.delete(panelId); @@ -74,7 +74,7 @@ function getSourceTypeFromModule(module: string): string { } } -function setupMessageHandler(panel: vscode.WebviewPanel, finding: Finding): void { +function setupMessageHandler(panel: vscode.WebviewPanel, finding: Finding, allFindings: Finding[] = []): void { // Handle messages from the webview panel.webview.onDidReceiveMessage( async (message) => { @@ -96,14 +96,8 @@ function setupMessageHandler(panel: vscode.WebviewPanel, finding: Finding): void case 'generateDependencyUpdate': AiMetricsService.track('generate_dependency_update', finding, 'webview'); await vscode.commands.executeCommand('devsecops.generateDependencyUpdate', { - finding: finding - }); - break; - case 'autoFixWithAgent': - case 'autoFixDependenciesWithAgent': - AiMetricsService.track('auto_fix_with_agent', finding, 'webview'); - await vscode.commands.executeCommand('devsecops.autoFixDependenciesWithAgent', { - finding: finding + finding: finding, + allFindings: allFindings }); break; } From d4dda490e6f8fb8f12ed02e8d8c9a4817eea59ed Mon Sep 17 00:00:00 2001 From: jcamilomolinar Date: Wed, 8 Jul 2026 11:27:58 -0500 Subject: [PATCH 2/3] feat: update button with all vulns --- ide_extension/vscode/devsecops/README.md | 10 +- ide_extension/vscode/devsecops/package.json | 19 +++ .../src/commands/DependenciesScanCommand.ts | 2 +- .../devsecops/src/commands/IacScanCommand.ts | 2 +- .../src/commands/ImageScanCommand.ts | 2 +- .../commands/vulnerabilityCopilotCommands.ts | 113 +++++++++++++++++- .../src/domain/model/mappers/Mappers.ts | 1 + .../domain/model/metrics/IAiMetricsData.ts | 3 +- .../executors/RemoteMicroserviceExecutor.ts | 15 ++- .../services/ErrorHandlingService.ts | 4 +- .../src/tree/results/finding/FindingDetail.ts | 18 +++ .../src/tree/results/finding/FindingItem.ts | 2 + .../tree/results/finding/FindingWebview.ts | 7 ++ 13 files changed, 183 insertions(+), 15 deletions(-) diff --git a/ide_extension/vscode/devsecops/README.md b/ide_extension/vscode/devsecops/README.md index 035e76d15..42194762b 100644 --- a/ide_extension/vscode/devsecops/README.md +++ b/ide_extension/vscode/devsecops/README.md @@ -71,15 +71,15 @@ Take advantage of the integration with **GitHub Copilot** for an intelligent, as - Details potential impact and security risks - Helps you understand the issue before taking action -### 3. 📦 Generate Update Solution *(Dependencies Only)* +### 3. 📦 Generate Update Solution *(Dependencies & Images)* - **Color:** Orange - **Icon:** 📦 - **Function:** - - Specific for dependency vulnerabilities - - Targets **all** dependency vulnerabilities found in that scan, not just the one you clicked + - Available for dependency vulnerabilities and container image vulnerabilities + - Targets **all** vulnerabilities found in that scan, not just the one you clicked - Includes a compact CVE + fixed version list per finding (to keep the prompt short) instead of full details for each one - - Generates a single consolidated set of CLI commands to update all affected libraries - - Suggests `npm`, `yarn`, or `maven` commands based on project type + - For dependencies: generates a single consolidated set of CLI commands (`npm`, `yarn`, `maven`, etc.) to update all affected libraries + - For images: generates a single consolidated update plan (base image/package updates, Dockerfile changes) to fix all affected packages --- diff --git a/ide_extension/vscode/devsecops/package.json b/ide_extension/vscode/devsecops/package.json index 13f08d8ee..a1cbb3609 100755 --- a/ide_extension/vscode/devsecops/package.json +++ b/ide_extension/vscode/devsecops/package.json @@ -237,6 +237,10 @@ "command": "devsecops.generateDependencyUpdate", "title": "Generate Dependency Update Solution" }, + { + "command": "devsecops.generateImageUpdate", + "title": "Generate Image Update Solution" + }, { "command": "devsecops.deleteScanResult", "title": "Delete Scan Result" @@ -316,6 +320,21 @@ "when": "view == devsecops-results && viewItem == findingItem-dependencies", "group": "copilot@3" }, + { + "command": "devsecops.fixVulnerabilityWithCopilot", + "when": "view == devsecops-results && viewItem == findingItem-image", + "group": "copilot@1" + }, + { + "command": "devsecops.explainVulnerabilityWithCopilot", + "when": "view == devsecops-results && viewItem == findingItem-image", + "group": "copilot@2" + }, + { + "command": "devsecops.generateImageUpdate", + "when": "view == devsecops-results && viewItem == findingItem-image", + "group": "copilot@3" + }, { "command": "devsecops.showGeneralFindingWebview", "when": "view == devsecops-results && viewItem == engine_secrets", diff --git a/ide_extension/vscode/devsecops/src/commands/DependenciesScanCommand.ts b/ide_extension/vscode/devsecops/src/commands/DependenciesScanCommand.ts index 01398dc45..9f6cf1e1d 100644 --- a/ide_extension/vscode/devsecops/src/commands/DependenciesScanCommand.ts +++ b/ide_extension/vscode/devsecops/src/commands/DependenciesScanCommand.ts @@ -82,7 +82,7 @@ export function registerDependenciesScanCommand( : ErrorHandlingService.isSelfSignedCertificateError(errorMessage) ? "SSL certificate error: self-signed certificate detected. Please update your certificates and try again." : ErrorHandlingService.isMicroserviceError(errorMessage) - ? "The microservice is unavailable or the connection was interrupted. Please try again." + ? "The microservice is experiencing high transaction load and the scan was retried automatically, but it still could not complete. Please try again later." : "Dependencies Scan failed - Check Output for details"; void vscode.window.showErrorMessage(userMessage); // Mark the scan as failed but keep it visible diff --git a/ide_extension/vscode/devsecops/src/commands/IacScanCommand.ts b/ide_extension/vscode/devsecops/src/commands/IacScanCommand.ts index 08c1b9f0c..bbd9cba1b 100644 --- a/ide_extension/vscode/devsecops/src/commands/IacScanCommand.ts +++ b/ide_extension/vscode/devsecops/src/commands/IacScanCommand.ts @@ -79,7 +79,7 @@ export function registerIacScanCommand( : ErrorHandlingService.isSelfSignedCertificateError(errorMessage) ? "SSL certificate error: self-signed certificate detected. Please update your certificates and try again." : ErrorHandlingService.isMicroserviceError(errorMessage) - ? "The microservice is unavailable or the connection was interrupted. Please try again." + ? "The microservice is experiencing high transaction load and the scan was retried automatically, but it still could not complete. Please try again later." : "Iac Scan failed - Check Output for details"; void vscode.window.showErrorMessage(userMessage); // Mark the scan as failed but keep it visible diff --git a/ide_extension/vscode/devsecops/src/commands/ImageScanCommand.ts b/ide_extension/vscode/devsecops/src/commands/ImageScanCommand.ts index 801b6fa46..fba94a925 100644 --- a/ide_extension/vscode/devsecops/src/commands/ImageScanCommand.ts +++ b/ide_extension/vscode/devsecops/src/commands/ImageScanCommand.ts @@ -97,7 +97,7 @@ export function registerImageScanCommand( : ErrorHandlingService.isSelfSignedCertificateError(errorMessage) ? "SSL certificate error: self-signed certificate detected. Please update your certificates and try again." : ErrorHandlingService.isMicroserviceError(errorMessage) - ? "The microservice is unavailable or the connection was interrupted. Please try again." + ? "The microservice is experiencing high transaction load and the scan was retried automatically, but it still could not complete. Please try again later." : "Image Scan failed - Check Output for details"; void vscode.window.showErrorMessage(userMessage); // Mark the scan as failed but keep it visible diff --git a/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts b/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts index dcee9f708..fee116835 100644 --- a/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts +++ b/ide_extension/vscode/devsecops/src/commands/vulnerabilityCopilotCommands.ts @@ -55,10 +55,28 @@ export function registerVulnerabilityCopilotCommands(context: vscode.ExtensionCo } ); + // Command to generate image update solutions + const generateImageUpdateCommand = vscode.commands.registerCommand( + 'devsecops.generateImageUpdate', + async (findingItem: any) => { + if (!findingItem || !findingItem.finding) { + void vscode.window.showErrorMessage('No vulnerability selected'); + return; + } + + const finding: Finding = findingItem.finding; + const allFindings: Finding[] = findingItem.allFindings || []; + + AiMetricsService.track('generate_image_update', finding, 'tree_context_menu'); + await generateImageUpdateSolution(finding, allFindings); + } + ); + context.subscriptions.push( fixVulnerabilityCommand, explainVulnerabilityCommand, - generateDependencyUpdateCommand + generateDependencyUpdateCommand, + generateImageUpdateCommand ); } @@ -181,6 +199,11 @@ async function generateDependencyUpdateSolution(finding: Finding, allFindings: F await openCopilotChatWithPrompt(prompt); } +async function generateImageUpdateSolution(finding: Finding, allFindings: Finding[] = []): Promise { + const prompt = generateImageUpdatePrompt(finding, allFindings); + await openCopilotChatWithPrompt(prompt); +} + function generateFixPrompt(finding: Finding, sourceType: string): string { const vulnerabilityId = finding.getId() || 'Unknown'; const description = finding.getDescription() || 'No description available'; @@ -207,7 +230,11 @@ function generateFixPrompt(finding: Finding, sourceType: string): string { basePrompt += ` **Dependency Vulnerability Context:** -This is a dependency vulnerability that may require updating package versions or changing dependency configurations.`; +This is a dependency vulnerability that may require updating package versions or changing dependency configurations. + +If this is a Gradle project and a direct version bump in build.gradle doesn't resolve the vulnerability (common with transitive dependencies), use this alternative mitigation method: + +${GRADLE_FORCED_VERSION_GUIDANCE}`; // Add impact paths if available if (finding.getAdditionalField('impact_paths_prompt')) { @@ -296,6 +323,39 @@ Provide a clear, educational explanation suitable for developers and security te return basePrompt; } +const GRADLE_FORCED_VERSION_GUIDANCE = `**Gradle Alternative Mitigation (use only if this is a Gradle project):** + +1. Check whether a \`dependencyMgmt.gradle\` file already exists in the project root. If it does NOT exist yet, create it there. This script forces specific versions for transitive dependencies that carry vulnerabilities/conflicts and/or excludes transitive dependencies that are unnecessary or problematic. If the file already exists, reuse it and just add/update the required rules instead of overwriting the rest of its content. + +2. Its content should look like this: +\`\`\`gradle +// Applied lazily to every configuration of the project +configurations.configureEach { + + /** + * ------------------------------- + * Force dependency versions + * ------------------------------- + */ + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + + // Example 1: force by group (applies to every dependency of that group) + if (details.requested.group == 'io.example') { + details.useVersion("\${exampleVersion}") + } + + // Example 2: force by group and name (applies only to a specific dependency) + if (details.requested.group == 'com.example' && details.requested.name == 'example-lib') { + details.useVersion("\${exampleVersion}") + } + } +} +\`\`\` + +3. Apply the script in both the root project and the specific module (check first that the line isn't already present to avoid duplicates): + - In the root \`build.gradle\`, inside the \`allprojects\` block, add: \`apply from: "\${rootDir}/dependencyMgmt.gradle"\` + - Additionally (recommended in practice, even though \`allprojects\` should be enough), add the same line as the **first line** of \`/applications/app-service/build.gradle\` (or the equivalent app module) so the configuration is reliably applied at every level.`; + function generateDependencyUpdatePrompt(finding: Finding, allFindings: Finding[] = []): string { // Scope to dependency findings from this same scan; fall back to the single selected finding const dependencyFindings = allFindings.filter((f) => f.getModule() === 'engine_dependencies'); @@ -338,5 +398,54 @@ Your goal is to resolve **all ${count} vulnerabilit${count === 1 ? 'y' : 'ies'} 4. **If a direct update isn't possible for any package:** - Suggest safe alternatives or temporary mitigations for that specific CVE +5. **If this is a Gradle project:** + - Apply the Gradle Alternative Mitigation described below instead + +${GRADLE_FORCED_VERSION_GUIDANCE} + +Provide one unified, actionable solution that addresses every vulnerability in the list above.`; +} + +function generateImageUpdatePrompt(finding: Finding, allFindings: Finding[] = []): string { + // Scope to image findings from this same scan; fall back to the single selected finding + const imageFindings = allFindings.filter((f) => f.getModule() === 'engine_container' || f.getModule() === 'engine_image'); + const findingsToFix = imageFindings.length > 0 ? imageFindings : [finding]; + const count = findingsToFix.length; + + // Keep each entry to the essentials (CVE + fixed version) so the prompt stays short even with many findings + const vulnerabilityList = findingsToFix + .map((f) => { + const cve = f.getId() || 'Unknown'; + const packageName = f.getAdditionalField('package_name') || 'Unknown package'; + const fixedVersion = f.getAdditionalField('fixed_version') || 'No fix available'; + return `- ${cve} | ${packageName} → fix: ${fixedVersion}`; + }) + .join('\n'); + + return `Container Image Update Strategy - Fix All Vulnerabilities in This Scan + +**Scan Summary:** ${count} image vulnerabilit${count === 1 ? 'y was' : 'ies were'} found in this scan and must ALL be remediated together. + +**Vulnerabilities (CVE | Package → Fixed Version):** +${vulnerabilityList} + +**Request:** +Your goal is to resolve **all ${count} vulnerabilit${count === 1 ? 'y' : 'ies'} listed above** with a single, consolidated update plan for this container image (not just the vulnerability that was clicked). Please provide: + +1. **Consolidated Update Plan:** + - The exact target version to use for each package (if a package appears more than once, use the highest fixed version required) + - Whether updating the base image (e.g. a newer tag/digest) resolves multiple vulnerabilities at once, versus patching individual OS packages + +2. **Commands/Changes:** + - Ready-to-apply Dockerfile changes (base image tag, RUN commands with package manager updates: apt/yum/apk/etc.) + +3. **Compatibility & Risk:** + - Potential breaking changes introduced by these specific updates + - Any conflicts between the required fixed versions of the listed packages + - Testing steps to validate the image still works after updating everything together + +4. **If a direct update isn't possible for any package:** + - Suggest safe alternatives or temporary mitigations for that specific CVE + Provide one unified, actionable solution that addresses every vulnerability in the list above.`; } diff --git a/ide_extension/vscode/devsecops/src/domain/model/mappers/Mappers.ts b/ide_extension/vscode/devsecops/src/domain/model/mappers/Mappers.ts index 70d5060cb..8e174bda0 100644 --- a/ide_extension/vscode/devsecops/src/domain/model/mappers/Mappers.ts +++ b/ide_extension/vscode/devsecops/src/domain/model/mappers/Mappers.ts @@ -107,6 +107,7 @@ export class Mappers { vendor_id: imageScanContext.vendor_id || "", vulnerability_status: imageScanContext.vulnerability_status || "unknown", target_image: imageScanContext.target_image || "", + package_name: imageScanContext.package_name || "", installed_version: imageScanContext.installed_version || "", fixed_version: imageScanContext.fixed_version || "", cvss_score: imageScanContext.cvss_score || "", diff --git a/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts b/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts index 631bc8af0..647efefbd 100644 --- a/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts +++ b/ide_extension/vscode/devsecops/src/domain/model/metrics/IAiMetricsData.ts @@ -1,7 +1,8 @@ export type AiAction = | 'fix_with_copilot' | 'explain_with_copilot' - | 'generate_dependency_update'; + | 'generate_dependency_update' + | 'generate_image_update'; export type AiTriggerOrigin = 'webview' | 'code_action' | 'tree_context_menu'; diff --git a/ide_extension/vscode/devsecops/src/infrastructure/executors/RemoteMicroserviceExecutor.ts b/ide_extension/vscode/devsecops/src/infrastructure/executors/RemoteMicroserviceExecutor.ts index 69e789aa5..c349db01b 100644 --- a/ide_extension/vscode/devsecops/src/infrastructure/executors/RemoteMicroserviceExecutor.ts +++ b/ide_extension/vscode/devsecops/src/infrastructure/executors/RemoteMicroserviceExecutor.ts @@ -351,10 +351,23 @@ export class RemoteMicroserviceExecutor implements IScanExecutor { lastError = error instanceof Error ? error : new Error(errorMessage); const delay = this.INITIAL_DELAY_MS * Math.pow(2, attempt - 1) + Math.random() * 500; + const retryReason = '⚠️ Microservice is experiencing high transaction load. Retrying the scan automatically'; outputChannel.appendLine( - `⚠️ Microservice unavailable (attempt ${attempt}/${this.MAX_RETRIES}). Retrying in ${(delay / 1000).toFixed(1)}s...` + `${retryReason} (attempt ${attempt}/${this.MAX_RETRIES}) in ${(delay / 1000).toFixed(1)}s...` ); outputChannel.appendLine(''); + + if (progressReporter) { + progressReporter.report({ message: 'Microservice busy, retrying scan...' }); + } + + // Notify the user only once so we don't spam them on every retry attempt + if (attempt === 1) { + void vscode.window.showWarningMessage( + '⚠️ The microservice is experiencing high transaction load. The scan is being retried automatically.' + ); + } + await new Promise(res => setTimeout(res, delay)); } } diff --git a/ide_extension/vscode/devsecops/src/infrastructure/services/ErrorHandlingService.ts b/ide_extension/vscode/devsecops/src/infrastructure/services/ErrorHandlingService.ts index bf2cf6d4d..706498d8a 100644 --- a/ide_extension/vscode/devsecops/src/infrastructure/services/ErrorHandlingService.ts +++ b/ide_extension/vscode/devsecops/src/infrastructure/services/ErrorHandlingService.ts @@ -206,9 +206,7 @@ export class ErrorHandlingService { lower.includes('http 504') || lower.includes('service unavailable') || lower.includes('upstream connect error') || - lower.includes('connection termination') || - lower.includes('enotfound') || - lower.includes('getaddrinfo'); + lower.includes('connection termination'); } private static hasErrorCategory(logs: string[], category: keyof typeof ERROR_PATTERNS): boolean { diff --git a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts index e67729cec..72add5470 100644 --- a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts +++ b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingDetail.ts @@ -58,6 +58,18 @@ export function findingDetailWebview(finding: Finding, sourceType?: string, allF `; } + if (sourceType === 'image') { + const imageFindingsCount = Math.max( + allFindings.filter(f => f.getModule() === "engine_container" || f.getModule() === "engine_image").length, + 1 + ); + buttons += ` + `; + } + buttons += ` `; @@ -348,6 +360,12 @@ export function findingDetailWebview(finding: Finding, sourceType?: string, allF command: 'generateDependencyUpdate' }); } + + window.generateImageUpdate = function() { + vscode.postMessage({ + command: 'generateImageUpdate' + }); + } diff --git a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts index 63da623bc..4ae519d09 100644 --- a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts +++ b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingItem.ts @@ -28,6 +28,8 @@ export class FindingItem extends vscode.TreeItem { // Set context value based on module for right-click menu if (sourceType === "dependencies") { this.contextValue = "findingItem-dependencies"; + } else if (sourceType === "image") { + this.contextValue = "findingItem-image"; } else if (finding.getModule() === "engine_iac") { this.contextValue = "engine_iac"; } else if (finding.getModule() === "engine_secrets") { diff --git a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts index bdcb87a09..f4822cfe1 100644 --- a/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts +++ b/ide_extension/vscode/devsecops/src/tree/results/finding/FindingWebview.ts @@ -100,6 +100,13 @@ function setupMessageHandler(panel: vscode.WebviewPanel, finding: Finding, allFi allFindings: allFindings }); break; + case 'generateImageUpdate': + AiMetricsService.track('generate_image_update', finding, 'webview'); + await vscode.commands.executeCommand('devsecops.generateImageUpdate', { + finding: finding, + allFindings: allFindings + }); + break; } } ); From e4b26dfac7f99f0b529881bbb11ad6ac9646cd9b Mon Sep 17 00:00:00 2001 From: jcamilomolinar Date: Wed, 8 Jul 2026 11:41:21 -0500 Subject: [PATCH 3/3] chore: update package version --- ide_extension/vscode/devsecops/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide_extension/vscode/devsecops/package.json b/ide_extension/vscode/devsecops/package.json index a1cbb3609..e43e42c01 100755 --- a/ide_extension/vscode/devsecops/package.json +++ b/ide_extension/vscode/devsecops/package.json @@ -4,7 +4,7 @@ "displayName": "DevSecOps Engine Tools", "publisher": "GrupoBancolombiaOSPO", "description": "", - "version": "1.27.0", + "version": "1.28.0", "engines": { "vscode": "^1.95.0" },