diff --git a/src/components/AlertsTable.tsx b/src/components/AlertsTable.tsx index 7483727a..6b0d7ac0 100644 --- a/src/components/AlertsTable.tsx +++ b/src/components/AlertsTable.tsx @@ -70,6 +70,7 @@ function IssueDetectedCellContent({ alert }: { alert: AlertConversation }) { return ( <> + Blocked malicious package ); diff --git a/src/features/alerts/lib/__tests__/is-alert-malicious.test.ts b/src/features/alerts/lib/__tests__/is-alert-malicious.test.ts new file mode 100644 index 00000000..55baa39b --- /dev/null +++ b/src/features/alerts/lib/__tests__/is-alert-malicious.test.ts @@ -0,0 +1,12 @@ +import { test, expect } from "vitest"; +import { isAlertMalicious } from "../is-malicious"; +import { ALERT_MALICIOUS } from "../../mocks/alert-malicious.mock"; +import { ALERT_SECRET } from "../../mocks/alert-secret.mock"; + +test("matches malicious alert", () => { + expect(isAlertMalicious(ALERT_MALICIOUS)).toBe(true); +}); + +test("doesn't match secret", () => { + expect(isAlertMalicious(ALERT_SECRET)).toBe(false); +}); diff --git a/src/features/alerts/lib/__tests__/is-alert-secret.test.ts b/src/features/alerts/lib/__tests__/is-alert-secret.test.ts new file mode 100644 index 00000000..a42a77ba --- /dev/null +++ b/src/features/alerts/lib/__tests__/is-alert-secret.test.ts @@ -0,0 +1,12 @@ +import { test, expect } from "vitest"; +import { ALERT_MALICIOUS } from "../../mocks/alert-malicious.mock"; +import { ALERT_SECRET } from "../../mocks/alert-secret.mock"; +import { isAlertSecret } from "../is-alert-secret"; + +test("matches secret alert", () => { + expect(isAlertSecret(ALERT_SECRET)).toBe(true); +}); + +test("doesn't match malicious", () => { + expect(isAlertSecret(ALERT_MALICIOUS)).toBe(false); +}); diff --git a/src/features/alerts/lib/is-alert-secret.ts b/src/features/alerts/lib/is-alert-secret.ts new file mode 100644 index 00000000..dbaf329a --- /dev/null +++ b/src/features/alerts/lib/is-alert-secret.ts @@ -0,0 +1,8 @@ +import { AlertConversation } from "@/api/generated"; + +export function isAlertSecret({ + trigger_type, + trigger_category, +}: AlertConversation) { + return trigger_category === "critical" && trigger_type === "codegate-secrets"; +} diff --git a/src/features/alerts/lib/is-malicious.ts b/src/features/alerts/lib/is-malicious.ts new file mode 100644 index 00000000..77ad4f2a --- /dev/null +++ b/src/features/alerts/lib/is-malicious.ts @@ -0,0 +1,14 @@ +import { AlertConversation } from "@/api/generated"; + +export function isAlertMalicious({ + trigger_string, + trigger_category, +}: AlertConversation) { + return ( + trigger_category === "critical" && + trigger_string !== null && + typeof trigger_string === "object" && + "status" in trigger_string && + trigger_string.status === "malicious" + ); +} diff --git a/src/features/alerts/mocks/alert-malicious.mock.ts b/src/features/alerts/mocks/alert-malicious.mock.ts new file mode 100644 index 00000000..0605ff64 --- /dev/null +++ b/src/features/alerts/mocks/alert-malicious.mock.ts @@ -0,0 +1,37 @@ +import { AlertConversation, QuestionType } from "@/api/generated"; + +export const ALERT_MALICIOUS = { + conversation: { + question_answers: [ + { + question: { + message: + "Context: invokehttp is a Python package available on PyPI ecosystem. However, this package is found to be malicious and must not be used. For additional information refer to https://www.insight.stacklok.com/report/pypi/invokehttp - Package offers this functionality: Python HTTP for Humans.\n \n\n Query: Is invokehttp a malicious package?", + timestamp: "2025-01-14T16:29:49.602403Z", + message_id: "bf92bf3c-fcec-4064-ad02-c792026c3555", + }, + answer: { + message: + "**Warning:** CodeGate detected one or more malicious, deprecated or archived packages.\n- Pkg 1: [https://www.insight.stacklok.com/report/pypi/invokehttp](https://www.insight.stacklok.com/report/pypi/invokehttp)", + timestamp: "2025-01-14T16:29:50.213490Z", + message_id: "7e260699-906e-43dc-a43e-8f288389bd9d", + }, + }, + ], + provider: "copilot", + type: QuestionType.CHAT, + chat_id: "bf92bf3c-fcec-4064-ad02-c792026c3555", + conversation_timestamp: "2025-01-14T16:29:49.602403Z", + }, + alert_id: "bf92bf3c-fcec-4064-ad02-c792026c3555", + code_snippet: null, + trigger_string: { + name: "invokehttp", + type: "pypi", + status: "malicious", + description: "Python HTTP for Humans.", + }, + trigger_type: "codegate-context-retriever", + trigger_category: "critical", + timestamp: "2025-01-14T16:29:49.602403Z", +} satisfies AlertConversation; diff --git a/src/features/alerts/mocks/alert-secret.mock.ts b/src/features/alerts/mocks/alert-secret.mock.ts new file mode 100644 index 00000000..287d8f99 --- /dev/null +++ b/src/features/alerts/mocks/alert-secret.mock.ts @@ -0,0 +1,31 @@ +import { AlertConversation, QuestionType } from "@/api/generated"; + +export const ALERT_SECRET = { + conversation: { + question_answers: [ + { + question: { + message: "Analyse this file please", + timestamp: "2025-01-13T17:15:06.942856Z", + message_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", + }, + answer: { + message: "foo-bar", + timestamp: "2025-01-13T17:15:08.537530Z", + message_id: "f1a6201f-0d7f-4c93-bb84-525f2a2d0d3b", + }, + }, + ], + provider: "copilot", + type: QuestionType.CHAT, + chat_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", + conversation_timestamp: "2025-01-13T17:15:06.942856Z", + }, + alert_id: "11ab8b11-0338-4fdb-b329-2184d3e71a14", + code_snippet: null, + trigger_string: + "Amazon - Secret Access Key:\n steps:\n - name: Checkout Repository\n uses: REDACTED<$foo-bar> # v4\n\n - name: Setup\n uses: ./.github/actions/setup", + trigger_type: "codegate-secrets", + trigger_category: "critical", + timestamp: "2025-01-13T17:15:06.942856Z", +} satisfies AlertConversation; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 262d785e..100451bb 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,6 @@ import { AlertConversation, Conversation } from "@/api/generated/types.gen"; +import { isAlertSecret } from "@/features/alerts/lib/is-alert-secret"; +import { isAlertMalicious } from "@/features/alerts/lib/is-malicious"; import { MaliciousPkgType, TriggerType } from "@/types"; import { format, isToday, isYesterday } from "date-fns"; @@ -178,12 +180,9 @@ export function getMaliciousPackage( export function getIssueDetectedType( alert: AlertConversation, -): "malicious_package" | "leaked_secret" { - const maliciousPackage = getMaliciousPackage(alert.trigger_string); +): "malicious_package" | "leaked_secret" | null { + if (isAlertMalicious(alert)) return "malicious_package"; + if (isAlertSecret(alert)) return "leaked_secret"; - if (maliciousPackage !== null && typeof maliciousPackage === "object") { - return "malicious_package"; - } - - return "leaked_secret"; + return null; }