Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,576 changes: 2,708 additions & 5,868 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@
"title": "Scan All Files",
"category": "Corgea"
},
{
"command": "corgea.scan-semgrep",
"title": "Scan with Semgrep",
"category": "Corgea"
},
{
"command": "corgea.scan-snyk",
"title": "Scan with Snyk",
"category": "Corgea"
},
{
"command": "corgea.logout",
"title": "Logout",
Expand Down
9 changes: 9 additions & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,12 @@ export const SCAN_STAGES = {
COMPLETED: 'completed',
CANCELLED: 'cancelled'
} as const;

// Scanner types
export const SCANNER_TYPES = {
CORGEA: 'corgea',
SEMGREP: 'semgrep',
SNYK: 'snyk'
} as const;

export type ScannerType = typeof SCANNER_TYPES[keyof typeof SCANNER_TYPES];
8 changes: 8 additions & 0 deletions src/providers/vulnerabilitiesWebviewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ export default class VulnerabilitiesWebviewProvider implements vscode.WebviewVie
console.log('Scan project button clicked from webview');
vscode.commands.executeCommand("corgea.scan-full");
break;
case "scanWithSemgrep":
console.log('Scan with Semgrep button clicked from webview');
vscode.commands.executeCommand("corgea.scan-semgrep");
break;
case "scanWithSnyk":
console.log('Scan with Snyk button clicked from webview');
vscode.commands.executeCommand("corgea.scan-snyk");
break;
case "cancelScan":
console.log('Cancel scan button clicked from webview');
vscode.commands.executeCommand("corgea.cancelScan");
Expand Down
86 changes: 71 additions & 15 deletions src/services/scanningService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ErrorHandlingManager, { withErrorHandling } from "../utils/ErrorHandlingM
import EventsManager from "../utils/eventsManager";
import WorkspaceManager from "../utils/workspaceManager";
import ConfigService from "./configService";
import { SCANNER_TYPES, ScannerType } from "../config/constants";
const kill = require('tree-kill');

export interface ScanProgress {
Expand All @@ -37,6 +38,7 @@ export interface ScanState {
scanId?: string;
output: string[];
error?: string;
scannerType?: ScannerType;
}

export default class scanningService {
Expand Down Expand Up @@ -66,7 +68,37 @@ export default class scanningService {
return;
}

await scanningService.scanProject(false);
await scanningService.scanProject(false, SCANNER_TYPES.CORGEA);
}

@OnCommand("corgea.scan-semgrep")
@withErrorHandling()
public static async scanWithSemgrep() {
// Check if IDE scanning is enabled
const isIdeScanningEnabled = await ConfigService.isIdeScanningEnabled();
if (!isIdeScanningEnabled) {
vscode.window.showInformationMessage(
"IDE scanning is disabled for your organization. Please contact your administrator to enable this feature."
);
return;
}

await scanningService.scanProject(true, SCANNER_TYPES.SEMGREP);
}

@OnCommand("corgea.scan-snyk")
@withErrorHandling()
public static async scanWithSnyk() {
// Check if IDE scanning is enabled
const isIdeScanningEnabled = await ConfigService.isIdeScanningEnabled();
if (!isIdeScanningEnabled) {
vscode.window.showInformationMessage(
"IDE scanning is disabled for your organization. Please contact your administrator to enable this feature."
);
return;
}

await scanningService.scanProject(true, SCANNER_TYPES.SNYK);
}

public static async getUncommittedFiles(includeIgnored: boolean = false): Promise<any[]> {
Expand Down Expand Up @@ -95,7 +127,7 @@ export default class scanningService {
*/
@OnCommand("corgea.scan-full")
@withErrorHandling()
public static async scanProject(isFullScan: boolean = true) {
public static async scanProject(isFullScan: boolean = true, scannerType: ScannerType = SCANNER_TYPES.CORGEA) {
// Check if IDE scanning is enabled
const isIdeScanningEnabled = await ConfigService.isIdeScanningEnabled();
if (!isIdeScanningEnabled) {
Expand All @@ -111,7 +143,7 @@ export default class scanningService {
isScanning: true,
progress: [{
stage: "initializing",
message: "Initializing scan...",
message: `Initializing ${scannerType} scan...`,
timestamp: Date.now()
}],
stages: {
Expand All @@ -121,6 +153,7 @@ export default class scanningService {
scan: false
},
output: [],
scannerType: scannerType,
};

// Notify webview that scan started
Expand Down Expand Up @@ -167,11 +200,18 @@ export default class scanningService {
scanArgs.push("--only-uncommitted");
}

// Add scanner type parameter
if (scannerType === SCANNER_TYPES.SEMGREP) {
scanArgs.push("semgrep");
} else if (scannerType === SCANNER_TYPES.SNYK) {
scanArgs.push("snyk");
}

// Run login first
await scanningService.runCommand(command, loginArgs);

// Then run scan
scanningService.updateScanProgress("scanning", "Starting security scan...");
scanningService.updateScanProgress("scanning", `Starting ${scannerType} security scan...`);
await scanningService.runScanCommand(command, scanArgs);

} catch (error: any) {
Expand Down Expand Up @@ -351,19 +391,35 @@ export default class scanningService {

private static parseOutput(output: string) {
const lines = output.split('\n');
const scannerType = scanningService._scanState.scannerType;

for (const line of lines) {
// Update stages based on output messages
if (line.includes('Packaging your project')) {
scanningService._scanState.stages.init = true;
scanningService.updateScanProgress("packaging", "Packaging your project...");
} else if (line.includes('Project packaged successfully')) {
scanningService._scanState.stages.package = true;
scanningService.updateScanProgress("packaged", "Project packaged successfully");
} else if (line.includes('Submitting scan to Corgea')) {
scanningService.updateScanProgress("submitting", "Submitting scan to Corgea...");
} else if (line.includes('Scanning with')) {
scanningService.updateScanProgress("scanning", line.trim());
// Handle different stages based on scanner type
if (scannerType === SCANNER_TYPES.SEMGREP || scannerType === SCANNER_TYPES.SNYK) {
// Custom stages for Semgrep and Snyk
if (line.includes('Scanning with')) {
scanningService._scanState.stages.init = true;
scanningService.updateScanProgress("scanning", line.trim());
} else if (line.includes('Uploading required files for the scan')) {
scanningService._scanState.stages.package = true;
scanningService.updateScanProgress("uploading", "Uploading required files for the scan...");
} else if (line.includes('Successfully uploaded scan')) {
scanningService._scanState.stages.upload = true;
scanningService.updateScanProgress("uploaded", "Scan uploaded successfully");
}
} else {
// Default Corgea stages
if (line.includes('Packaging your project')) {
scanningService._scanState.stages.init = true;
scanningService.updateScanProgress("packaging", "Packaging your project...");
} else if (line.includes('Project packaged successfully')) {
scanningService._scanState.stages.package = true;
scanningService.updateScanProgress("packaged", "Project packaged successfully");
} else if (line.includes('Submitting scan to Corgea')) {
scanningService.updateScanProgress("submitting", "Submitting scan to Corgea...");
} else if (line.includes('Scanning with')) {
scanningService.updateScanProgress("scanning", line.trim());
}
}

// Extract progress percentage
Expand Down
19 changes: 19 additions & 0 deletions src/views/components/CurrentProgress/CurrentProgress.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
height: 100%;
background: var(--corgea-orange);
transition: width 0.3s ease;
width: var(--progress-width, 0%);
}

.progress-percentage {
Expand All @@ -36,3 +37,21 @@
color: var(--vscode-foreground);
min-width: 40px;
}

.scanner-type {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 12px;
color: var(--vscode-descriptionForeground);
margin-bottom: 8px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}

.scanner-type i {
font-size: 10px;
opacity: 0.8;
}
20 changes: 17 additions & 3 deletions src/views/components/CurrentProgress/CurrentProgress.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import { ScanState } from '../../context/VulnerabilitiesContext';
import './CurrentProgress.css';

Expand All @@ -7,6 +7,8 @@ interface CurrentProgressProps {
}

const CurrentProgress: React.FC<CurrentProgressProps> = ({ scanState }) => {
const progressFillRef = useRef<HTMLDivElement>(null);

const getLatestProgress = () => {
if (!scanState.progress || scanState.progress.length === 0) return null;
// Filter out "Scan url available" messages
Expand All @@ -16,19 +18,31 @@ const CurrentProgress: React.FC<CurrentProgressProps> = ({ scanState }) => {

const latestProgress = getLatestProgress();

useEffect(() => {
if (progressFillRef.current && latestProgress?.percentage !== undefined) {
progressFillRef.current.style.setProperty('--progress-width', `${latestProgress.percentage}%`);
}
}, [latestProgress?.percentage]);

if (!latestProgress) {
return null;
}

return (
<div className="current-progress">
{scanState.scannerType && (
<div className="scanner-type">
<i className="fas fa-shield-alt"></i>
Scanning with {scanState.scannerType.charAt(0).toUpperCase() + scanState.scannerType.slice(1)}
</div>
)}
<div className="progress-text">{latestProgress.message}</div>
{latestProgress.percentage !== undefined && (
<div className="progress-bar-container">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${latestProgress.percentage}%` }}
ref={progressFillRef}
className="progress-fill"
></div>
</div>
<div className="progress-percentage">
Expand Down
72 changes: 50 additions & 22 deletions src/views/components/ProgressSteps/ProgressSteps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,55 @@ const ProgressSteps: React.FC<ProgressStepsProps> = ({ scanState }) => {
'initializing': 'init',
'packaging': 'package',
'uploading': 'upload',
'scanning': 'scan'
'scanning': 'scan',
'done': 'scan' // Done stage maps to scan completion
};
return mapping[stage];
};

const getCurrentActiveStage = () => {
if (!scanState.stages) return 'initializing';

// Find the first false stage - that's the active one
if (!scanState.stages.init) return 'initializing';
if (!scanState.stages.package) return 'packaging';
if (!scanState.stages.upload) return 'uploading';
if (!scanState.stages.scan) return 'scanning';
const scannerType = scanState.scannerType;

if (scannerType === 'semgrep' || scannerType === 'snyk') {
// Semgrep/Snyk stages: initializing -> scanning -> uploading -> done
if (!scanState.stages.init) return 'initializing';
if (!scanState.stages.scan) return 'scanning';
if (!scanState.stages.upload) return 'uploading';
if (scanState.stages.scan) return 'done';
} else {
// Corgea stages: initializing -> packaging -> uploading -> scanning
if (!scanState.stages.init) return 'initializing';
if (!scanState.stages.package) return 'packaging';
if (!scanState.stages.upload) return 'uploading';
if (!scanState.stages.scan) return 'scanning';
}

// All stages complete
return null;
};

const getStepsForScanner = () => {
const scannerType = scanState.scannerType;

if (scannerType === 'semgrep' || scannerType === 'snyk') {
return [
{ key: 'initializing', label: 'Initializing', icon: 'fas fa-cog' },
{ key: 'scanning', label: 'Scanning', icon: 'fas fa-search' },
{ key: 'uploading', label: 'Uploading', icon: 'fas fa-upload' },
{ key: 'done', label: 'Done', icon: 'fas fa-check' }
];
} else {
return [
{ key: 'initializing', label: 'Initializing', icon: 'fas fa-cog' },
{ key: 'packaging', label: 'Packaging', icon: 'fas fa-box' },
{ key: 'uploading', label: 'Uploading', icon: 'fas fa-upload' },
{ key: 'scanning', label: 'Scanning', icon: 'fas fa-search' }
];
}
};

const getStepIcon = (stage: string) => {
if (!scanState.stages) {
return getDefaultIcon(stage);
Expand Down Expand Up @@ -73,24 +104,21 @@ const ProgressSteps: React.FC<ProgressStepsProps> = ({ scanState }) => {
}
};

const steps = getStepsForScanner();

return (
<div className="progress-steps">
<div className={`step ${getStepClass('initializing')}`}>
<div className="step-icon">{getStepIcon('initializing')}</div>
<div className="step-text">Initializing</div>
</div>
<div className={`step ${getStepClass('packaging')}`}>
<div className="step-icon">{getStepIcon('packaging')}</div>
<div className="step-text">Packaging</div>
</div>
<div className={`step ${getStepClass('uploading')}`}>
<div className="step-icon">{getStepIcon('uploading')}</div>
<div className="step-text">Uploading</div>
</div>
<div className={`step ${getStepClass('scanning')}`}>
<div className="step-icon">{getStepIcon('scanning')}</div>
<div className="step-text">Scanning</div>
</div>
{steps.map((step, index) => (
<div key={step.key} className={`step ${getStepClass(step.key)}`}>
<div className="step-icon">
{scanState.stages && getStageKey(step.key) && scanState.stages[getStageKey(step.key) as keyof typeof scanState.stages]
? <i className="fas fa-check"></i>
: <i className={step.icon}></i>
}
</div>
<div className="step-text">{step.label}</div>
</div>
))}
</div>
);
};
Expand Down
13 changes: 12 additions & 1 deletion src/views/components/ScanningCompleted/ScanningCompleted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import './ScanningCompleted.css';
const ScanningCompleted: React.FC = () => {
const { state, actions } = useVulnerabilities();

const handleScanAgain = () => {
const scannerType = state.scanState.scannerType;
if (scannerType === 'semgrep') {
actions.scanWithSemgrep();
} else if (scannerType === 'snyk') {
actions.scanWithSnyk();
} else {
actions.scanProject();
}
};

return (
<div className="scanning-completed">
<div className="completed-icon">
Expand All @@ -18,7 +29,7 @@ const ScanningCompleted: React.FC = () => {
&nbsp;View Results in Corgea
</button>
)}
<button className="btn btn-secondary me-2" onClick={actions.scanProject}>
<button className="btn btn-secondary me-2" onClick={handleScanAgain}>
<i className="fas fa-redo"></i>
&nbsp;Scan Again
</button>
Expand Down
Loading