@cryptotaxi247 / CoPilot / commits / 441f6b31

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"