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) */