export async function startSecurityScan()

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)
    }
}