in packages/core/src/codewhisperer/commands/startSecurityScan.ts [101:394]
export async function startSecurityScan(
securityPanelViewProvider: SecurityPanelViewProvider,
editor: vscode.TextEditor | undefined,
client: DefaultCodeWhispererClient,
context: vscode.ExtensionContext,
scope: CodeWhispererConstants.CodeAnalysisScope,
initiatedByChat: boolean,
zipUtil: ZipUtil = new ZipUtil(),
scanUuid?: string
) {
const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile
const logger = getLoggerForScope(scope)
/**
* Step 0: Initial Code Scan telemetry
*/
const codeScanStartTime = performance.now()
if (scope === CodeAnalysisScope.FILE_AUTO) {
CodeScansState.instance.setLatestScanTime(codeScanStartTime)
}
let serviceInvocationStartTime = 0
const codeScanTelemetryEntry: CodeScanTelemetryEntry = {
codewhispererLanguage: editor
? runtimeLanguageContext.getLanguageContext(
editor.document.languageId,
path.extname(editor.document.fileName)
).language
: 'plaintext',
codewhispererCodeScanSrcPayloadBytes: 0,
codewhispererCodeScanSrcZipFileBytes: 0,
codewhispererCodeScanLines: 0,
duration: 0,
contextTruncationDuration: 0,
artifactsUploadDuration: 0,
codeScanServiceInvocationsDuration: 0,
result: 'Succeeded',
codewhispererCodeScanTotalIssues: 0,
codewhispererCodeScanIssuesWithFixes: 0,
credentialStartUrl: AuthUtil.instance.startUrl,
codewhispererCodeScanScope: scope,
source: initiatedByChat ? 'chat' : 'menu',
}
const fileName = editor?.document.fileName
const scanState = scope === CodeAnalysisScope.PROJECT ? codeScanState : onDemandFileScanState
try {
logger.verbose(`Starting security scan `)
/**
* Step 1: Generate zip
*/
throwIfCancelled(scope, codeScanStartTime)
if (initiatedByChat) {
scanState.getChatControllers()?.scanProgress.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
step: SecurityScanStep.GENERATE_ZIP,
scope,
fileName,
scanUuid,
})
}
const zipMetadata = await zipUtil.generateZip(editor?.document.uri, scope)
const projectPaths = zipUtil.getProjectPaths()
const contextTruncationStartTime = performance.now()
codeScanTelemetryEntry.contextTruncationDuration = performance.now() - contextTruncationStartTime
logger.verbose(`Complete project context processing.`)
codeScanTelemetryEntry.codewhispererCodeScanSrcPayloadBytes = zipMetadata.srcPayloadSizeInBytes
codeScanTelemetryEntry.codewhispererCodeScanBuildPayloadBytes = zipMetadata.buildPayloadSizeInBytes
codeScanTelemetryEntry.codewhispererCodeScanSrcZipFileBytes = zipMetadata.zipFileSizeInBytes
codeScanTelemetryEntry.codewhispererCodeScanLines = zipMetadata.lines
if (zipMetadata.language) {
codeScanTelemetryEntry.codewhispererLanguage = zipMetadata.language
}
/**
* Step 2: Get presigned Url, upload and clean up
*/
throwIfCancelled(scope, codeScanStartTime)
if (initiatedByChat) {
scanState.getChatControllers()?.scanProgress.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
step: SecurityScanStep.UPLOAD_TO_S3,
scope,
fileName,
scanUuid,
})
}
let artifactMap: ArtifactMap = {}
const uploadStartTime = performance.now()
const scanName = randomUUID()
try {
artifactMap = await getPresignedUrlAndUpload(client, zipMetadata, scope, scanName, profile)
} finally {
await zipUtil.removeTmpFiles(zipMetadata, scope)
codeScanTelemetryEntry.artifactsUploadDuration = performance.now() - uploadStartTime
}
/**
* Step 3: Create scan job
*/
throwIfCancelled(scope, codeScanStartTime)
if (initiatedByChat) {
scanState.getChatControllers()?.scanProgress.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
step: SecurityScanStep.CREATE_SCAN_JOB,
scope,
fileName,
scanUuid,
})
}
serviceInvocationStartTime = performance.now()
const scanJob = await createScanJob(
client,
artifactMap,
codeScanTelemetryEntry.codewhispererLanguage,
scope,
scanName,
profile
)
if (scanJob.status === 'Failed') {
logger.verbose(`${scanJob.errorMessage}`)
const errorMessage = scanJob.errorMessage ?? 'CreateCodeScanFailed'
throw new CreateCodeScanFailedError(errorMessage)
}
logger.verbose(`Created security scan job.`)
codeScanTelemetryEntry.codewhispererCodeScanJobId = scanJob.jobId
/**
* Step 4: Polling mechanism on scan job status
*/
throwIfCancelled(scope, codeScanStartTime)
if (initiatedByChat) {
scanState.getChatControllers()?.scanProgress.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
step: SecurityScanStep.POLL_SCAN_STATUS,
scope,
fileName,
scanUuid,
})
}
// pass profile
const jobStatus = await pollScanJobStatus(client, scanJob.jobId, scope, codeScanStartTime, profile)
if (jobStatus === 'Failed') {
logger.verbose(`Security scan failed.`)
throw new CodeScanJobFailedError()
}
/**
* Step 5: Process and render scan results
*/
throwIfCancelled(scope, codeScanStartTime)
if (initiatedByChat) {
scanState.getChatControllers()?.scanProgress.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
step: SecurityScanStep.PROCESS_SCAN_RESULTS,
scope,
fileName,
scanUuid,
})
}
logger.verbose(`Security scan job succeeded and start processing result.`)
const securityRecommendationCollection = await listScanResults(
client,
scanJob.jobId,
CodeWhispererConstants.codeScanFindingsSchema,
projectPaths,
scope,
editor,
profile
)
for (const issue of securityRecommendationCollection
.flatMap(({ issues }) => issues)
.filter(({ visible, autoDetected }) => visible && !autoDetected)) {
telemetry.codewhisperer_codeScanIssueDetected.emit({
autoDetected: issue.autoDetected,
codewhispererCodeScanJobId: issue.scanJobId,
detectorId: issue.detectorId,
findingId: issue.findingId,
includesFix: issue.suggestedFixes.length > 0,
ruleId: issue.ruleId,
result: 'Succeeded',
})
}
const { total, withFixes } = securityRecommendationCollection.reduce(
(accumulator, current) => ({
total: accumulator.total + current.issues.length,
withFixes: accumulator.withFixes + current.issues.filter((i) => i.suggestedFixes.length > 0).length,
}),
{ total: 0, withFixes: 0 }
)
codeScanTelemetryEntry.codewhispererCodeScanTotalIssues = total
codeScanTelemetryEntry.codewhispererCodeScanIssuesWithFixes = withFixes
throwIfCancelled(scope, codeScanStartTime)
logger.verbose(`Security scan totally found ${total} issues. ${withFixes} of them have fixes.`)
/**
* initiatedByChat is true for PROJECT and FILE_ON_DEMAND scopes,
* initiatedByChat is false for PROJECT and FILE_AUTO scopes
*/
if (initiatedByChat) {
showScanResultsInChat(
securityPanelViewProvider,
securityRecommendationCollection,
editor,
context,
scope,
zipMetadata,
total,
scanUuid
)
} else {
showSecurityScanResults(securityRecommendationCollection, editor, context, scope, zipMetadata, total)
}
TelemetryHelper.instance.sendCodeScanSucceededEvent(
codeScanTelemetryEntry.codewhispererLanguage,
scanJob.jobId,
total,
scope
)
logger.verbose(`Security scan completed.`)
} catch (error) {
getLogger().error('Security scan failed. %O', error)
if (error instanceof CodeScanStoppedError) {
codeScanState.getChatControllers()?.scanCancelled.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
scanUuid,
})
codeScanTelemetryEntry.result = 'Cancelled'
} else if (isAwsError(error) && error.code === 'ThrottlingException') {
codeScanTelemetryEntry.result = 'Failed'
if (
scope === CodeAnalysisScope.PROJECT &&
error.message.includes(CodeWhispererConstants.scansLimitReachedErrorMessage)
) {
const maximumProjectScanReachedError = new MaximumProjectScanReachedError()
getLogger().error(maximumProjectScanReachedError.customerFacingMessage)
errorPromptHelper(maximumProjectScanReachedError, scope, initiatedByChat, fileName, scanUuid)
// TODO: Should we set a graphical state?
// We shouldn't set vsCodeState.isFreeTierLimitReached here because it will hide CW and Q chat options.
} else if (scope === CodeAnalysisScope.PROJECT) {
getLogger().error(error.message)
errorPromptHelper(
new SecurityScanError(
error.code,
(error as any).statusCode?.toString() ?? '',
'Too many requests, please wait before trying again.'
),
scope,
initiatedByChat,
fileName,
scanUuid
)
} else {
const maximumFileScanReachedError = new MaximumFileScanReachedError()
getLogger().error(maximumFileScanReachedError.customerFacingMessage)
errorPromptHelper(maximumFileScanReachedError, scope, initiatedByChat, fileName, scanUuid)
CodeScansState.instance.setMonthlyQuotaExceeded()
}
} else {
codeScanTelemetryEntry.result = 'Failed'
errorPromptHelper(
new SecurityScanError(
(error as any).code ?? 'unknown error',
(error as any).statusCode?.toString() ?? '',
'Encountered an unexpected error when processing the request, please try again'
),
scope,
initiatedByChat,
fileName
)
}
codeScanTelemetryEntry.reasonDesc =
(error as ToolkitError)?.code === 'ContentLengthError'
? 'Payload size limit reached'
: getTelemetryReasonDesc(error)
codeScanTelemetryEntry.reason = (error as ToolkitError)?.code ?? 'DefaultError'
if (codeScanTelemetryEntry.codewhispererCodeScanJobId) {
TelemetryHelper.instance.sendCodeScanFailedEvent(
codeScanTelemetryEntry.codewhispererLanguage,
codeScanTelemetryEntry.codewhispererCodeScanJobId,
scope
)
}
} finally {
const scanState = scope === CodeAnalysisScope.PROJECT ? codeScanState : onDemandFileScanState
scanState.setToNotStarted()
scanState.getChatControllers()?.scanStopped.fire({
tabID: ChatSessionManager.Instance.getSession().tabID,
scanUuid,
})
codeScanTelemetryEntry.duration = performance.now() - codeScanStartTime
codeScanTelemetryEntry.codeScanServiceInvocationsDuration = performance.now() - serviceInvocationStartTime
await emitCodeScanTelemetry(codeScanTelemetryEntry)
}
}