From 6b65f551fa6862713936ecad785454ae4f8b3ccb Mon Sep 17 00:00:00 2001 From: Evgeny Shurakov Date: Thu, 5 Feb 2026 22:21:59 +0100 Subject: [PATCH] WebRisk - Add Slack notification for deploy threat detections Implement Slack webhook integration in the deploy threat handler to alert admins when a threat is detected. Includes deployment details, threat types, and a direct link to the admin panel. --- src/lib/config.server.ts | 3 +++ src/lib/webrisk/handler.ts | 44 +++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/lib/config.server.ts b/src/lib/config.server.ts index 87e9fdb7d..0fbc08889 100644 --- a/src/lib/config.server.ts +++ b/src/lib/config.server.ts @@ -103,6 +103,9 @@ export const SLACK_SIGNING_SECRET = getEnvVariable('SLACK_SIGNING_SECRET'); // Posts user feedback into a fixed Slack channel in the Kilo workspace. // Expected to be a Slack Incoming Webhook URL. export const SLACK_USER_FEEDBACK_WEBHOOK_URL = getEnvVariable('SLACK_USER_FEEDBACK_WEBHOOK_URL'); +// Posts deploy threat alerts to a dedicated Slack channel. +// Expected to be a Slack Incoming Webhook URL. +export const SLACK_DEPLOY_THREAT_WEBHOOK_URL = getEnvVariable('SLACK_DEPLOY_THREAT_WEBHOOK_URL'); export const ENABLE_MILVUS_DUAL_WRITE = true; // AI Attribution Service diff --git a/src/lib/webrisk/handler.ts b/src/lib/webrisk/handler.ts index 6772ee883..aa1043832 100644 --- a/src/lib/webrisk/handler.ts +++ b/src/lib/webrisk/handler.ts @@ -1,6 +1,8 @@ import { db } from '@/lib/drizzle'; import { deployments, deployment_threat_detections } from '@/db/schema'; import { eq } from 'drizzle-orm'; +import { APP_URL } from '@/lib/constants'; +import { SLACK_DEPLOY_THREAT_WEBHOOK_URL } from '@/lib/config.server'; import type { CheckUrlResult, ThreatType } from './web-risk-client'; type Deployment = typeof deployments.$inferSelect; @@ -41,20 +43,38 @@ type AlertAdminsParams = { threatTypes: ThreatType[]; }; -/** - * Alert admins about a detected threat. - * - * Currently logs to console. Future: integrate with Slack webhook. - */ async function alertAdmins(params: AlertAdminsParams): Promise { + const { deployment, threatTypes } = params; + const timestamp = new Date().toISOString(); + console.warn('[THREAT DETECTED]', { - deploymentId: params.deployment.id, - deploymentUrl: params.deployment.deployment_url, - deploymentSlug: params.deployment.deployment_slug, - threatTypes: params.threatTypes, - buildId: params.deployment.last_build_id, - timestamp: new Date().toISOString(), + deploymentId: deployment.id, + deploymentUrl: deployment.deployment_url, + deploymentSlug: deployment.deployment_slug, + threatTypes, + buildId: deployment.last_build_id, + timestamp, }); - // TODO: Integrate with Slack admin notification system when webhook is available + if (SLACK_DEPLOY_THREAT_WEBHOOK_URL) { + const adminUrl = `${APP_URL}/admin/deployments?search=${encodeURIComponent(deployment.deployment_slug ?? '')}`; + const textLines = [ + ':rotating_light: *Deploy Threat Detected*', + deployment.deployment_slug ? `• Deployment: \`${deployment.deployment_slug}\`` : null, + deployment.deployment_url ? `• URL: ${deployment.deployment_url}` : null, + `• Threat types: \`${threatTypes.join(', ')}\``, + `• Deployment ID: \`${deployment.id}\``, + deployment.last_build_id ? `• Build ID: \`${deployment.last_build_id}\`` : null, + `• Detected at: \`${timestamp}\``, + `• <${adminUrl}|View in Admin>`, + ].filter((line): line is string => line !== null); + + await fetch(SLACK_DEPLOY_THREAT_WEBHOOK_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: textLines.join('\n') }), + }).catch(error => { + console.error('[DeployThreat] Failed to post to Slack webhook', error); + }); + } }