From 4431fe687d5a3cce7bb5f444dd57dc5e59a50239 Mon Sep 17 00:00:00 2001 From: x032205 Date: Tue, 16 Sep 2025 01:27:28 -0400 Subject: [PATCH] feat(notifications): secret scan alert notifications --- .../secret-scanning-v2-queue.ts | 36 ++++++++++++++++--- backend/src/server/routes/index.ts | 3 +- .../notification/notification-types.ts | 4 ++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts index 8621b039b..406c25e03 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-queue.ts @@ -21,6 +21,8 @@ import { decryptAppConnection } from "@app/services/app-connection/app-connectio import { TAppConnection } from "@app/services/app-connection/app-connection-types"; import { ActorType } from "@app/services/auth/auth-type"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TNotificationServiceFactory } from "@app/services/notification/notification-service"; +import { NotificationType } from "@app/services/notification/notification-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; @@ -52,6 +54,7 @@ type TSecretRotationV2QueueServiceFactoryDep = { appConnectionDAL: Pick; auditLogService: Pick; keyStore: Pick; + notificationService: Pick; }; export type TSecretScanningV2QueueServiceFactory = Awaited>; @@ -65,7 +68,8 @@ export const secretScanningV2QueueServiceFactory = async ({ kmsService, auditLogService, keyStore, - appConnectionDAL + appConnectionDAL, + notificationService }: TSecretRotationV2QueueServiceFactoryDep) => { const queueDataSourceFullScan = async ( dataSource: TSecretScanningDataSourceWithConnection, @@ -592,16 +596,38 @@ export const secretScanningV2QueueServiceFactory = async ({ const timestamp = new Date().toISOString(); + const subjectLine = + payload.status === SecretScanningScanStatus.Completed + ? "Incident Alert: Secret(s) Leaked" + : `Secret Scanning Failed`; + + await notificationService.createUserNotifications( + recipients.map((member) => ({ + userId: member.userId, + orgId: project.orgId, + type: + payload.status === SecretScanningScanStatus.Completed + ? NotificationType.SECRET_SCANNING_SECRETS_DETECTED + : NotificationType.SECRET_SCANNING_SCAN_FAILED, + title: subjectLine, + body: + payload.status === SecretScanningScanStatus.Completed + ? `Uncovered **${payload.numberOfSecrets}** secret(s) ${payload.isDiffScan ? " from a recent commit to" : " in"} **${resourceName}**.` + : `Encountered an error while attempting to scan the resource **${resourceName}**: ${payload.errorMessage}`, + link: + payload.status === SecretScanningScanStatus.Completed + ? `/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}` + : `/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}` + })) + ); + await smtpService.sendMail({ recipients: recipients.map((member) => member.user.email!).filter(Boolean), template: payload.status === SecretScanningScanStatus.Completed ? SmtpTemplates.SecretScanningV2SecretsDetected : SmtpTemplates.SecretScanningV2ScanFailed, - subjectLine: - payload.status === SecretScanningScanStatus.Completed - ? "Incident Alert: Secret(s) Leaked" - : `Secret Scanning Failed`, + subjectLine, substitutions: payload.status === SecretScanningScanStatus.Completed ? { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 11cf65d15..c88874499 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2030,7 +2030,8 @@ export const registerRoutes = async ( smtpService, kmsService, keyStore, - appConnectionDAL + appConnectionDAL, + notificationService }); const secretScanningV2Service = secretScanningV2ServiceFactory({ diff --git a/backend/src/services/notification/notification-types.ts b/backend/src/services/notification/notification-types.ts index 800e412d5..e33bafcb9 100644 --- a/backend/src/services/notification/notification-types.ts +++ b/backend/src/services/notification/notification-types.ts @@ -4,7 +4,9 @@ export enum NotificationType { ACCESS_POLICY_BYPASSED = "access-policy-bypassed", SECRET_CHANGE_REQUEST = "secret-change-request", SECRET_CHANGE_POLICY_BYPASSED = "secret-change-policy-bypassed", - SECRET_ROTATION_FAILED = "secret-rotation-failed" + SECRET_ROTATION_FAILED = "secret-rotation-failed", + SECRET_SCANNING_SECRETS_DETECTED = "secret-scanning-secrets-detected", + SECRET_SCANNING_SCAN_FAILED = "secret-scanning-scan-failed" } export interface TCreateUserNotificationDTO {