Velo artifact rec (#349)
* velo artifact recommendation route * Handle 429 error response in Socfortress AI Alert API invocation * Add notification_invoked_number column to Case model * chore: update dependencies in frontend * feat: add AI Velociraptor Artifact Recommendation Button * Implement case notification count increment and update schema * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>
taylor_socfortress committed
Dec 4, 2024 at 15:52 UTC
4ba91a3c07efb6928445205380f332053242fec4
22 files changed
+669
-154
backend/alembic/versions/b453170bbefc_add_case_notification_column_to_table.py
new
+31
@@ -0,0 +1,31 @@
1
+"""Add case notification column to table
2
+
3
+Revision ID: b453170bbefc
4
+Revises: 21a945c2982b
5
+Create Date: 2024-12-04 15:27:29.341675
6
+
7
+"""
8
+from typing import Sequence
9
+from typing import Union
10
+
11
+import sqlalchemy as sa
12
+
13
+from alembic import op
14
+
15
+# revision identifiers, used by Alembic.
16
+revision: str = "b453170bbefc"
17
+down_revision: Union[str, None] = "21a945c2982b"
18
+branch_labels: Union[str, Sequence[str], None] = None
19
+depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.add_column("incident_management_case", sa.Column("notification_invoked_number", sa.Integer(), nullable=True))
25
+ # ### end Alembic commands ###
26
+
27
+
28
+def downgrade() -> None:
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ op.drop_column("incident_management_case", "notification_invoked_number")
31
+ # ### end Alembic commands ###
backend/app/agents/services/status.py
+31
@@ -32,6 +32,37 @@ def get_agent(agent_id: str) -> List[Agents]:
32
)
33
34
35
+async def get_agent_os_by_id(agent_id: str, session: AsyncSession) -> str:
36
+ """
37
+ Retrieves the operating system of a specific agent from the database using its ID.
38
+
39
+ Args:
40
+ agent_id (str): The ID of the agent to retrieve.
41
+ session (AsyncSession): The SQLAlchemy asynchronous session to use for the query.
42
+
43
+ Returns:
44
+ str: The operating system of the agent if found, otherwise None.
45
+ """
46
+ try:
47
+ agent_result = await session.execute(select(Agents).filter(Agents.agent_id == agent_id))
48
+ agent = agent_result.scalars().first()
49
+
50
+ if agent is None:
51
+ logger.error(f"Agent with agent_id {agent_id} not found.")
52
+ raise HTTPException(
53
+ status_code=404,
54
+ detail=f"Agent with agent_id {agent_id} not found.",
55
+ )
56
+
57
+ return agent.os
58
+ except Exception as e:
59
+ logger.error(f"Failed to fetch agent with agent_id {agent_id}: {e}")
60
+ raise HTTPException(
61
+ status_code=500,
62
+ detail=f"Failed to fetch agent with agent_id {agent_id}: {e}",
63
+ )
64
+
65
+
66
async def get_outdated_agents_wazuh(
67
session: AsyncSession,
68
) -> OutdatedWazuhAgentsResponse:
backend/app/incidents/models.py
+1
@@ -154,6 +154,7 @@ class Case(SQLModel, table=True):
154
case_status: str = Field(max_length=50, nullable=False)
155
assigned_to: Optional[str] = Field(max_length=50, nullable=True)
156
customer_code: Optional[str] = Field(max_length=50, nullable=True)
157
+ notification_invoked_number: Optional[int] = Field(default=0, nullable=True)
158
159
alerts: List["CaseAlertLink"] = Relationship(back_populates="case")
160
data_store: List["CaseDataStore"] = Relationship(back_populates="case")
backend/app/incidents/routes/db_operations.py
+2
@@ -153,6 +153,7 @@ from app.incidents.services.db_operations import get_customer_notification
153
from app.incidents.services.db_operations import get_field_names
154
from app.incidents.services.db_operations import get_ioc_names
155
from app.incidents.services.db_operations import get_timefield_names
156
+from app.incidents.services.db_operations import increment_case_notification_count
157
from app.incidents.services.db_operations import is_alert_linked_to_case
158
from app.incidents.services.db_operations import list_alert_by_assigned_to
159
from app.incidents.services.db_operations import list_alert_by_status
@@ -972,6 +973,7 @@ async def create_case_notification_endpoint(request: CaseNotificationCreate, db:
973
974
logger.info(f"Creating case notification for case {case_notification_payload}")
975
await handle_customer_notifications_case(customer_code=case_details.customer_code, case_payload=case_notification_payload, session=db)
976
+ await increment_case_notification_count(request.case_id, db)
977
return CaseNotificationResponse(success=True, message="Case notification created successfully")
978
979
backend/app/incidents/schema/db_operations.py
+1
@@ -375,6 +375,7 @@ class CaseOut(BaseModel):
375
case_status: Optional[str] = None
376
case_creation_time: Optional[datetime] = None
377
customer_code: Optional[str] = None
378
+ notification_invoked_number: Optional[int] = 0
379
380
381
class CaseOutResponse(BaseModel):
backend/app/incidents/schema/incident_alert.py
+4
@@ -28,6 +28,10 @@ class CreateAlertRequestRoute(BaseModel):
28
description="The name of the index to search alerts for.",
29
)
30
index_id: str = Field(..., description="The index id.")
31
+ agent_id: Optional[str] = Field(
32
+ None,
33
+ description="The agent id.",
34
+ )
35
36
37
class CreateAlertResponse(BaseModel):
backend/app/incidents/services/db_operations.py
+17
@@ -802,6 +802,21 @@ async def update_alert_assigned_to(alert_id: int, assigned_to: str, db: AsyncSes
802
return alert
803
804
805
+async def increment_case_notification_count(case_id: int, db: AsyncSession) -> Case:
806
+ result = await db.execute(select(Case).where(Case.id == case_id))
807
+ case = result.scalars().first()
808
+ if not case:
809
+ raise HTTPException(status_code=404, detail="Case not found")
810
+
811
+ # Initialize notification_invoked_number to 0 if it is None
812
+ if case.notification_invoked_number is None:
813
+ case.notification_invoked_number = 0
814
+
815
+ case.notification_invoked_number += 1
816
+ await db.commit()
817
+ return case
818
+
819
+
820
async def create_comment(comment: CommentCreate, db: AsyncSession) -> Comment:
821
# Check if the alert exists
822
result = await db.execute(select(Alert).options(selectinload(Alert.comments)).where(Alert.id == comment.alert_id))
@@ -1130,6 +1145,7 @@ async def get_case_by_id(case_id: int, db: AsyncSession) -> CaseOut:
1145
alerts=alerts_out,
1146
case_creation_time=case.case_creation_time,
1147
customer_code=case.customer_code,
1148
+ notification_invoked_number=case.notification_invoked_number or 0,
1149
)
1150
return case_out
1151
@@ -1181,6 +1197,7 @@ async def list_cases(db: AsyncSession) -> List[CaseOut]:
1197
case_creation_time=case.case_creation_time,
1198
case_status=case.case_status,
1199
customer_code=case.customer_code,
1200
+ notification_invoked_number=case.notification_invoked_number or 0,
1201
)
1202
cases_out.append(case_out)
1203
return cases_out
backend/app/threat_intel/routes/socfortress.py
+114
@@ -5,7 +5,9 @@ from fastapi import Security
5
from loguru import logger
6
from sqlalchemy.ext.asyncio import AsyncSession
7
8
+from app.agents.services.status import get_agent_os_by_id
9
from app.auth.utils import AuthHandler
10
+from app.connectors.velociraptor.services.artifacts import get_artifacts
11
from app.db.db_session import get_db
12
from app.incidents.schema.incident_alert import CreateAlertRequest
13
from app.incidents.schema.incident_alert import CreateAlertRequestRoute
@@ -20,12 +22,21 @@ from app.threat_intel.schema.socfortress import SocfortressAiWazuhExclusionRuleR
22
from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisRequest
23
from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisResponse
24
from app.threat_intel.schema.socfortress import SocfortressThreatIntelRequest
25
+from app.threat_intel.schema.socfortress import (
26
+ VelociraptorArtifactRecommendationRequest,
27
+)
28
+from app.threat_intel.schema.socfortress import (
29
+ VelociraptorArtifactRecommendationResponse,
30
+)
31
from app.threat_intel.schema.socfortress import VirusTotalThreatIntelRequest
32
from app.threat_intel.schema.virustotal import VirusTotalRouteResponse
33
from app.threat_intel.services.socfortress import invoke_virustotal_api
34
from app.threat_intel.services.socfortress import socfortress_ai_alert_lookup
35
from app.threat_intel.services.socfortress import socfortress_process_analysis_lookup
36
from app.threat_intel.services.socfortress import socfortress_threat_intel_lookup
37
+from app.threat_intel.services.socfortress import (
38
+ socfortress_velociraptor_recommendation_lookup,
39
+)
40
from app.threat_intel.services.socfortress import (
41
socfortress_wazuh_exclusion_rule_lookup,
42
)
@@ -242,3 +253,106 @@ async def ai_wazuh_exclusion_rule_socfortress(
253
request=request,
254
)
255
return socfortress_lookup
256
+
257
+
258
+async def fetch_agent_os(agent_id: str, session: AsyncSession) -> str:
259
+ """
260
+ Fetch the operating system of the agent.
261
+
262
+ Args:
263
+ agent_id (str): The ID of the agent.
264
+ session (AsyncSession): The database session.
265
+
266
+ Returns:
267
+ str: The normalized operating system name.
268
+
269
+ Raises:
270
+ HTTPException: If the agent OS is not found or unsupported.
271
+ """
272
+ agent_os = await get_agent_os_by_id(agent_id=agent_id, session=session)
273
+
274
+ if agent_os is None:
275
+ raise HTTPException(
276
+ status_code=404,
277
+ detail="Agent OS not found.",
278
+ )
279
+
280
+ # Normalize the OS name
281
+ agent_os_lower = agent_os.lower()
282
+ if "windows" in agent_os_lower:
283
+ return "Windows"
284
+ elif "linux" in agent_os_lower:
285
+ return "Linux"
286
+ elif "macos" in agent_os_lower or "mac" in agent_os_lower:
287
+ return "MacOS"
288
+ else:
289
+ raise HTTPException(
290
+ status_code=400,
291
+ detail="Unsupported OS type.",
292
+ )
293
+
294
+
295
+async def filter_artifacts_by_os(artifacts, os):
296
+ # Only get the artifacts that start with `Windows.`, `Linux.`, `MacOS.`, or `Generic.`
297
+ os_artifacts = ["Windows", "Linux", "MacOS", "Generic"]
298
+ os_artifacts = [os_artifact for os_artifact in os_artifacts if os_artifact in os or os_artifact == "Generic"]
299
+
300
+ # Artifacts to be stripped out
301
+ excluded_artifacts = {
302
+ "Windows.Sysinternals.SysmonInstall",
303
+ "Windows.Sysinternals.SysmonLogForward",
304
+ "Windows.Sysinternals.Autoruns",
305
+ "Windows.Sigma.EventLogs",
306
+ "Windows.Remediation.Quarantine",
307
+ "Windows.Remediation.QuarantineMonitor",
308
+ "Windows.Custom.InstallHuntress",
309
+ "Windows.Applications.TeamViewer.Incoming",
310
+ }
311
+
312
+ return [
313
+ artifact
314
+ for artifact in artifacts
315
+ if any(artifact.name.startswith(os_artifact + ".") for os_artifact in os_artifacts) and artifact.name not in excluded_artifacts
316
+ ]
317
+
318
+
319
+async def fetch_artifacts(os: str) -> list:
320
+ """
321
+ Fetch the artifacts.
322
+
323
+ Returns:
324
+ list: The list of artifacts.
325
+ """
326
+ artifacts = await get_artifacts()
327
+ return await filter_artifacts_by_os(artifacts.artifacts, os)
328
+
329
+
330
+@threat_intel_socfortress_router.post(
331
+ "/ai/velociraptor-artifact-recommendation",
332
+ response_model=VelociraptorArtifactRecommendationResponse,
333
+ description="SocFortress Process Name Evaluation",
334
+ dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
335
+)
336
+async def ai_velociraptor_artifact_recommendation_socfortress(
337
+ request: CreateAlertRequestRoute,
338
+ session: AsyncSession = Depends(get_db),
339
+):
340
+ # Fetch alert details
341
+ alert_payload = await get_single_alert_details(CreateAlertRequest(index_name=request.index_name, alert_id=request.index_id))
342
+
343
+ assert isinstance(alert_payload, GenericAlertModel)
344
+
345
+ os = await fetch_agent_os(request.agent_id, session)
346
+
347
+ request = VelociraptorArtifactRecommendationRequest(
348
+ integration="SOCFORTRESS AI",
349
+ alert_payload=alert_payload._source.dict(),
350
+ os=os,
351
+ artifacts=await fetch_artifacts(os),
352
+ )
353
+
354
+ socfortress_lookup = await socfortress_velociraptor_recommendation_lookup(
355
+ lincense_key=(await get_license(session)).license_key,
356
+ request=request,
357
+ )
358
+ return socfortress_lookup
backend/app/threat_intel/schema/socfortress.py
+80
@@ -198,3 +198,83 @@ class SocfortressProcessNameAnalysisResponse(BaseModel):
198
199
def to_dict(self):
200
return self.dict()
201
+
202
+
203
+class Artifacts(BaseModel):
204
+ description: str = Field(..., description="Description of the artifact.")
205
+ name: str = Field(..., description="Name of the artifact.")
206
+
207
+
208
+class OS(str, Enum):
209
+ Windows = "Windows"
210
+ Linux = "Linux"
211
+ MacOS = "MacOS"
212
+
213
+
214
+class VelociraptorArtifactRecommendationRequest(BaseModel):
215
+ integration: str = Field(..., example="SOCFORTRESS AI")
216
+ artifacts: Optional[List[Artifacts]] = Field(
217
+ None,
218
+ description="List of artifacts to recommend.",
219
+ )
220
+ os: OS = Field(..., description="The operating system of the endpoint.")
221
+ alert_payload: dict = Field(..., example={"alert": "test"})
222
+
223
+ @validator("integration")
224
+ def check_integration(cls, v):
225
+ if v != "SOCFORTRESS AI":
226
+ raise HTTPException(
227
+ status_code=400,
228
+ detail="Invalid integration. Only 'SOCFORTRESS AI' is supported.",
229
+ )
230
+ return v
231
+
232
+ @validator("alert_payload")
233
+ def check_syslog_type(cls, v):
234
+ if v.get("syslog_type") != "wazuh":
235
+ raise HTTPException(
236
+ status_code=400,
237
+ detail="Invalid syslog_type. Only 'wazuh' is supported.",
238
+ )
239
+ # Remove 'message' and 'full_log' fields if they exist
240
+ v.pop("message", None)
241
+ v.pop("full_log", None)
242
+ v.pop("gl2_processing_error", None)
243
+ v.pop("gl2_accounted_message_size", None)
244
+ v.pop("gl2_source_input", None)
245
+ v.pop("gl2_remote_ip", None)
246
+ v.pop("gl2_message_id", None)
247
+ v.pop("gl2_remote_port", None)
248
+ return v
249
+
250
+
251
+class VelociraptorArtifactRecommendation(BaseModel):
252
+ name: str = Field(..., description="The name of the artifact.")
253
+ description: str = Field(..., description="A description of the artifact.")
254
+ explanation: str = Field(
255
+ ...,
256
+ description="A detailed explanation of the purpose and why the artifact was selected.",
257
+ )
258
+
259
+
260
+class AiVelociraptorArtifactsRecommendationModel(BaseModel):
261
+ artifact_recommendations: List[VelociraptorArtifactRecommendation] = Field(
262
+ description="The recommended artifacts which detail the name, description, and explanation of why the artifact was selected.",
263
+ )
264
+ general_thoughts: str = Field(
265
+ description="General thoughts on the artifacts and why they were selected.",
266
+ )
267
+
268
+
269
+class VelociraptorArtifactRecommendationResponse(BaseModel):
270
+ artifact_recommendations: List[VelociraptorArtifactRecommendation] = Field(
271
+ description="The recommended artifacts which detail the name, description, and explanation of why the artifact was selected.",
272
+ )
273
+ success: bool = Field(..., description="Whether the request was successful.")
274
+ message: str = Field(
275
+ ...,
276
+ description="A message describing the result of the request.",
277
+ )
278
+ general_thoughts: str = Field(
279
+ description="General thoughts on the artifacts and why they were selected.",
280
+ )
backend/app/threat_intel/services/socfortress.py
+59
@@ -20,6 +20,12 @@ from app.threat_intel.schema.socfortress import (
20
from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisRequest
21
from app.threat_intel.schema.socfortress import SocfortressProcessNameAnalysisResponse
22
from app.threat_intel.schema.socfortress import SocfortressThreatIntelRequest
23
+from app.threat_intel.schema.socfortress import (
24
+ VelociraptorArtifactRecommendationRequest,
25
+)
26
+from app.threat_intel.schema.socfortress import (
27
+ VelociraptorArtifactRecommendationResponse,
28
+)
29
from app.threat_intel.schema.virustotal import VirusTotalResponse
30
from app.utils import get_connector_attribute
31
@@ -409,6 +415,39 @@ async def get_wazuh_exclusion_rule_response(
415
return SocfortressAiWazuhExclusionRuleResponse(**response_data)
416
417
418
+async def get_velociraptor_artifact_recommendation_response(
419
+ license_key: str,
420
+ request: VelociraptorArtifactRecommendationRequest,
421
+) -> VelociraptorArtifactRecommendationResponse:
422
+ """
423
+ Retrieves Artifact recommendation response from Socfortress Threat Intel API.
424
+
425
+ Args:
426
+ request (VelociraptorArtifactRecommendationRequest): The request object containing the alert data.
427
+ session (AsyncSession): The async session object for making HTTP requests.
428
+
429
+ Returns:
430
+ VelociraptorArtifactRecommendationResponse: The response object containing the artifact recommendation data and success status.
431
+ """
432
+ url = "https://ai.socfortress.co/velociraptor-artifact-recommendation"
433
+
434
+ response_data = await invoke_socfortress_ai_alert_api(license_key, url, request)
435
+
436
+ # If message is `Forbidden`, raise an HTTPException
437
+ if response_data.get("message") == "Forbidden":
438
+ raise HTTPException(
439
+ status_code=403,
440
+ detail="Forbidden access to the Socfortress AI Alert API",
441
+ )
442
+ elif "429" in response_data.get("detail", ""):
443
+ raise HTTPException(
444
+ status_code=429,
445
+ detail="Message is too large. Please try again with a smaller message.",
446
+ )
447
+
448
+ return VelociraptorArtifactRecommendationResponse(**response_data)
449
+
450
+
451
async def get_process_analysis_response(
452
license_key: str,
453
request: SocfortressProcessNameAnalysisRequest,
@@ -524,3 +563,23 @@ async def socfortress_wazuh_exclusion_rule_lookup(
563
license_key=lincense_key,
564
request=request,
565
)
566
+
567
+
568
+async def socfortress_velociraptor_recommendation_lookup(
569
+ lincense_key: str,
570
+ request: VelociraptorArtifactRecommendationRequest,
571
+) -> VelociraptorArtifactRecommendationResponse:
572
+ """
573
+ Performs a AI alert lookup using the Socfortress service.
574
+
575
+ Args:
576
+ request (VelociraptorArtifactRecommendationRequest): The request object containing the IoC to lookup.
577
+ session (AsyncSession): The async session object for making HTTP requests.
578
+
579
+ Returns:
580
+ IoCResponse: The response object containing the threat intelligence information.
581
+ """
582
+ return await get_velociraptor_artifact_recommendation_response(
583
+ license_key=lincense_key,
584
+ request=request,
585
+ )
frontend/package-lock.json
+150
-117
@@ -16,7 +16,7 @@
16
"@shikijs/markdown-it": "^1.24.0",
17
"@tailwindcss/container-queries": "^0.1.1",
18
"@vueuse/core": "^12.0.0",
19
- "axios": "^1.7.8",
19
+ "axios": "^1.7.9",
20
"bytes": "^3.1.2",
21
"colord": "^2.9.3",
22
"dayjs": "^1.11.13",
@@ -28,10 +28,10 @@
28
"js-md5": "^0.8.3",
29
"lodash": "^4.17.21",
30
"mitt": "^3.0.1",
31
- "naive-ui": "^2.40.2",
31
+ "naive-ui": "^2.40.3",
32
"nanoid": "^5.0.9",
33
"password-validator": "^5.3.0",
34
- "pinia": "^2.2.8",
34
+ "pinia": "^2.3.0",
35
"pinia-plugin-persistedstate": "^4.1.3",
36
"secure-ls": "^2.0.0",
37
"shiki": "^1.24.0",
@@ -51,7 +51,7 @@
51
"@clack/prompts": "^0.8.2",
52
"@iconify/vue": "^4.1.2",
53
"@tsconfig/node20": "^20.1.4",
54
- "@types/bytes": "^3.1.4",
54
+ "@types/bytes": "^3.1.5",
55
"@types/file-saver": "^2.0.7",
56
"@types/fs-extra": "^11.0.4",
57
"@types/jsdom": "^21.1.7",
@@ -63,7 +63,7 @@
63
"@vue/test-utils": "^2.4.6",
64
"@vue/tsconfig": "^0.7.0",
65
"autoprefixer": "^10.4.20",
66
- "cypress": "^13.16.0",
66
+ "cypress": "^13.16.1",
67
"depcheck": "^1.4.7",
68
"eslint": "^9.16.0",
69
"flourite": "^1.3.0",
@@ -71,28 +71,28 @@
71
"jsdom": "^25.0.1",
72
"npm-run-all2": "^7.0.1",
73
"postcss": "^8.4.49",
74
- "prettier": "^3.4.1",
74
+ "prettier": "^3.4.2",
75
"prettier-plugin-tailwindcss": "^0.6.9",
76
- "sass": "^1.81.0",
76
+ "sass": "^1.82.0",
77
"start-server-and-test": "^2.0.8",
78
"tailwind-config-viewer": "^2.0.4",
79
- "tailwindcss": "^3.4.15",
79
+ "tailwindcss": "^3.4.16",
80
"taze": "^0.18.0",
81
- "type-fest": "^4.29.0",
81
+ "type-fest": "^4.30.0",
82
"typescript": "~5.6.3",
83
"unplugin-vue-components": "^0.27.5",
84
"vite": "^5.4.11",
85
"vite-bundle-visualizer": "^1.2.1",
86
"vite-plugin-vue-devtools": "^7.6.7",
87
"vite-svg-loader": "^5.1.0",
88
- "vitest": "^2.1.6",
88
+ "vitest": "^2.1.8",
89
"vue-tsc": "^2.1.10"
90
},
91
"engines": {
92
"node": ">=18.0.0"
93
},
94
"optionalDependencies": {
95
- "@rollup/rollup-linux-x64-gnu": "^4.27.4"
95
+ "@rollup/rollup-linux-x64-gnu": "^4.28.0"
96
}
97
},
98
"node_modules/@ajoelp/json-to-formdata": {
@@ -2516,9 +2516,9 @@
2516
]
2517
},
2518
"node_modules/@rollup/rollup-linux-x64-gnu": {
2519
- "version": "4.27.4",
2520
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
2521
- "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
2519
+ "version": "4.28.0",
2520
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz",
2521
+ "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==",
2522
"cpu": [
2523
"x64"
2524
],
@@ -2796,9 +2796,9 @@
2796
"license": "MIT"
2797
},
2798
"node_modules/@types/bytes": {
2799
- "version": "3.1.4",
2800
- "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.4.tgz",
2801
- "integrity": "sha512-A0uYgOj3zNc4hNjHc5lYUfJQ/HVyBXiUMKdXd7ysclaE6k9oJdavQzODHuwjpUu2/boCP8afjQYi8z/GtvNCWA==",
2799
+ "version": "3.1.5",
2800
+ "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.5.tgz",
2801
+ "integrity": "sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ==",
2802
"dev": true,
2803
"license": "MIT"
2804
},
@@ -3117,14 +3117,14 @@
3117
}
3118
},
3119
"node_modules/@typescript-eslint/typescript-estree": {
3120
- "version": "8.16.0",
3121
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz",
3122
- "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==",
3120
+ "version": "8.17.0",
3121
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
3122
+ "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
3123
"dev": true,
3124
"license": "BSD-2-Clause",
3125
"dependencies": {
3126
- "@typescript-eslint/types": "8.16.0",
3127
- "@typescript-eslint/visitor-keys": "8.16.0",
3126
+ "@typescript-eslint/types": "8.17.0",
3127
+ "@typescript-eslint/visitor-keys": "8.17.0",
3128
"debug": "^4.3.4",
3129
"fast-glob": "^3.3.2",
3130
"is-glob": "^4.0.3",
@@ -3145,6 +3145,38 @@
3145
}
3146
}
3147
},
3148
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": {
3149
+ "version": "8.17.0",
3150
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
3151
+ "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
3152
+ "dev": true,
3153
+ "license": "MIT",
3154
+ "engines": {
3155
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3156
+ },
3157
+ "funding": {
3158
+ "type": "opencollective",
3159
+ "url": "https://opencollective.com/typescript-eslint"
3160
+ }
3161
+ },
3162
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": {
3163
+ "version": "8.17.0",
3164
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
3165
+ "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
3166
+ "dev": true,
3167
+ "license": "MIT",
3168
+ "dependencies": {
3169
+ "@typescript-eslint/types": "8.17.0",
3170
+ "eslint-visitor-keys": "^4.2.0"
3171
+ },
3172
+ "engines": {
3173
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3174
+ },
3175
+ "funding": {
3176
+ "type": "opencollective",
3177
+ "url": "https://opencollective.com/typescript-eslint"
3178
+ }
3179
+ },
3180
"node_modules/@typescript-eslint/utils": {
3181
"version": "8.16.0",
3182
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz",
@@ -3252,14 +3284,14 @@
3284
}
3285
},
3286
"node_modules/@vitest/expect": {
3255
- "version": "2.1.6",
3256
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.6.tgz",
3257
- "integrity": "sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==",
3287
+ "version": "2.1.8",
3288
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz",
3289
+ "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==",
3290
"dev": true,
3291
"license": "MIT",
3292
"dependencies": {
3261
- "@vitest/spy": "2.1.6",
3262
- "@vitest/utils": "2.1.6",
3293
+ "@vitest/spy": "2.1.8",
3294
+ "@vitest/utils": "2.1.8",
3295
"chai": "^5.1.2",
3296
"tinyrainbow": "^1.2.0"
3297
},
@@ -3268,13 +3300,13 @@
3300
}
3301
},
3302
"node_modules/@vitest/mocker": {
3271
- "version": "2.1.6",
3272
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.6.tgz",
3273
- "integrity": "sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==",
3303
+ "version": "2.1.8",
3304
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz",
3305
+ "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==",
3306
"dev": true,
3307
"license": "MIT",
3308
"dependencies": {
3277
- "@vitest/spy": "2.1.6",
3309
+ "@vitest/spy": "2.1.8",
3310
"estree-walker": "^3.0.3",
3311
"magic-string": "^0.30.12"
3312
},
@@ -3283,7 +3315,7 @@
3315
},
3316
"peerDependencies": {
3317
"msw": "^2.4.9",
3286
- "vite": "^5.0.0 || ^6.0.0"
3318
+ "vite": "^5.0.0"
3319
},
3320
"peerDependenciesMeta": {
3321
"msw": {
@@ -3305,9 +3337,9 @@
3337
}
3338
},
3339
"node_modules/@vitest/pretty-format": {
3308
- "version": "2.1.6",
3309
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.6.tgz",
3310
- "integrity": "sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==",
3340
+ "version": "2.1.8",
3341
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz",
3342
+ "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==",
3343
"dev": true,
3344
"license": "MIT",
3345
"dependencies": {
@@ -3318,13 +3350,13 @@
3350
}
3351
},
3352
"node_modules/@vitest/runner": {
3321
- "version": "2.1.6",
3322
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.6.tgz",
3323
- "integrity": "sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==",
3353
+ "version": "2.1.8",
3354
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz",
3355
+ "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==",
3356
"dev": true,
3357
"license": "MIT",
3358
"dependencies": {
3327
- "@vitest/utils": "2.1.6",
3359
+ "@vitest/utils": "2.1.8",
3360
"pathe": "^1.1.2"
3361
},
3362
"funding": {
@@ -3332,13 +3364,13 @@
3364
}
3365
},
3366
"node_modules/@vitest/snapshot": {
3335
- "version": "2.1.6",
3336
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.6.tgz",
3337
- "integrity": "sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==",
3367
+ "version": "2.1.8",
3368
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz",
3369
+ "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==",
3370
"dev": true,
3371
"license": "MIT",
3372
"dependencies": {
3341
- "@vitest/pretty-format": "2.1.6",
3373
+ "@vitest/pretty-format": "2.1.8",
3374
"magic-string": "^0.30.12",
3375
"pathe": "^1.1.2"
3376
},
@@ -3347,9 +3379,9 @@
3379
}
3380
},
3381
"node_modules/@vitest/spy": {
3350
- "version": "2.1.6",
3351
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.6.tgz",
3352
- "integrity": "sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==",
3382
+ "version": "2.1.8",
3383
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz",
3384
+ "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==",
3385
"dev": true,
3386
"license": "MIT",
3387
"dependencies": {
@@ -3360,13 +3392,13 @@
3392
}
3393
},
3394
"node_modules/@vitest/utils": {
3363
- "version": "2.1.6",
3364
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.6.tgz",
3365
- "integrity": "sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==",
3395
+ "version": "2.1.8",
3396
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz",
3397
+ "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==",
3398
"dev": true,
3399
"license": "MIT",
3400
"dependencies": {
3369
- "@vitest/pretty-format": "2.1.6",
3401
+ "@vitest/pretty-format": "2.1.8",
3402
"loupe": "^3.1.2",
3403
"tinyrainbow": "^1.2.0"
3404
},
@@ -4121,9 +4153,9 @@
4153
"license": "MIT"
4154
},
4155
"node_modules/axios": {
4124
- "version": "1.7.8",
4125
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
4126
- "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
4156
+ "version": "1.7.9",
4157
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
4158
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
4159
"license": "MIT",
4160
"dependencies": {
4161
"follow-redirects": "^1.15.6",
@@ -5176,9 +5208,9 @@
5208
"license": "MIT"
5209
},
5210
"node_modules/cypress": {
5179
- "version": "13.16.0",
5180
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz",
5181
- "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==",
5211
+ "version": "13.16.1",
5212
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.1.tgz",
5213
+ "integrity": "sha512-17FtCaz0cx7ssWYKXzGB0Vub8xHwpVPr+iPt2fHhLMDhVAPVrplD+rTQsZUsfb19LVBn5iwkEUFjQ1yVVJXsLA==",
5214
"dev": true,
5215
"hasInstallScript": true,
5216
"license": "MIT",
@@ -8994,12 +9026,15 @@
9026
}
9027
},
9028
"node_modules/lilconfig": {
8997
- "version": "2.1.0",
8998
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
8999
- "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
9029
+ "version": "3.1.3",
9030
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
9031
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
9032
"license": "MIT",
9033
"engines": {
9002
- "node": ">=10"
9034
+ "node": ">=14"
9035
+ },
9036
+ "funding": {
9037
+ "url": "https://github.com/sponsors/antonk52"
9038
}
9039
},
9040
"node_modules/lines-and-columns": {
@@ -10412,9 +10447,9 @@
10447
}
10448
},
10449
"node_modules/naive-ui": {
10415
- "version": "2.40.2",
10416
- "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.40.2.tgz",
10417
- "integrity": "sha512-iOpk9/SdAxOPnbFDiN9c0N6vzRJiyC+xgs8rqeaulLvxg0ix39xA3noordy9wyq0YUbqS3ID6jPdld/kW4KSEg==",
10450
+ "version": "2.40.3",
10451
+ "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.40.3.tgz",
10452
+ "integrity": "sha512-TpgYfOg0SNlG4HHhTdFnFcPc1trZiX3r10Pn6biyEgRoi6ZC5qbsY8xgKsqQuG4nWj2PHLT8pPVEkt2pKOlxag==",
10453
"license": "MIT",
10454
"dependencies": {
10455
"@css-render/plugin-bem": "^0.15.14",
@@ -11327,9 +11362,9 @@
11362
}
11363
},
11364
"node_modules/pinia": {
11330
- "version": "2.2.8",
11331
- "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.8.tgz",
11332
- "integrity": "sha512-NRTYy2g+kju5tBRe0oNlriZIbMNvma8ZJrpHsp3qudyiMEA8jMmPPKQ2QMHg0Oc4BkUyQYWagACabrwriCK9HQ==",
11365
+ "version": "2.3.0",
11366
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.0.tgz",
11367
+ "integrity": "sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==",
11368
"license": "MIT",
11369
"dependencies": {
11370
"@vue/devtools-api": "^6.6.3",
@@ -11339,14 +11374,10 @@
11374
"url": "https://github.com/sponsors/posva"
11375
},
11376
"peerDependencies": {
11342
- "@vue/composition-api": "^1.4.0",
11377
"typescript": ">=4.4.4",
11344
- "vue": "^2.6.14 || ^3.5.11"
11378
+ "vue": "^2.7.0 || ^3.5.11"
11379
},
11380
"peerDependenciesMeta": {
11347
- "@vue/composition-api": {
11348
- "optional": true
11349
- },
11381
"typescript": {
11382
"optional": true
11383
}
@@ -11576,18 +11607,6 @@
11607
}
11608
}
11609
},
11579
- "node_modules/postcss-load-config/node_modules/lilconfig": {
11580
- "version": "3.1.2",
11581
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
11582
- "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
11583
- "license": "MIT",
11584
- "engines": {
11585
- "node": ">=14"
11586
- },
11587
- "funding": {
11588
- "url": "https://github.com/sponsors/antonk52"
11589
- }
11590
- },
11610
"node_modules/postcss-load-config/node_modules/yaml": {
11611
"version": "2.6.1",
11612
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
@@ -11673,9 +11692,9 @@
11692
}
11693
},
11694
"node_modules/prettier": {
11676
- "version": "3.4.1",
11677
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz",
11678
- "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==",
11695
+ "version": "3.4.2",
11696
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
11697
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
11698
"dev": true,
11699
"license": "MIT",
11700
"bin": {
@@ -12504,6 +12523,20 @@
12523
"url": "https://github.com/sponsors/jonschlinkert"
12524
}
12525
},
12526
+ "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
12527
+ "version": "4.27.4",
12528
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
12529
+ "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
12530
+ "cpu": [
12531
+ "x64"
12532
+ ],
12533
+ "dev": true,
12534
+ "license": "MIT",
12535
+ "optional": true,
12536
+ "os": [
12537
+ "linux"
12538
+ ]
12539
+ },
12540
"node_modules/rrweb-cssom": {
12541
"version": "0.7.1",
12542
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
@@ -12586,9 +12619,9 @@
12619
"license": "MIT"
12620
},
12621
"node_modules/sass": {
12589
- "version": "1.81.0",
12590
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.81.0.tgz",
12591
- "integrity": "sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==",
12622
+ "version": "1.82.0",
12623
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.82.0.tgz",
12624
+ "integrity": "sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q==",
12625
"dev": true,
12626
"license": "MIT",
12627
"dependencies": {
@@ -13398,9 +13431,9 @@
13431
}
13432
},
13433
"node_modules/tailwindcss": {
13401
- "version": "3.4.15",
13402
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz",
13403
- "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==",
13434
+ "version": "3.4.16",
13435
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz",
13436
+ "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==",
13437
"license": "MIT",
13438
"dependencies": {
13439
"@alloc/quick-lru": "^5.2.0",
@@ -13412,7 +13445,7 @@
13445
"glob-parent": "^6.0.2",
13446
"is-glob": "^4.0.3",
13447
"jiti": "^1.21.6",
13415
- "lilconfig": "^2.1.0",
13448
+ "lilconfig": "^3.1.3",
13449
"micromatch": "^4.0.8",
13450
"normalize-path": "^3.0.0",
13451
"object-hash": "^3.0.0",
@@ -13889,9 +13922,9 @@
13922
}
13923
},
13924
"node_modules/type-fest": {
13892
- "version": "4.29.0",
13893
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.29.0.tgz",
13894
- "integrity": "sha512-RPYt6dKyemXJe7I6oNstcH24myUGSReicxcHTvCLgzm4e0n8y05dGvcGB15/SoPRBmhlMthWQ9pvKyL81ko8nQ==",
13925
+ "version": "4.30.0",
13926
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz",
13927
+ "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==",
13928
"dev": true,
13929
"license": "(MIT OR CC0-1.0)",
13930
"engines": {
@@ -14484,9 +14517,9 @@
14517
}
14518
},
14519
"node_modules/vite-node": {
14487
- "version": "2.1.6",
14488
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.6.tgz",
14489
- "integrity": "sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==",
14520
+ "version": "2.1.8",
14521
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz",
14522
+ "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==",
14523
"dev": true,
14524
"license": "MIT",
14525
"dependencies": {
@@ -14494,13 +14527,13 @@
14527
"debug": "^4.3.7",
14528
"es-module-lexer": "^1.5.4",
14529
"pathe": "^1.1.2",
14497
- "vite": "^5.0.0 || ^6.0.0"
14530
+ "vite": "^5.0.0"
14531
},
14532
"bin": {
14533
"vite-node": "vite-node.mjs"
14534
},
14535
"engines": {
14503
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
14536
+ "node": "^18.0.0 || >=20.0.0"
14537
},
14538
"funding": {
14539
"url": "https://opencollective.com/vitest"
@@ -15251,19 +15284,19 @@
15284
}
15285
},
15286
"node_modules/vitest": {
15254
- "version": "2.1.6",
15255
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.6.tgz",
15256
- "integrity": "sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==",
15287
+ "version": "2.1.8",
15288
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz",
15289
+ "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==",
15290
"dev": true,
15291
"license": "MIT",
15292
"dependencies": {
15260
- "@vitest/expect": "2.1.6",
15261
- "@vitest/mocker": "2.1.6",
15262
- "@vitest/pretty-format": "^2.1.6",
15263
- "@vitest/runner": "2.1.6",
15264
- "@vitest/snapshot": "2.1.6",
15265
- "@vitest/spy": "2.1.6",
15266
- "@vitest/utils": "2.1.6",
15293
+ "@vitest/expect": "2.1.8",
15294
+ "@vitest/mocker": "2.1.8",
15295
+ "@vitest/pretty-format": "^2.1.8",
15296
+ "@vitest/runner": "2.1.8",
15297
+ "@vitest/snapshot": "2.1.8",
15298
+ "@vitest/spy": "2.1.8",
15299
+ "@vitest/utils": "2.1.8",
15300
"chai": "^5.1.2",
15301
"debug": "^4.3.7",
15302
"expect-type": "^1.1.0",
@@ -15274,24 +15307,24 @@
15307
"tinyexec": "^0.3.1",
15308
"tinypool": "^1.0.1",
15309
"tinyrainbow": "^1.2.0",
15277
- "vite": "^5.0.0 || ^6.0.0",
15278
- "vite-node": "2.1.6",
15310
+ "vite": "^5.0.0",
15311
+ "vite-node": "2.1.8",
15312
"why-is-node-running": "^2.3.0"
15313
},
15314
"bin": {
15315
"vitest": "vitest.mjs"
15316
},
15317
"engines": {
15285
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
15318
+ "node": "^18.0.0 || >=20.0.0"
15319
},
15320
"funding": {
15321
"url": "https://opencollective.com/vitest"
15322
},
15323
"peerDependencies": {
15324
"@edge-runtime/vm": "*",
15292
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
15293
- "@vitest/browser": "2.1.6",
15294
- "@vitest/ui": "2.1.6",
15325
+ "@types/node": "^18.0.0 || >=20.0.0",
15326
+ "@vitest/browser": "2.1.8",
15327
+ "@vitest/ui": "2.1.8",
15328
"happy-dom": "*",
15329
"jsdom": "*"
15330
},
frontend/package.json
+14
-14
@@ -44,7 +44,7 @@
44
"@shikijs/markdown-it": "^1.24.0",
45
"@tailwindcss/container-queries": "^0.1.1",
46
"@vueuse/core": "^12.0.0",
47
- "axios": "^1.7.8",
47
+ "axios": "^1.7.9",
48
"bytes": "^3.1.2",
49
"colord": "^2.9.3",
50
"dayjs": "^1.11.13",
@@ -56,10 +56,10 @@
56
"js-md5": "^0.8.3",
57
"lodash": "^4.17.21",
58
"mitt": "^3.0.1",
59
- "naive-ui": "^2.40.2",
59
+ "naive-ui": "^2.40.3",
60
"nanoid": "^5.0.9",
61
"password-validator": "^5.3.0",
62
- "pinia": "^2.2.8",
62
+ "pinia": "^2.3.0",
63
"pinia-plugin-persistedstate": "^4.1.3",
64
"secure-ls": "^2.0.0",
65
"shiki": "^1.24.0",
@@ -75,14 +75,14 @@
75
"vuedraggable": "^4.1.0"
76
},
77
"optionalDependencies": {
78
- "@rollup/rollup-linux-x64-gnu": "^4.27.4"
78
+ "@rollup/rollup-linux-x64-gnu": "^4.28.0"
79
},
80
"devDependencies": {
81
"@antfu/eslint-config": "^3.11.2",
82
"@clack/prompts": "^0.8.2",
83
"@iconify/vue": "^4.1.2",
84
"@tsconfig/node20": "^20.1.4",
85
- "@types/bytes": "^3.1.4",
85
+ "@types/bytes": "^3.1.5",
86
"@types/file-saver": "^2.0.7",
87
"@types/fs-extra": "^11.0.4",
88
"@types/jsdom": "^21.1.7",
@@ -94,7 +94,7 @@
94
"@vue/test-utils": "^2.4.6",
95
"@vue/tsconfig": "^0.7.0",
96
"autoprefixer": "^10.4.20",
97
- "cypress": "^13.16.0",
97
+ "cypress": "^13.16.1",
98
"depcheck": "^1.4.7",
99
"eslint": "^9.16.0",
100
"flourite": "^1.3.0",
@@ -102,28 +102,28 @@
102
"jsdom": "^25.0.1",
103
"npm-run-all2": "^7.0.1",
104
"postcss": "^8.4.49",
105
- "prettier": "^3.4.1",
105
+ "prettier": "^3.4.2",
106
"prettier-plugin-tailwindcss": "^0.6.9",
107
- "sass": "^1.81.0",
107
+ "sass": "^1.82.0",
108
"start-server-and-test": "^2.0.8",
109
"tailwind-config-viewer": "^2.0.4",
110
- "tailwindcss": "^3.4.15",
110
+ "tailwindcss": "^3.4.16",
111
"taze": "^0.18.0",
112
- "type-fest": "^4.29.0",
112
+ "type-fest": "^4.30.0",
113
"typescript": "~5.6.3",
114
"unplugin-vue-components": "^0.27.5",
115
"vite": "^5.4.11",
116
"vite-bundle-visualizer": "^1.2.1",
117
"vite-plugin-vue-devtools": "^7.6.7",
118
"vite-svg-loader": "^5.1.0",
119
- "vitest": "^2.1.6",
119
+ "vitest": "^2.1.8",
120
"vue-tsc": "^2.1.10"
121
},
122
"pnpm": {
123
"overrides": {
124
- "@typescript-eslint/eslint-plugin": "^8.16.0",
124
+ "@typescript-eslint/eslint-plugin": "^8.17.0",
125
"@typescript-eslint/eslint-plugin>eslint": "$eslint",
126
- "@typescript-eslint/parser": "^8.16.0",
126
+ "@typescript-eslint/parser": "^8.17.0",
127
"@typescript-eslint/parser>eslint": "$eslint",
128
"eslint": "$eslint"
129
}
@@ -135,6 +135,6 @@
135
"@typescript-eslint/parser": {
136
"eslint": "^9.16.0"
137
},
138
- "@typescript-eslint/typescript-estree": "^8.16.0"
138
+ "@typescript-eslint/typescript-estree": "^8.17.0"
139
}
140
}
frontend/src/api/endpoints/threatIntel.ts
+19
@@ -1,6 +1,7 @@
1
import type { FlaskBaseResponse } from "@/types/flask.d"
2
import type {
3
AiAnalysisResponse,
4
+ AiVelociraptorArtifactRecommendationResponse,
5
AiWazuhExclusionRuleResponse,
6
EpssScore,
7
EvaluationData,
@@ -46,6 +47,24 @@ export default {
47
}
48
)
49
},
50
+ aiVelociraptorArtifactRecommendation({
51
+ indexId,
52
+ indexName,
53
+ agentId
54
+ }: {
55
+ indexId: string
56
+ indexName: string
57
+ agentId: string
58
+ }) {
59
+ return HttpClient.post<FlaskBaseResponse & AiVelociraptorArtifactRecommendationResponse>(
60
+ `/threat_intel/ai/velociraptor-artifact-recommendation`,
61
+ {
62
+ index_name: indexName,
63
+ index_id: indexId,
64
+ agent_id: agentId
65
+ }
66
+ )
67
+ },
68
virusTotalEnrichment(iocValue: string) {
69
return HttpClient.post<FlaskBaseResponse & VirusTotalResponse>(`/threat_intel/virustotal`, {
70
ioc_value: iocValue
frontend/src/components/incidentManagement/alerts/AlertAsset.vue
+15
-12
@@ -49,19 +49,19 @@
49
content-class="!p-0"
50
:style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(550px, 90vh)', overflow: 'hidden' }"
51
:bordered="false"
52
+ :title="assetNameTruncated"
53
segmented
54
>
54
- <template #header>
55
- <div class="min-h-8 whitespace-nowrap">
56
- {{ assetNameTruncated }}
57
- </div>
58
- </template>
59
- <template #header-extra>
60
- <div class="flex min-h-8 flex-wrap gap-3">
61
- <AIWazuhExclusionRuleButton :index-id="asset.index_id" :index-name="asset.index_name" />
62
- <AIAnalystButton :index-id="asset.index_id" :index-name="asset.index_name" />
63
- </div>
64
- </template>
55
+ <div class="flex flex-wrap justify-end gap-3 p-6">
56
+ <AIVelociraptorArtifactRecommendationButton
57
+ :index-id="asset.index_id"
58
+ :index-name="asset.index_name"
59
+ :agent-id="asset.agent_id"
60
+ />
61
+ <AIWazuhExclusionRuleButton :index-id="asset.index_id" :index-name="asset.index_name" />
62
+ <AIAnalystButton :index-id="asset.index_id" :index-name="asset.index_name" />
63
+ </div>
64
+ <n-divider class="!my-0" />
65
<n-tabs type="line" animated :tabs-padding="24">
66
<n-tab-pane name="Info" tab="Info" display-directive="show">
67
<AlertAssetInfo :asset />
@@ -141,7 +141,7 @@ import CardEntity from "@/components/common/cards/CardEntity.vue"
141
import Icon from "@/components/common/Icon.vue"
142
import { useGoto } from "@/composables/useGoto"
143
import _truncate from "lodash/truncate"
144
-import { NCard, NModal, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
144
+import { NCard, NDivider, NModal, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
145
import { computed, defineAsyncComponent, ref, watch } from "vue"
146
147
const { asset, embedded, badge } = defineProps<{ asset: AlertAsset; embedded?: boolean; badge?: boolean }>()
@@ -153,6 +153,9 @@ const AIAnalystButton = defineAsyncComponent(() => import("@/components/threatIn
153
const AIWazuhExclusionRuleButton = defineAsyncComponent(
154
() => import("@/components/threatIntel/AIWazuhExclusionRuleButton.vue")
155
)
156
+const AIVelociraptorArtifactRecommendationButton = defineAsyncComponent(
157
+ () => import("@/components/threatIntel/AIVelociraptorArtifactRecommendationButton.vue")
158
+)
159
const ThreatIntelProcessEvaluationProvider = defineAsyncComponent(
160
() => import("@/components/threatIntel/ThreatIntelProcessEvaluationProvider.vue")
161
)
frontend/src/components/indices/TopIndices.vue
+1
-1
@@ -34,7 +34,7 @@ function getOptions() {
34
const data = _.chain(indices.value || [])
35
.map(i => {
36
if (typeof i.store_size === "string") {
37
- i.store_size_value = bytes(i.store_size)
37
+ i.store_size_value = bytes(i.store_size) || undefined
38
} else {
39
i.store_size_value = i.store_size
40
}
frontend/src/components/threatIntel/AIVelociraptorArtifactRecommendationButton.vue
new
+119
@@ -0,0 +1,119 @@
1
+<template>
2
+ <div>
3
+ <LicenseFeatureCheck
4
+ feature="SOCFORTRESS AI"
5
+ feedback="tooltip"
6
+ @response="
7
+ (() => {
8
+ licenseChecked = true
9
+ licenseResponse = $event
10
+ })()
11
+ "
12
+ @start-loading="licenseChecking = true"
13
+ @stop-loading="licenseChecking = false"
14
+ >
15
+ <n-button
16
+ :size="size || 'small'"
17
+ ghost
18
+ type="primary"
19
+ :loading="loading || licenseChecking"
20
+ :disabled="!licenseChecked || !licenseResponse"
21
+ @click="analysis()"
22
+ >
23
+ <template #icon>
24
+ <Icon :name="AiIcon" />
25
+ </template>
26
+ <div class="flex items-center gap-2">
27
+ <span>Velociraptor Artifact Recommendation</span>
28
+ <Icon v-if="!licenseResponse && licenseChecked" :name="LockIcon" :size="14" />
29
+ </div>
30
+ </n-button>
31
+ </LicenseFeatureCheck>
32
+
33
+ <n-modal
34
+ v-model:show="showModal"
35
+ preset="card"
36
+ content-class="!p-0"
37
+ :style="{ maxWidth: 'min(710px, 90vw)', minHeight: 'min(500px, 90vh)' }"
38
+ :bordered="false"
39
+ title="Velociraptor Artifact Recommendation"
40
+ segmented
41
+ >
42
+ <div v-if="analysisResponse?.general_thoughts" class="p-6 pb-3">
43
+ {{ analysisResponse.general_thoughts }}
44
+ </div>
45
+ <div v-if="analysisResponse?.artifact_recommendations?.length" class="flex flex-col gap-3 p-6">
46
+ <CardEntity
47
+ v-for="recommendation of analysisResponse.artifact_recommendations"
48
+ :key="recommendation.name + recommendation.explanation + recommendation.description"
49
+ embedded
50
+ >
51
+ <template #header>
52
+ {{ recommendation.name }}
53
+ </template>
54
+ <template #default>
55
+ {{ recommendation.description }}
56
+ </template>
57
+ <template #footer>
58
+ {{ recommendation.explanation }}
59
+ </template>
60
+ </CardEntity>
61
+ </div>
62
+ <n-empty v-else description="No Recommendations found" class="h-48 justify-center" />
63
+ </n-modal>
64
+ </div>
65
+</template>
66
+
67
+<script setup lang="ts">
68
+import type { AiVelociraptorArtifactRecommendationResponse } from "@/types/threatIntel.d"
69
+import type { Size } from "naive-ui/es/button/src/interface"
70
+import Api from "@/api"
71
+import CardEntity from "@/components/common/cards/CardEntity.vue"
72
+import Icon from "@/components/common/Icon.vue"
73
+import LicenseFeatureCheck from "@/components/license/LicenseFeatureCheck.vue"
74
+import { NButton, NEmpty, NModal, useMessage } from "naive-ui"
75
+import { ref } from "vue"
76
+
77
+const { indexName, indexId, agentId, size } = defineProps<{
78
+ indexName: string
79
+ indexId: string
80
+ agentId: string
81
+ size?: Size
82
+}>()
83
+
84
+const LockIcon = "carbon:locked"
85
+const AiIcon = "mage:stars-c"
86
+const showModal = ref<boolean>(false)
87
+const loading = ref<boolean>(false)
88
+const message = useMessage()
89
+const analysisResponse = ref<AiVelociraptorArtifactRecommendationResponse | null>(null)
90
+const licenseChecking = ref(false)
91
+const licenseChecked = ref(false)
92
+const licenseResponse = ref(false)
93
+
94
+function openResponse() {
95
+ showModal.value = true
96
+}
97
+
98
+function analysis() {
99
+ loading.value = true
100
+
101
+ Api.threatIntel
102
+ .aiVelociraptorArtifactRecommendation({ indexName, indexId, agentId })
103
+ .then(res => {
104
+ if (res.data.success) {
105
+ analysisResponse.value = res.data
106
+
107
+ openResponse()
108
+ } else {
109
+ message.warning(res.data?.message || "An error occurred. Please try again later.")
110
+ }
111
+ })
112
+ .catch(err => {
113
+ message.error(err.response?.data?.message || "An error occurred. Please try again later.")
114
+ })
115
+ .finally(() => {
116
+ loading.value = false
117
+ })
118
+}
119
+</script>
frontend/src/components/threatIntel/AIWazuhExclusionRuleButton.vue
+1
-1
@@ -41,7 +41,7 @@
41
>
42
<div
43
v-if="analysisResponse?.wazuh_exclusion_rule || analysisResponse?.wazuh_exclusion_rule_justification"
44
- class="flex flex-col gap-7 !p-7"
44
+ class="flex flex-col gap-7 p-7"
45
>
46
<div v-if="analysisResponse?.wazuh_exclusion_rule">
47
<CodeSource :code="analysisResponse.wazuh_exclusion_rule" :decode="true" />
frontend/src/types/threatIntel.d.ts
+9
@@ -87,6 +87,15 @@ export interface AiWazuhExclusionRuleResponse {
87
wazuh_exclusion_rule_justification: string
88
}
89
90
+export interface AiVelociraptorArtifactRecommendationResponse {
91
+ artifact_recommendations: {
92
+ name: string
93
+ description: string
94
+ explanation: string
95
+ }[]
96
+ general_thoughts: string
97
+}
98
+
99
export interface VirusTotalResponse {
100
data: VirusTotal
101
}
frontend/src/views/AlertsGraylog.vue
-2
@@ -7,5 +7,3 @@
7
<script setup lang="ts">
8
import AlertsGraylogList from "@/components/alerts/AlertsGraylogList.vue"
9
</script>
10
-
11
-<style lang="scss" scoped></style>
frontend/src/views/AlertsOld.vue
-2
@@ -7,5 +7,3 @@
7
<script setup lang="ts">
8
import AlertsList from "@/components/alerts/AlertsList.vue"
9
</script>
10
-
11
-<style lang="scss" scoped></style>
frontend/src/views/Artifacts.vue
-2
@@ -48,5 +48,3 @@ const agents = ref<Agent[]>([])
48
49
const activeTab = ref<string | undefined>(undefined)
50
</script>
51
-
52
-<style lang="scss" scoped></style>
frontend/src/views/graylog/Management.vue
+1
-3
@@ -51,7 +51,7 @@ const Inputs = defineAsyncComponent(() => import("@/components/graylog/Inputs/Li
51
const tabsList = ["messages", "alerts", "events", "streams", "provisioning"]
52
const drawersList = ["inputs"]
53
54
-const activeTab = ref<string | undefined>(tabsList[0])
54
+const activeTab = ref<string>(tabsList[0])
55
const highlightEvent = ref<string | undefined>(undefined)
56
const showInputDrawer = ref(false)
57
const events = ref<EventDefinition[]>([])
@@ -79,5 +79,3 @@ onBeforeMount(() => {
79
}
80
})
81
</script>
82
-
83
-<style lang="scss" scoped></style>