@cryptotaxi247 / CoPilot / commits / 7b7d1b97

Process analysis update (#509)

* Integrate MCP query for process name analysis in SocFortress route * Add MCP query response handling and update process name evaluation logic * Remove Raw Result tab from process evaluation component * precommit-fixes

taylor_socfortress committed Sep 12, 2025 at 09:52 UTC 7b7d1b9709ee3bd17a82c51d72b596b7dc13da37
5 files changed +70 -30
backend/app/threat_intel/routes/socfortress.py
+14 -13
@@ -20,14 +20,18 @@ from app.incidents.schema.incident_alert import CreateAlertRequestRoute
20 from app.incidents.schema.incident_alert import GenericAlertModel
21 from app.incidents.services.db_operations import create_comment
22 from app.incidents.services.incident_alert import get_single_alert_details
23 +from app.integrations.copilot_mcp.routes.copilot_mcp import query_mcp
24 +from app.integrations.copilot_mcp.schema.copilot_mcp import MCPQueryRequest
25 +from app.integrations.copilot_mcp.schema.copilot_mcp import MCPQueryResponse
26 from app.middleware.license import get_license
27 from app.middleware.license import is_feature_enabled
28 +
29 +# from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisResponse
30 from app.threat_intel.schema.socfortress import IoCResponse
31 from app.threat_intel.schema.socfortress import SocfortressAiAlertRequest
32 from app.threat_intel.schema.socfortress import SocfortressAiAlertResponse
33 from app.threat_intel.schema.socfortress import SocfortressAiWazuhExclusionRuleResponse
34 from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisRequest
30 -from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisResponse
35 from app.threat_intel.schema.socfortress import SocfortressThreatIntelRequest
36 from app.threat_intel.schema.socfortress import (
37 VelociraptorArtifactRecommendationRequest,
@@ -41,9 +45,10 @@ from app.threat_intel.schema.virustotal import FileReportResponse
45 from app.threat_intel.schema.virustotal import FileSubmissionRequest
46 from app.threat_intel.schema.virustotal import FileSubmissionResponse
47 from app.threat_intel.schema.virustotal import VirusTotalRouteResponse
48 +
49 +# from app.threat_intel.services.socfortress import socfortress_process_analysis_lookup
50 from app.threat_intel.services.socfortress import invoke_virustotal_api
51 from app.threat_intel.services.socfortress import socfortress_ai_alert_lookup
46 -from app.threat_intel.services.socfortress import socfortress_process_analysis_lookup
52 from app.threat_intel.services.socfortress import socfortress_threat_intel_lookup
53 from app.threat_intel.services.socfortress import (
54 socfortress_velociraptor_recommendation_lookup,
@@ -395,7 +400,7 @@ async def analyze_file_complete(
400
401 @threat_intel_socfortress_router.post(
402 "/process_name",
398 - response_model=SocfortressProcessNameAnalysisResponse,
403 + response_model=MCPQueryResponse,
404 description="SocFortress Process Name Evaluation",
405 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
406 )
@@ -414,22 +419,18 @@ async def process_name_intel_socfortress(
419 - _key_exists: bool (optional) - A dependency to ensure the API key exists.
420
421 Returns:
417 - - SocfortressProcessNameAnalysisResponse: The response model containing the results of the SocFortress process name analysis lookup.
422 + - MCPQueryResponse: The response model containing the results of the SocFortress process name analysis lookup.
423 """
424 # await is_feature_enabled("PROCESS ANALYSIS", session=session)
425 logger.info("Running SOCFortress Process Name Analysis. Grabbing License")
426
422 - raise HTTPException(
423 - status_code=501,
424 - detail="SOCFortress Process Name Analysis is currently disabled as the leveraged 3rd party service is unavailable.",
425 - )
426 -
427 - socfortress_lookup = await socfortress_process_analysis_lookup(
428 - lincense_key=(await get_license(session)).license_key,
429 - request=request,
427 + return await query_mcp(
428 + MCPQueryRequest(
429 + mcp_server="cyber-news",
430 + input=f"Analyze the process name: {request.process_name}. Provide a risk assessment and any relevant details.",
431 + ),
432 session=session,
433 )
432 - return socfortress_lookup
434
435
436 async def current_time():
frontend/src/api/endpoints/threatIntel.ts
+2 -1
@@ -5,6 +5,7 @@ import type {
5 AiWazuhExclusionRuleResponse,
6 EpssScore,
7 EvaluationData,
8 + MCPQueryResponse,
9 ThreatIntelResponse,
10 VirusTotalAnalysis,
11 VirusTotalFileCheckResponse,
@@ -23,7 +24,7 @@ export default {
24 const body = {
25 process_name: processName
26 }
26 - return HttpClient.post<FlaskBaseResponse & { data: EvaluationData }>(`/threat_intel/process_name`, body)
27 + return HttpClient.post<(FlaskBaseResponse & { data: EvaluationData }) | MCPQueryResponse>(`/threat_intel/process_name`, body)
28 },
29 epssScore(cve: string) {
30 const body = {
frontend/src/components/threatIntel/ThreatIntelProcessEvaluationBadge.vue
+2 -2
@@ -1,8 +1,8 @@
1 <template>
2 <ThreatIntelProcessEvaluationProvider v-slot="{ openEvaluation }" :process-name>
3 - <code class="text-primary cursor-pointer" @click="openEvaluation()">
3 + <code class="text-primary cursor-pointer hover:text-primary-hover transition-colors" @click="openEvaluation()">
4 {{ processName }}
5 - <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
5 + <Icon :name="LinkIcon" :size="13" class="relative top-0.5 ml-1" />
6 </code>
7 </ThreatIntelProcessEvaluationProvider>
8 </template>
frontend/src/components/threatIntel/ThreatIntelProcessEvaluationProvider.vue
+39 -14
@@ -6,18 +6,34 @@
6 preset="card"
7 content-class="!p-0"
8 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)' }"
9 - :title="`Evaluation: ${processName}`"
9 + :title="`Process Analysis: ${processName}`"
10 :bordered="false"
11 segmented
12 >
13 <n-spin :show="loading" class="min-h-48">
14 - <n-tabs v-if="evaluation" type="line" animated :tabs-padding="24">
14 + <n-tabs v-if="mcpResponse" type="line" animated :tabs-padding="24">
15 <n-tab-pane
16 - name="Overview"
17 - tab="Overview"
16 + v-if="mcpResponse.structured_result?.response"
17 + name="Analysis"
18 + tab="Analysis"
19 + display-directive="show"
20 + >
21 + <div class="p-7 pt-4">
22 + <Markdown :source="mcpResponse.structured_result.response" />
23 + </div>
24 + </n-tab-pane>
25 + <n-tab-pane
26 + v-if="mcpResponse.structured_result?.thinking_process"
27 + name="ThinkingProcess"
28 + tab="Thinking Process"
29 display-directive="show:lazy"
19 - class="flex flex-col gap-4 !py-8"
30 >
31 + <div class="p-7 pt-4">
32 + <Markdown :source="mcpResponse.structured_result.thinking_process" />
33 + </div>
34 + </n-tab-pane>
35 + <!-- Legacy tabs for backward compatibility -->
36 + <n-tab-pane v-if="evaluation" name="Overview" tab="Overview" display-directive="show:lazy" class="flex flex-col gap-4 !py-8">
37 <div class="px-7">
38 <n-card embedded class="overflow-hidden">
39 <div class="flex flex-wrap justify-between gap-8">
@@ -32,7 +48,7 @@
48 {{ evaluation.description }}
49 </div>
50 </n-tab-pane>
35 - <n-tab-pane name="Intel" tab="Intel" display-directive="show:lazy">
51 + <n-tab-pane v-if="evaluation" name="Intel" tab="Intel" display-directive="show:lazy">
52 <div class="p-7 pt-4">
53 <n-input
54 :value="evaluation.intel"
@@ -47,7 +63,7 @@
63 />
64 </div>
65 </n-tab-pane>
50 - <n-tab-pane name="Hashes" tab="Hashes" display-directive="show:lazy">
66 + <n-tab-pane v-if="evaluation?.hashes?.length" name="Hashes" tab="Hashes" display-directive="show:lazy">
67 <ListPercentage
68 class="p-7 pt-4"
69 :list="evaluation.hashes"
@@ -55,7 +71,7 @@
71 percentage-key="percentage"
72 />
73 </n-tab-pane>
58 - <n-tab-pane name="Network" tab="Network" display-directive="show:lazy">
74 + <n-tab-pane v-if="evaluation?.network?.length" name="Network" tab="Network" display-directive="show:lazy">
75 <ListPercentage
76 class="p-7 pt-4"
77 :list="evaluation.network"
@@ -63,7 +79,7 @@
79 percentage-key="usage"
80 />
81 </n-tab-pane>
66 - <n-tab-pane name="Parents" tab="Parents" display-directive="show:lazy">
82 + <n-tab-pane v-if="evaluation?.parents?.length" name="Parents" tab="Parents" display-directive="show:lazy">
83 <ListPercentage
84 class="p-7 pt-4"
85 :list="evaluation.parents"
@@ -71,7 +87,7 @@
87 percentage-key="percentage"
88 />
89 </n-tab-pane>
74 - <n-tab-pane name="Paths" tab="Paths" display-directive="show:lazy">
90 + <n-tab-pane v-if="evaluation?.paths?.length" name="Paths" tab="Paths" display-directive="show:lazy">
91 <ListPercentage
92 class="p-7 pt-4"
93 :list="evaluation.paths"
@@ -80,13 +96,13 @@
96 />
97 </n-tab-pane>
98 </n-tabs>
83 - <n-empty v-if="!loading && !evaluation" description="Evaluation not found" class="h-48 justify-center" />
99 + <n-empty v-if="!loading && !mcpResponse && !evaluation" description="Process analysis not found" class="h-48 justify-center" />
100 </n-spin>
101 </n-modal>
102 </template>
103
104 <script setup lang="ts">
89 -import type { EvaluationData } from "@/types/threatIntel.d"
105 +import type { EvaluationData, MCPQueryResponse } from "@/types/threatIntel.d"
106 import _toSafeInteger from "lodash/toSafeInteger"
107 import { NCard, NEmpty, NInput, NModal, NSpin, NStatistic, NTabPane, NTabs, useMessage } from "naive-ui"
108 import { computed, defineAsyncComponent, ref } from "vue"
@@ -97,8 +113,10 @@ const { processName } = defineProps<{
113 }>()
114
115 const ListPercentage = defineAsyncComponent(() => import("@/components/common/ListPercentage.vue"))
116 +const Markdown = defineAsyncComponent(() => import("@/components/common/Markdown.vue"))
117
118 const evaluation = ref<EvaluationData | null>(null)
119 +const mcpResponse = ref<MCPQueryResponse | null>(null)
120 const showDetails = ref<boolean>(false)
121 const loading = ref<boolean>(false)
122 const message = useMessage()
@@ -106,7 +124,7 @@ const message = useMessage()
124 const eps = computed(() => _toSafeInteger(evaluation.value?.eps || 0))
125
126 function openEvaluation() {
109 - if (!evaluation.value) {
127 + if (!mcpResponse.value && !evaluation.value) {
128 getEvaluation()
129 }
130 showDetails.value = true
@@ -119,7 +137,14 @@ function getEvaluation() {
137 .processNameEvaluation(processName)
138 .then(res => {
139 if (res.data.success) {
122 - evaluation.value = res.data?.data || null
140 + // Check if response is in new MCP format by checking for structured_result property
141 + if ('structured_result' in res.data && res.data.structured_result) {
142 + mcpResponse.value = res.data as MCPQueryResponse
143 + } else {
144 + // Fallback to legacy format
145 + const legacyResponse = res.data as any
146 + evaluation.value = legacyResponse?.data || null
147 + }
148 } else {
149 message.warning(res.data?.message || "An error occurred. Please try again later.")
150 }
frontend/src/types/threatIntel.d.ts
+13
@@ -9,6 +9,19 @@ export interface ThreatIntelResponse {
9 virustotal_url: string | null
10 }
11
12 +export interface StructuredAgentResponse {
13 + response: string
14 + thinking_process: string | null
15 +}
16 +
17 +export interface MCPQueryResponse {
18 + message: string
19 + success: boolean
20 + result?: any
21 + structured_result?: StructuredAgentResponse
22 + execution_time?: number
23 +}
24 +
25 export interface EpssScore {
26 cve: string
27 /** a float (0.000680000) */