Velo artifacts rec (#227)
* testing route * updated dependencies * updated artifacts api/types * type refactor * updated modal style * updated SocAlertItemContext component * added SocAlertItemRecommendation component * url change * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>
taylor_socfortress committed
May 31, 2024 at 08:43 UTC
441f6b3123113d807257bb08bb90e6a1a1727598
15 files changed
+369
-107
backend/app/connectors/velociraptor/routes/artifacts.py
+4
-1
@@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
9
from sqlalchemy.future import select
10
11
from app.auth.utils import AuthHandler
12
+from app.connectors.velociraptor.schema.artifacts import ArtifactReccomendationAIRequest
13
from app.connectors.velociraptor.schema.artifacts import ArtifactReccomendationRequest
14
from app.connectors.velociraptor.schema.artifacts import ArtifactsResponse
15
from app.connectors.velociraptor.schema.artifacts import CollectArtifactBody
@@ -408,7 +409,7 @@ async def quarantine(
409
"/velociraptor-artifact-recommendation",
410
description="Retrieve artifact to run based on alert. Invokes the `copilot-ai-module",
411
)
411
-async def get_artifact_recommendation():
412
+async def get_artifact_recommendation(request: ArtifactReccomendationAIRequest):
413
"""
414
Retrieve the artifact to run based on the alert.
415
@@ -421,5 +422,7 @@ async def get_artifact_recommendation():
422
return await post_to_copilot_ai_module(
423
data=ArtifactReccomendationRequest(
424
artifacts=artifacts.artifacts,
425
+ os=request.os,
426
+ prompt=request.prompt,
427
),
428
)
backend/app/connectors/velociraptor/schema/artifacts.py
+77
@@ -119,10 +119,87 @@ class QuarantineResponse(BaseResponse):
119
pass # If you have additional fields, you can define them here
120
121
122
+payload = {
123
+ "data_win_system_eventRecordID": "521098",
124
+ "data_win_eventdata_user": "WIN-HFOU106TD7K\\Administrator",
125
+ "agent_id": "111",
126
+ "agent_name": "WIN-HFO106TD7K",
127
+ "gl2_remote_ip": "10.255.255.13",
128
+ "data_win_system_eventID": "22",
129
+ "agent_labels_customer": "00002",
130
+ "source": "10.255.255.13",
131
+ "gl2_source_input": "660320f176ca320e8393f030",
132
+ "rule_level": 3,
133
+ "data_win_system_task": "22",
134
+ "timestamp_utc": "2024-04-17T15:06:54.742Z",
135
+ "syslog_type": "wazuh",
136
+ "data_win_system_threadID": "2888",
137
+ "rule_description": "Sysmon - Event 22: DNS Request by C:\\Windows\\system32\\PING.EXE",
138
+ "gl2_source_node": "3b68efa4-3319-4885-a38f-c944f0fcf191",
139
+ "id": "1713366415.56188571",
140
+ "rule_mitre_tactic": "Command and Control",
141
+ "process_image": "C:\\Windows\\system32\\PING.EXE",
142
+ "data_win_eventdata_utcTime": "2024-04-17 15:06:28.457",
143
+ "streams": ["661555f676ca320e837b14cc", "660320f176ca320e8393f057"],
144
+ "rule_mitre_id": "T1071",
145
+ "gl2_message_id": "01HVP9HG8YE31EQH1878V50H89",
146
+ "data_win_system_computer": "WIN-HFOU106TD7K",
147
+ "agent_ip": "192.168.200.3",
148
+ "data_win_eventdata_image": "C:\\Windows\\system32\\PING.EXE",
149
+ "threat_intel_value": "evil.socfortress.co",
150
+ "data_win_eventdata_queryName": "evil.socfortress.co",
151
+ "rule_groups": "windows, sysmon, sysmon_event_22",
152
+ "data_win_system_keywords": "0x8000000000000000",
153
+ "data_win_system_level": "4",
154
+ "process_id": "6072",
155
+ "data_win_eventdata_queryStatus": "0",
156
+ "data_win_system_severityValue": "INFORMATION",
157
+ "dns_response_code": "0",
158
+ "dns_query": "evil.socfortress.co",
159
+ "data_win_eventdata_processGuid": "{691ff406-e58c-661f-b401-000000002300}",
160
+ "rule_mitre_technique": "Application Layer Protocol",
161
+ "rule_firedtimes": 2,
162
+ "data_win_system_systemTime": "2024-04-17T15:06:54.742696000Z",
163
+ "decoder_name": "windows_eventchannel",
164
+ "data_win_system_processID": "2180",
165
+ "data_win_system_channel": "Microsoft-Windows-Sysmon/Operational",
166
+ "syslog_level": "ALERT",
167
+ "threat_intel_comment": "This is a test IoC",
168
+ "data_win_system_providerName": "Microsoft-Windows-Sysmon",
169
+ "data_win_eventdata_processId": "6072",
170
+ "data_win_system_version": "5",
171
+ "data_win_system_providerGuid": "{5770385f-c22a-43e0-bf4c-06f5698ffbd9}",
172
+ "timestamp": "2024-04-17 15:06:57.694",
173
+ "threat_intel_ioc_source": "test",
174
+ "rule_group1": "windows",
175
+ "data_win_system_opcode": "0",
176
+}
177
+
178
+
179
+class OS(str, Enum):
180
+ Windows = "Windows"
181
+ Linux = "Linux"
182
+ MacOS = "MacOS"
183
+
184
+
185
+class ArtifactReccomendationAIRequest(BaseModel):
186
+ os: OS = Field(..., description="Operating system of the client")
187
+ prompt: dict = Field(..., example=payload)
188
+
189
+
190
class ArtifactReccomendationRequest(BaseModel):
191
artifacts: List[Artifacts] = Field(..., description="List of artifacts to be recommended")
192
+ os: str = Field(..., description="Operating system of the client")
193
+ prompt: dict = Field(..., example=payload)
194
+
195
+
196
+class VelociraptorArtifactRecommendation(BaseModel):
197
+ name: str = Field(..., description="The name of the artifact.")
198
+ description: str = Field(..., description="A description of the artifact.")
199
+ explanation: str = Field(..., description="A detailed explanation of the purpose and why the artifact was selected.")
200
201
202
class ArtifactReccomendationResponse(BaseModel):
203
message: str = Field(...)
204
success: bool = Field(...)
205
+ recommendations: list[VelociraptorArtifactRecommendation]
backend/app/connectors/velociraptor/services/artifacts.py
+1
-1
@@ -284,7 +284,7 @@ async def post_to_copilot_ai_module(data: ArtifactReccomendationRequest) -> Arti
284
# raise HTTPException(status_code=501, detail="Not Implemented Yet")
285
async with httpx.AsyncClient() as client:
286
data = await client.post(
287
- "http://127.0.0.1:5001/velociraptor-artifact-recommendation",
287
+ "http://copilot-ai-module/velociraptor-artifact-recommendation",
288
json=data.dict(),
289
timeout=120,
290
)
frontend/package-lock.json
+74
-68
@@ -14,9 +14,9 @@
14
"@fontsource/lexend": "^5.0.20",
15
"@fontsource/public-sans": "^5.0.18",
16
"@popperjs/core": "^2.11.8",
17
- "@shikijs/markdown-it": "^1.6.0",
18
- "@vueuse/components": "^10.9.0",
19
- "@vueuse/core": "^10.9.0",
17
+ "@shikijs/markdown-it": "^1.6.1",
18
+ "@vueuse/components": "^10.10.0",
19
+ "@vueuse/core": "^10.10.0",
20
"apexcharts": "^3.49.1",
21
"bytes": "^3.1.2",
22
"colord": "^2.9.3",
@@ -35,7 +35,7 @@
35
"pinia": "^2.1.7",
36
"pinia-plugin-persistedstate": "^3.2.1",
37
"secure-ls": "^1.2.6",
38
- "shiki": "^1.6.0",
38
+ "shiki": "^1.6.1",
39
"validator": "^13.12.0",
40
"vue": "^3.4.27",
41
"vue-advanced-cropper": "^2.8.8",
@@ -58,7 +58,7 @@
58
"@types/inquirer": "^9.0.7",
59
"@types/jsdom": "^21.1.6",
60
"@types/lodash": "^4.17.4",
61
- "@types/node": "^20.12.12",
61
+ "@types/node": "^20.12.13",
62
"@types/validator": "^13.11.10",
63
"@vitejs/plugin-vue": "^5.0.4",
64
"@vitejs/plugin-vue-jsx": "^3.1.0",
@@ -69,24 +69,24 @@
69
"autoprefixer": "^10.4.19",
70
"cypress": "^13.10.0",
71
"eslint": "^8.57.0",
72
- "eslint-plugin-cypress": "^3.2.0",
72
+ "eslint-plugin-cypress": "^3.3.0",
73
"eslint-plugin-vue": "^9.26.0",
74
"flourite": "^1.2.4",
75
"fs-extra": "^11.2.0",
76
"ip": "^2.0.1",
77
- "jsdom": "^24.0.0",
77
+ "jsdom": "^24.1.0",
78
"json5": "^2.2.3",
79
"npm-run-all": "^4.1.5",
80
"picocolors": "^1.0.1",
81
"postcss": "^8.4.38",
82
"prettier": "^3.2.5",
83
- "sass": "^1.77.2",
83
+ "sass": "^1.77.3",
84
"start-server-and-test": "^2.0.3",
85
"tailwind-config-viewer": "^2.0.2",
86
"tailwindcss": "^3.4.3",
87
"taze": "^0.13.8",
88
"unplugin-vue-components": "^0.27.0",
89
- "vite": "^5.2.11",
89
+ "vite": "^5.2.12",
90
"vite-bundle-analyzer": "^0.9.4",
91
"vite-bundle-visualizer": "^1.2.1",
92
"vite-svg-loader": "^5.1.0",
@@ -1839,26 +1839,26 @@
1839
"dev": true
1840
},
1841
"node_modules/@shikijs/core": {
1842
- "version": "1.6.0",
1843
- "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz",
1844
- "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA=="
1842
+ "version": "1.6.1",
1843
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.1.tgz",
1844
+ "integrity": "sha512-CqYyepN4SnBopaoXYwng4NO8riB5ask/LTCkhOFq+GNGtr2X+aKeD767eYdqYukeixEUvv4bXdyTYVaogj7KBw=="
1845
},
1846
"node_modules/@shikijs/markdown-it": {
1847
- "version": "1.6.0",
1848
- "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-1.6.0.tgz",
1849
- "integrity": "sha512-FZBOyjgtRb3SgV5MSRdpS8h0fHkhVH7MlCJSVatA4RAYgFwjxuUtH22lGHnnn353cCQvx2Z/9t2qIV9bXXfcsQ==",
1847
+ "version": "1.6.1",
1848
+ "resolved": "https://registry.npmjs.org/@shikijs/markdown-it/-/markdown-it-1.6.1.tgz",
1849
+ "integrity": "sha512-l6yCxrL18vNGtChXonuq2SxFeqYna6qwIXDncb/fH1RASFZjLroEblV/lsONvuDudRyhKJu8MC3ErLIpcT9BLw==",
1850
"dependencies": {
1851
- "@shikijs/transformers": "1.6.0",
1851
+ "@shikijs/transformers": "1.6.1",
1852
"markdown-it": "^14.1.0",
1853
- "shiki": "1.6.0"
1853
+ "shiki": "1.6.1"
1854
}
1855
},
1856
"node_modules/@shikijs/transformers": {
1857
- "version": "1.6.0",
1858
- "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.6.0.tgz",
1859
- "integrity": "sha512-qGfHe1ECiqfE2STPWvfogIj/9Q0SK+MCRJdoITkW7AmFuB7DmbFnBT2US84+zklJOB51MzNO8RUXZiauWssJlQ==",
1857
+ "version": "1.6.1",
1858
+ "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.6.1.tgz",
1859
+ "integrity": "sha512-m/h2Dh99XWvTzHL8MUQmEnrB+/gxDljIfgDNR00Zg941KENqORx8Hi9sKpGYjCgXoEJKASZlEMQdPnkHj9/8aQ==",
1860
"dependencies": {
1861
- "shiki": "1.6.0"
1861
+ "shiki": "1.6.1"
1862
}
1863
},
1864
"node_modules/@sideway/address": {
@@ -1985,9 +1985,9 @@
1985
}
1986
},
1987
"node_modules/@types/node": {
1988
- "version": "20.12.12",
1989
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
1990
- "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
1988
+ "version": "20.12.13",
1989
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz",
1990
+ "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==",
1991
"dev": true,
1992
"dependencies": {
1993
"undici-types": "~5.26.4"
@@ -2898,12 +2898,12 @@
2898
"dev": true
2899
},
2900
"node_modules/@vueuse/components": {
2901
- "version": "10.9.0",
2902
- "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.9.0.tgz",
2903
- "integrity": "sha512-BHQpA0yIi3y7zKa1gYD0FUzLLkcRTqVhP8smnvsCK6GFpd94Nziq1XVPD7YpFeho0k5BzbBiNZF7V/DpkJ967A==",
2901
+ "version": "10.10.0",
2902
+ "resolved": "https://registry.npmjs.org/@vueuse/components/-/components-10.10.0.tgz",
2903
+ "integrity": "sha512-HiA10NQ9HJAGnju+8ZK4TyA8LIc0a6BnJmVWDa/k+TRhaYCVacSDU04k0BQ2otV+gghUDdwu98upf6TDRXpoeg==",
2904
"dependencies": {
2905
- "@vueuse/core": "10.9.0",
2906
- "@vueuse/shared": "10.9.0",
2905
+ "@vueuse/core": "10.10.0",
2906
+ "@vueuse/shared": "10.10.0",
2907
"vue-demi": ">=0.14.7"
2908
}
2909
},
@@ -2933,13 +2933,13 @@
2933
}
2934
},
2935
"node_modules/@vueuse/core": {
2936
- "version": "10.9.0",
2937
- "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz",
2938
- "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==",
2936
+ "version": "10.10.0",
2937
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.10.0.tgz",
2938
+ "integrity": "sha512-vexJ/YXYs2S42B783rI95lMt3GzEwkxzC8Hb0Ndpd8rD+p+Lk/Za4bd797Ym7yq4jXqdSyj3JLChunF/vyYjUw==",
2939
"dependencies": {
2940
"@types/web-bluetooth": "^0.0.20",
2941
- "@vueuse/metadata": "10.9.0",
2942
- "@vueuse/shared": "10.9.0",
2941
+ "@vueuse/metadata": "10.10.0",
2942
+ "@vueuse/shared": "10.10.0",
2943
"vue-demi": ">=0.14.7"
2944
},
2945
"funding": {
@@ -2947,9 +2947,9 @@
2947
}
2948
},
2949
"node_modules/@vueuse/core/node_modules/vue-demi": {
2950
- "version": "0.14.7",
2951
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
2952
- "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
2950
+ "version": "0.14.8",
2951
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
2952
+ "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
2953
"hasInstallScript": true,
2954
"bin": {
2955
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -2972,17 +2972,17 @@
2972
}
2973
},
2974
"node_modules/@vueuse/metadata": {
2975
- "version": "10.9.0",
2976
- "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz",
2977
- "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==",
2975
+ "version": "10.10.0",
2976
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.10.0.tgz",
2977
+ "integrity": "sha512-UNAo2sTCAW5ge6OErPEHb5z7NEAg3XcO9Cj7OK45aZXfLLH1QkexDcZD77HBi5zvEiLOm1An+p/4b5K3Worpug==",
2978
"funding": {
2979
"url": "https://github.com/sponsors/antfu"
2980
}
2981
},
2982
"node_modules/@vueuse/shared": {
2983
- "version": "10.9.0",
2984
- "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz",
2985
- "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==",
2983
+ "version": "10.10.0",
2984
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.10.0.tgz",
2985
+ "integrity": "sha512-2aW33Ac0Uk0U+9yo3Ypg9s5KcR42cuehRWl7vnUHadQyFvCktseyxxEPBi1Eiq4D2yBGACOnqLZpx1eMc7g5Og==",
2986
"dependencies": {
2987
"vue-demi": ">=0.14.7"
2988
},
@@ -2991,9 +2991,9 @@
2991
}
2992
},
2993
"node_modules/@vueuse/shared/node_modules/vue-demi": {
2994
- "version": "0.14.7",
2995
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
2996
- "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
2994
+ "version": "0.14.8",
2995
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
2996
+ "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
2997
"hasInstallScript": true,
2998
"bin": {
2999
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -5157,9 +5157,9 @@
5157
}
5158
},
5159
"node_modules/eslint-plugin-cypress": {
5160
- "version": "3.2.0",
5161
- "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.2.0.tgz",
5162
- "integrity": "sha512-HaxMz6BoU4ay+K4WrG9ZJC1NdX06FqSlAwtRDStjM0ORFT7zCNPNuRJ+kUPc17Rt2AMUBSqeD9L0zTR3uZhPpw==",
5160
+ "version": "3.3.0",
5161
+ "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.3.0.tgz",
5162
+ "integrity": "sha512-HPHMPzYBIshzJM8wqgKSKHG2p/8R0Gbg4Pb3tcdC9WrmkuqxiKxSKbjunUrajhV5l7gCIFrh1P7C7GuBqH6YuQ==",
5163
"dev": true,
5164
"dependencies": {
5165
"globals": "^13.20.0"
@@ -6945,9 +6945,9 @@
6945
"dev": true
6946
},
6947
"node_modules/jsdom": {
6948
- "version": "24.0.0",
6949
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz",
6950
- "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==",
6948
+ "version": "24.1.0",
6949
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz",
6950
+ "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==",
6951
"dev": true,
6952
"dependencies": {
6953
"cssstyle": "^4.0.1",
@@ -6955,21 +6955,21 @@
6955
"decimal.js": "^10.4.3",
6956
"form-data": "^4.0.0",
6957
"html-encoding-sniffer": "^4.0.0",
6958
- "http-proxy-agent": "^7.0.0",
6959
- "https-proxy-agent": "^7.0.2",
6958
+ "http-proxy-agent": "^7.0.2",
6959
+ "https-proxy-agent": "^7.0.4",
6960
"is-potential-custom-element-name": "^1.0.1",
6961
- "nwsapi": "^2.2.7",
6961
+ "nwsapi": "^2.2.10",
6962
"parse5": "^7.1.2",
6963
- "rrweb-cssom": "^0.6.0",
6963
+ "rrweb-cssom": "^0.7.0",
6964
"saxes": "^6.0.0",
6965
"symbol-tree": "^3.2.4",
6966
- "tough-cookie": "^4.1.3",
6966
+ "tough-cookie": "^4.1.4",
6967
"w3c-xmlserializer": "^5.0.0",
6968
"webidl-conversions": "^7.0.0",
6969
"whatwg-encoding": "^3.1.1",
6970
"whatwg-mimetype": "^4.0.0",
6971
"whatwg-url": "^14.0.0",
6972
- "ws": "^8.16.0",
6972
+ "ws": "^8.17.0",
6973
"xml-name-validator": "^5.0.0"
6974
},
6975
"engines": {
@@ -6998,6 +6998,12 @@
6998
"node": ">= 6"
6999
}
7000
},
7001
+ "node_modules/jsdom/node_modules/rrweb-cssom": {
7002
+ "version": "0.7.0",
7003
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.0.tgz",
7004
+ "integrity": "sha512-KlSv0pm9kgQSRxXEMgtivPJ4h826YHsuob8pSHcfSZsSXGtvpEAie8S0AnXuObEJ7nhikOb4ahwxDm0H2yW17g==",
7005
+ "dev": true
7006
+ },
7007
"node_modules/jsdom/node_modules/xml-name-validator": {
7008
"version": "5.0.0",
7009
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -9557,9 +9563,9 @@
9563
"dev": true
9564
},
9565
"node_modules/sass": {
9560
- "version": "1.77.2",
9561
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz",
9562
- "integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==",
9566
+ "version": "1.77.3",
9567
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.3.tgz",
9568
+ "integrity": "sha512-WJHo+jmFp0dwRuymPmIovuxHaBntcCyja5hCB0yYY9wWrViEp4kF5Cdai98P72v6FzroPuABqu+ddLMbQWmwzA==",
9569
"dev": true,
9570
"dependencies": {
9571
"chokidar": ">=3.0.0 <4.0.0",
@@ -9677,11 +9683,11 @@
9683
}
9684
},
9685
"node_modules/shiki": {
9680
- "version": "1.6.0",
9681
- "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz",
9682
- "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==",
9686
+ "version": "1.6.1",
9687
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.1.tgz",
9688
+ "integrity": "sha512-1Pu/A1rtsG6HZvQm4W0NExQ45e02og+rPog7PDaFDiMumZgOYnZIu4JtGQeAIfMwdbKSjJQoCUr79vDLKUUxWA==",
9689
"dependencies": {
9684
- "@shikijs/core": "1.6.0"
9690
+ "@shikijs/core": "1.6.1"
9691
}
9692
},
9693
"node_modules/side-channel": {
@@ -11014,9 +11020,9 @@
11020
}
11021
},
11022
"node_modules/vite": {
11017
- "version": "5.2.11",
11018
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
11019
- "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
11023
+ "version": "5.2.12",
11024
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
11025
+ "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
11026
"dev": true,
11027
"dependencies": {
11028
"esbuild": "^0.20.1",
frontend/package.json
+9
-9
@@ -40,9 +40,9 @@
40
"@fontsource/lexend": "^5.0.20",
41
"@fontsource/public-sans": "^5.0.18",
42
"@popperjs/core": "^2.11.8",
43
- "@shikijs/markdown-it": "^1.6.0",
44
- "@vueuse/components": "^10.9.0",
45
- "@vueuse/core": "^10.9.0",
43
+ "@shikijs/markdown-it": "^1.6.1",
44
+ "@vueuse/components": "^10.10.0",
45
+ "@vueuse/core": "^10.10.0",
46
"apexcharts": "^3.49.1",
47
"bytes": "^3.1.2",
48
"colord": "^2.9.3",
@@ -61,7 +61,7 @@
61
"pinia": "^2.1.7",
62
"pinia-plugin-persistedstate": "^3.2.1",
63
"secure-ls": "^1.2.6",
64
- "shiki": "^1.6.0",
64
+ "shiki": "^1.6.1",
65
"validator": "^13.12.0",
66
"vue": "^3.4.27",
67
"vue-advanced-cropper": "^2.8.8",
@@ -84,7 +84,7 @@
84
"@types/inquirer": "^9.0.7",
85
"@types/jsdom": "^21.1.6",
86
"@types/lodash": "^4.17.4",
87
- "@types/node": "^20.12.12",
87
+ "@types/node": "^20.12.13",
88
"@types/validator": "^13.11.10",
89
"@vitejs/plugin-vue": "^5.0.4",
90
"@vitejs/plugin-vue-jsx": "^3.1.0",
@@ -95,24 +95,24 @@
95
"autoprefixer": "^10.4.19",
96
"cypress": "^13.10.0",
97
"eslint": "^8.57.0",
98
- "eslint-plugin-cypress": "^3.2.0",
98
+ "eslint-plugin-cypress": "^3.3.0",
99
"eslint-plugin-vue": "^9.26.0",
100
"flourite": "^1.2.4",
101
"fs-extra": "^11.2.0",
102
"ip": "^2.0.1",
103
- "jsdom": "^24.0.0",
103
+ "jsdom": "^24.1.0",
104
"json5": "^2.2.3",
105
"npm-run-all": "^4.1.5",
106
"picocolors": "^1.0.1",
107
"postcss": "^8.4.38",
108
"prettier": "^3.2.5",
109
- "sass": "^1.77.2",
109
+ "sass": "^1.77.3",
110
"start-server-and-test": "^2.0.3",
111
"tailwind-config-viewer": "^2.0.2",
112
"tailwindcss": "^3.4.3",
113
"taze": "^0.13.8",
114
"unplugin-vue-components": "^0.27.0",
115
- "vite": "^5.2.11",
115
+ "vite": "^5.2.12",
116
"vite-bundle-analyzer": "^0.9.4",
117
"vite-bundle-visualizer": "^1.2.1",
118
"vite-svg-loader": "^5.1.0",
frontend/src/api/artifacts.ts
+15
-2
@@ -1,9 +1,11 @@
1
import { HttpClient } from "./httpClient"
2
import type { FlaskBaseResponse } from "@/types/flask.d"
3
-import type { Artifact, CollectResult, CommandResult, QuarantineResult } from "@/types/artifacts.d"
3
+import type { Artifact, CollectResult, CommandResult, QuarantineResult, Recommendation } from "@/types/artifacts.d"
4
+import type { AlertContext } from "@/types/soc/alert"
5
+import type { OsTypesFull, OsTypesLower } from "@/types/common"
6
7
export interface ArtifactsQuery {
6
- os?: "windows" | "linux" | "macos"
8
+ os?: OsTypesLower
9
hostname?: string
10
}
11
@@ -27,6 +29,11 @@ export interface QuarantineRequest {
29
artifact_name: "Windows.Remediation.Quarantine" | "Linux.Remediation.Quarantine"
30
}
31
32
+export interface ArtifactRecommendationRequest {
33
+ os: OsTypesFull
34
+ prompt: AlertContext
35
+}
36
+
37
export default {
38
getAll(filters?: ArtifactsQuery) {
39
let url = "/artifacts"
@@ -48,5 +55,11 @@ export default {
55
},
56
quarantine(payload: QuarantineRequest) {
57
return HttpClient.post<FlaskBaseResponse & { results: QuarantineResult[] }>(`/artifacts/quarantine`, payload)
58
+ },
59
+ getArtifactRecommendation(payload: ArtifactRecommendationRequest) {
60
+ return HttpClient.post<FlaskBaseResponse & { recommendations: Recommendation[] }>(
61
+ `/artifacts/velociraptor-artifact-recommendation`,
62
+ payload
63
+ )
64
}
65
}
frontend/src/assets/scss/naive-override.scss
+10
@@ -7,6 +7,16 @@
7
backdrop-filter: blur(20px);
8
max-width: 90%;
9
margin: 10vh auto;
10
+
11
+ .n-card-header {
12
+ flex-wrap: wrap-reverse;
13
+ gap: 12px;
14
+ justify-content: flex-end;
15
+
16
+ .n-card-header__main {
17
+ min-width: auto;
18
+ }
19
+ }
20
}
21
22
.n-image-preview-overlay {
frontend/src/components/activeResponse/ActiveResponseWizard.vue
+3
-4
@@ -103,8 +103,7 @@ import type { SupportedActiveResponse } from "@/types/activeResponse.d"
103
import ActiveResponseItem from "./ActiveResponseItem.vue"
104
import ActiveResponseInvokeForm from "./ActiveResponseInvokeForm.vue"
105
import { iconFromOs } from "@/utils"
106
-
107
-type OS = "linux" | "windows" | "macos"
106
+import type { OsTypesLower } from "@/types/common"
107
108
const emit = defineEmits<{
109
(e: "update:loading", value: boolean): void
@@ -126,7 +125,7 @@ const activeResponseList = ref<SupportedActiveResponse[]>([])
125
const message = useMessage()
126
const current = ref<number>(1)
127
const currentStatus = ref<StepsProps["status"]>("process")
129
-const selectedOS = ref<OS | null>(null)
128
+const selectedOS = ref<OsTypesLower | null>(null)
129
const selectedActiveResponse = ref<SupportedActiveResponse | null>(null)
130
const activeResponseInvokeFormCTX = ref<{ reset: () => void } | null>(null)
131
@@ -186,7 +185,7 @@ function getActiveResponseList() {
185
})
186
}
187
189
-function setOs(os: OS) {
188
+function setOs(os: OsTypesLower) {
189
selectedOS.value = os
190
next()
191
}
frontend/src/components/common/ExpandableText.vue
+1
@@ -4,6 +4,7 @@
4
content-class="expandable-text-popover"
5
scrollable
6
to="body"
7
+ display-directive="show"
8
:disabled="text.length < maxLength"
9
>
10
<template #trigger>
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItem.vue
+17
-16
@@ -18,13 +18,13 @@
18
</div>
19
<SocAlertItemBookmarkToggler
20
v-if="!hideBookmarkAction && alert"
21
- :alert="alert"
22
- :isBookmark="isBookmark"
21
+ :alert
22
+ :isBookmark
23
@bookmark="emit('bookmark', $event)"
24
/>
25
</div>
26
<div class="time">
27
- <SocAlertItemTime :alert="alert" />
27
+ <SocAlertItemTime :alert />
28
</div>
29
</div>
30
<div class="main-box flex justify-between gap-4">
@@ -40,7 +40,7 @@
40
<SocAlertItemActions
41
v-if="!hideSocCaseAction"
42
class="actions-box"
43
- :caseId="caseId"
43
+ :caseId
44
:alertId="alert.alert_id"
45
@caseCreated="caseCreated($event)"
46
@deleted="deleted()"
@@ -60,13 +60,7 @@
60
</span>
61
</div>
62
<n-collapse-transition :show="!showBadgesToggle || showBadges">
63
- <SocAlertItemBadges
64
- class="badges-box"
65
- v-if="alert"
66
- :alert="alert"
67
- :users="users"
68
- @updated="updateAlert"
69
- />
63
+ <SocAlertItemBadges class="badges-box" v-if="alert" :alert :users @updated="updateAlert" />
64
</n-collapse-transition>
65
</div>
66
@@ -76,14 +70,14 @@
70
class="actions-box grow !flex-wrap !justify-start"
71
style="flex-direction: initial"
72
size="small"
79
- :caseId="caseId"
73
+ :caseId
74
:alertId="alert.alert_id"
75
@caseCreated="caseCreated($event)"
76
@deleted="deleted()"
77
@startDeleting="loadingDelete = true"
78
/>
79
<div class="time">
86
- <SocAlertItemTime :alert="alert" hide-timeline />
80
+ <SocAlertItemTime :alert hide-timeline />
81
</div>
82
</div>
83
</div>
@@ -97,7 +91,7 @@
91
<template #header>
92
<div class="py-3 -ml-2">Alert details</div>
93
</template>
100
- <AlertItem :alert="alertObject" :hide-actions="true" class="-mt-4" />
94
+ <AlertItem :alert="alertObject" hide-actions class="-mt-4" />
95
</n-collapse-item>
96
</n-collapse>
97
@@ -106,11 +100,17 @@
100
preset="card"
101
content-class="!p-0"
102
:style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
109
- :title="`SOC Alert: #${alert?.alert_id} - ${alert?.alert_uuid}`"
103
:bordered="false"
104
+ display-directive="show"
105
segmented
106
>
113
- <SocAlertItemDetails v-if="alert" :alert="alert" :users="users" @updated="updateAlert" />
107
+ <template #header>
108
+ <div class="whitespace-nowrap">SOC Alert: {{ alert?.alert_id }}</div>
109
+ </template>
110
+ <template #header-extra>
111
+ <SocAlertItemRecommendation v-if="alert" :alert />
112
+ </template>
113
+ <SocAlertItemDetails v-if="alert" :alert :users @updated="updateAlert" />
114
</n-modal>
115
</n-spin>
116
</template>
@@ -128,6 +128,7 @@ import { NCollapse, useMessage, NCollapseItem, NModal, NSpin, NCheckbox, NCollap
128
import type { SocUser } from "@/types/soc/user.d"
129
const SocAlertItemDetails = defineAsyncComponent(() => import("./SocAlertItemDetails.vue"))
130
const SocAlertItemBadges = defineAsyncComponent(() => import("./SocAlertItemBadges.vue"))
131
+const SocAlertItemRecommendation = defineAsyncComponent(() => import("./SocAlertItemRecommendation.vue"))
132
const AlertItem = defineAsyncComponent(() => import("@/components/alerts/Alert.vue"))
133
134
const checked = defineModel<boolean>("checked", { default: false })
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemContext.vue
+11
-2
@@ -11,7 +11,7 @@
11
<template #key>{{ key }}</template>
12
<template #value>
13
<template v-if="key === 'process_name'">
14
- <template v-if="value && value !== '-' && value.toString()">
14
+ <template v-if="value && value !== '-' && value.toString() && processNameList.length">
15
<div class="flex flex-wrap gap-2">
16
<SocAlertItemEvaluation v-for="pn of processNameList" :key="pn" :process-name="pn" />
17
</div>
@@ -35,6 +35,7 @@ import Icon from "@/components/common/Icon.vue"
35
import { computed, defineAsyncComponent, ref } from "vue"
36
import _split from "lodash/split"
37
import _compact from "lodash/compact"
38
+import _uniq from "lodash/uniq"
39
const SocAlertItemEvaluation = defineAsyncComponent(() => import("./SocAlertItemEvaluation.vue"))
40
const ExpandableText = defineAsyncComponent(() => import("@/components/common/ExpandableText.vue"))
41
@@ -45,7 +46,15 @@ const { alert } = defineProps<{
46
const SearchIcon = "carbon:search"
47
48
const textFilter = ref("")
48
-const processNameList = computed(() => _compact(_split(alert.alert_context?.process_name || "", ",")))
49
+const processNameList = computed(() =>
50
+ _uniq(
51
+ _compact(
52
+ _split(alert.alert_context?.process_name || "", ",").filter(
53
+ p => p.toLowerCase() !== "no process name found"
54
+ )
55
+ )
56
+ )
57
+)
58
const contextNormalized = computed(() => {
59
const list = []
60
for (const key in alert.alert_context) {
frontend/src/components/soc/SocAlerts/SocAlertItem/SocAlertItemRecommendation.vue
new
+136
@@ -0,0 +1,136 @@
1
+<template>
2
+ <div>
3
+ <n-button :size="size || 'small'" ghost type="primary" @click="openRecommendations()">
4
+ <template #icon>
5
+ <Icon :name="AiIcon" />
6
+ </template>
7
+ Recommend Artifact Collection
8
+ </n-button>
9
+
10
+ <n-modal
11
+ v-model:show="showModal"
12
+ preset="card"
13
+ :style="{ maxWidth: 'min(700px, 90vw)', minHeight: 'min(500px, 90vh)' }"
14
+ title="Recommend Artifact Collection"
15
+ :bordered="false"
16
+ display-directive="show"
17
+ segmented
18
+ >
19
+ <div class="flex gap-3 items-center flex-wrap mb-4">
20
+ <div>Get Recommendations for OS:</div>
21
+ <n-select
22
+ size="small"
23
+ v-model:value="selectedOs"
24
+ :options="osOptions"
25
+ class="!w-32"
26
+ placeholder="Select OS"
27
+ />
28
+ <n-button
29
+ size="small"
30
+ type="primary"
31
+ :loading
32
+ @click="getRecommendations()"
33
+ :disabled="!selectedOs"
34
+ v-if="!recommendations.length"
35
+ >
36
+ <template #icon>
37
+ <Icon :name="AiIcon" />
38
+ </template>
39
+ Submit
40
+ </n-button>
41
+ </div>
42
+ <n-spin :show="loading" class="min-h-48">
43
+ <div v-if="recommendations.length" class="flex flex-col gap-2">
44
+ <n-card
45
+ content-class="bg-secondary-color flex flex-col gap-2 !p-0"
46
+ class="overflow-hidden item-appear item-appear-bottom item-appear-005"
47
+ v-for="recommendation of recommendations"
48
+ :key="recommendation.name"
49
+ size="small"
50
+ >
51
+ <strong class="recommendation-name font-mono px-4 pt-3 pb-1">{{ recommendation.name }}</strong>
52
+ <n-divider class="!m-0" />
53
+ <div class="recommendation-description px-4 pt-1">{{ recommendation.description }}</div>
54
+ <p class="recommendation-explanation px-4 pb-3 pt-2">{{ recommendation.explanation }}</p>
55
+ </n-card>
56
+ </div>
57
+ <n-empty
58
+ description="Recommendations not found"
59
+ class="justify-center h-48"
60
+ v-if="!loading && !recommendations.length"
61
+ />
62
+ </n-spin>
63
+ </n-modal>
64
+ </div>
65
+</template>
66
+
67
+<script setup lang="ts">
68
+import Icon from "@/components/common/Icon.vue"
69
+import { useMessage, NModal, NSpin, NEmpty, NButton, NDivider, NSelect, NCard } from "naive-ui"
70
+import type { Size } from "naive-ui/es/button/src/interface"
71
+import { computed, ref } from "vue"
72
+import Api from "@/api"
73
+import _uniqBy from "lodash/uniqBy"
74
+import type { SocAlert } from "@/types/soc/alert"
75
+import type { OsTypesFull } from "@/types/common"
76
+import type { Recommendation } from "@/types/artifacts"
77
+
78
+interface RecommendationStore {
79
+ os: OsTypesFull
80
+ recommendation: Recommendation[]
81
+}
82
+
83
+const { alert, size } = defineProps<{
84
+ alert: SocAlert
85
+ size?: Size
86
+}>()
87
+
88
+const AiIcon = "mage:stars-c"
89
+const showModal = ref<boolean>(false)
90
+const loading = ref<boolean>(false)
91
+const message = useMessage()
92
+
93
+const recommendationsStore = ref<RecommendationStore[]>([])
94
+const selectedOs = ref<OsTypesFull | null>(null)
95
+const recommendations = computed<Recommendation[]>(
96
+ () => recommendationsStore.value.find(item => item.os === selectedOs.value)?.recommendation || []
97
+)
98
+
99
+const osOptions: { label: string; value: OsTypesFull }[] = [
100
+ { label: "Windows", value: "Windows" },
101
+ { label: "Linux", value: "Linux" },
102
+ { label: "MacOS", value: "MacOS" }
103
+]
104
+
105
+function openRecommendations() {
106
+ showModal.value = true
107
+}
108
+
109
+function getRecommendations() {
110
+ if (selectedOs.value !== null && alert) {
111
+ loading.value = true
112
+
113
+ const requestedOs = selectedOs.value
114
+
115
+ Api.artifacts
116
+ .getArtifactRecommendation({ os: requestedOs, prompt: alert.alert_context })
117
+ .then(res => {
118
+ if (res.data.success) {
119
+ recommendationsStore.value.push({
120
+ os: requestedOs,
121
+ recommendation: res.data?.recommendations || []
122
+ })
123
+ recommendationsStore.value = _uniqBy(recommendationsStore.value, "os")
124
+ } else {
125
+ message.warning(res.data?.message || "An error occurred. Please try again later.")
126
+ }
127
+ })
128
+ .catch(err => {
129
+ message.error(err.response?.data?.message || "An error occurred. Please try again later.")
130
+ })
131
+ .finally(() => {
132
+ loading.value = false
133
+ })
134
+ }
135
+}
136
+</script>
frontend/src/types/artifacts.d.ts
+6
@@ -18,3 +18,9 @@ export interface QuarantineResult {
18
Time: string
19
Result: string
20
}
21
+
22
+export interface Recommendation {
23
+ name: string
24
+ description: string
25
+ explanation: string
26
+}
frontend/src/types/common.d.ts
new
+2
@@ -0,0 +1,2 @@
1
+export type OsTypesFull = "Unknown" | "Windows" | "MacOS" | "UNIX" | "Linux"
2
+export type OsTypesLower = "linux" | "windows" | "macos"
frontend/src/utils/index.ts
+3
-4
@@ -3,8 +3,7 @@ import { type Component, h } from "vue"
3
import { isMobile as detectMobile } from "detect-touch-device"
4
import { md5 } from "js-md5"
5
import dayjs from "@/utils/dayjs"
6
-
7
-export type OS = "Unknown" | "Windows" | "MacOS" | "UNIX" | "Linux"
6
+import type { OsTypesFull } from "@/types/common"
7
8
// Transform File Instance in base64 string
9
export function file2Base64(blob: Blob): Promise<string> {
@@ -63,8 +62,8 @@ export function iconFromOs(os: string): string {
62
return "mdi:help-box"
63
}
64
66
-export function getOS(): OS {
67
- let os: OS = "Unknown"
65
+export function getOS(): OsTypesFull {
66
+ let os: OsTypesFull = "Unknown"
67
if (navigator.userAgent.indexOf("Win") != -1) os = "Windows"
68
if (navigator.userAgent.indexOf("Mac") != -1) os = "MacOS"
69
if (navigator.userAgent.indexOf("X11") != -1) os = "UNIX"