Compare commits
3 Commits
bb562a91ca
...
7c454036c7
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c454036c7 | |||
| 365cf87c90 | |||
| ab8038867a |
1
.playwright-mcp/console-2026-02-20T16-32-53-248Z.log
Normal file
@@ -0,0 +1 @@
|
||||
[ 656ms] [WARNING] No routes matched location "/network-map" @ http://localhost:3000/static/js/main.c0a7ab6d.js:1
|
||||
1
.playwright-mcp/console-2026-02-20T18-16-44-089Z.log
Normal file
@@ -0,0 +1 @@
|
||||
[ 4269ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.6d916bcf.js:1
|
||||
1
.playwright-mcp/console-2026-02-20T18-26-05-692Z.log
Normal file
@@ -0,0 +1 @@
|
||||
[ 496ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.28ae077d.js:1
|
||||
76
.playwright-mcp/console-2026-02-20T18-30-45-724Z.log
Normal file
@@ -0,0 +1,76 @@
|
||||
[ 402ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 60389ms] [ERROR] Failed to load resource: the server responded with a status of 500 (Internal Server Error) @ http://localhost:3000/api/analysis/process-tree?hunt_id=4bb956a4225e45459a464da1146d3cf5:0
|
||||
[ 114742ms] [ERROR] Failed to load resource: the server responded with a status of 500 (Internal Server Error) @ http://localhost:3000/api/analysis/process-tree?hunt_id=4bb956a4225e45459a464da1146d3cf5:0
|
||||
[ 116603ms] [ERROR] Failed to load resource: the server responded with a status of 500 (Internal Server Error) @ http://localhost:3000/api/analysis/process-tree?hunt_id=4bb956a4225e45459a464da1146d3cf5:0
|
||||
[ 362021ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 379006ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 379019ms] [ERROR] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785) @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 379021ms] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
[ 382647ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 386088ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 386343ms] [ERROR] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785) @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 386345ms] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
[ 397704ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 519009ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 519273ms] [ERROR] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785) @ http://localhost:3000/static/js/main.cb47c3a0.js:1
|
||||
[ 519274ms] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.cb47c3a0.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.cb47c3a0.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.cb47c3a0.js:2:228785)
|
||||
1
.playwright-mcp/console-2026-02-20T18-44-41-738Z.log
Normal file
@@ -0,0 +1 @@
|
||||
[ 1803ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.b2c21c5a.js:1
|
||||
48
.playwright-mcp/console-2026-02-20T18-46-54-542Z.log
Normal file
@@ -0,0 +1,48 @@
|
||||
[ 2196ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.0e63bc98.js:1
|
||||
[ 46100ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.0e63bc98.js:1
|
||||
[ 46117ms] [ERROR] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785) @ http://localhost:3000/static/js/main.0e63bc98.js:1
|
||||
[ 46118ms] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785)
|
||||
[ 52506ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.0e63bc98.js:1
|
||||
[ 54912ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.0e63bc98.js:1
|
||||
[ 54928ms] [ERROR] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785) @ http://localhost:3000/static/js/main.0e63bc98.js:1
|
||||
[ 54929ms] NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227378)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at ds (http://localhost:3000/static/js/main.0e63bc98.js:2:227062)
|
||||
at ps (http://localhost:3000/static/js/main.0e63bc98.js:2:227824)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228635)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:229095)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785)
|
||||
at vs (http://localhost:3000/static/js/main.0e63bc98.js:2:228898)
|
||||
at hs (http://localhost:3000/static/js/main.0e63bc98.js:2:228785)
|
||||
7
.playwright-mcp/console-2026-02-20T18-50-52-269Z.log
Normal file
@@ -0,0 +1,7 @@
|
||||
[ 2548ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.c311038e.js:1
|
||||
[ 32912ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.c311038e.js:1
|
||||
[ 55583ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.c311038e.js:1
|
||||
[ 58208ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.c311038e.js:1
|
||||
[ 1168933ms] [ERROR] Failed to load resource: the server responded with a status of 504 (Gateway Time-out) @ http://localhost:3000/api/analysis/llm-analyze:0
|
||||
[ 1477343ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.c311038e.js:1
|
||||
[ 1482908ms] [WARNING] You have set a custom wheel sensitivity. This will make your app zoom unnaturally when using mainstream mice. You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine. @ http://localhost:3000/static/js/main.c311038e.js:1
|
||||
7
.playwright-mcp/console-2026-02-20T19-16-43-503Z.log
Normal file
@@ -0,0 +1,7 @@
|
||||
[ 9612ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/:0
|
||||
[ 17464ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/enterprise:0
|
||||
[ 20742ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/enterprise:0
|
||||
[ 53258ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/pricing:0
|
||||
[ 59240ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/features/copilot#pricing:0
|
||||
[ 67668ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/features/spark?utm_source=web-copilot-ce-cta&utm_campaign=spark-launch-sep-2025:0
|
||||
[ 72166ms] [WARNING] The resource https://github.githubassets.com/assets/mona-sans-14595085164a.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://github.com/features/spark?utm_source=web-copilot-ce-cta&utm_campaign=spark-launch-sep-2025:0
|
||||
3923
.playwright-mcp/console-2026-02-20T19-27-06-976Z.log
Normal file
BIN
.playwright-mcp/page-2026-02-20T16-33-40-311Z.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
.playwright-mcp/page-2026-02-20T16-34-14-809Z.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
.playwright-mcp/page-2026-02-20T16-38-20-099Z.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
.playwright-mcp/page-2026-02-20T16-42-11-611Z.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-17-19-668Z.png
Normal file
|
After Width: | Height: | Size: 558 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-26-49-357Z.png
Normal file
|
After Width: | Height: | Size: 607 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-31-29-013Z.png
Normal file
|
After Width: | Height: | Size: 341 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-44-58-287Z.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-45-12-934Z.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-47-14-660Z.png
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
.playwright-mcp/page-2026-02-20T18-51-32-804Z.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,72 @@
|
||||
"""add cases and activity logs
|
||||
|
||||
Revision ID: a3b1c2d4e5f6
|
||||
Revises: 98ab619418bc
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision: str = "a3b1c2d4e5f6"
|
||||
down_revision: Union[str, None] = "98ab619418bc"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"cases",
|
||||
sa.Column("id", sa.String(32), primary_key=True),
|
||||
sa.Column("title", sa.String(512), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("severity", sa.String(16), server_default="medium"),
|
||||
sa.Column("tlp", sa.String(16), server_default="amber"),
|
||||
sa.Column("pap", sa.String(16), server_default="amber"),
|
||||
sa.Column("status", sa.String(24), server_default="open"),
|
||||
sa.Column("priority", sa.Integer, server_default="2"),
|
||||
sa.Column("assignee", sa.String(128), nullable=True),
|
||||
sa.Column("tags", sa.JSON, nullable=True),
|
||||
sa.Column("hunt_id", sa.String(32), sa.ForeignKey("hunts.id"), nullable=True),
|
||||
sa.Column("owner_id", sa.String(32), sa.ForeignKey("users.id"), nullable=True),
|
||||
sa.Column("mitre_techniques", sa.JSON, nullable=True),
|
||||
sa.Column("iocs", sa.JSON, nullable=True),
|
||||
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_cases_hunt", "cases", ["hunt_id"])
|
||||
op.create_index("ix_cases_status", "cases", ["status"])
|
||||
|
||||
op.create_table(
|
||||
"case_tasks",
|
||||
sa.Column("id", sa.String(32), primary_key=True),
|
||||
sa.Column("case_id", sa.String(32), sa.ForeignKey("cases.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("title", sa.String(512), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("status", sa.String(24), server_default="todo"),
|
||||
sa.Column("assignee", sa.String(128), nullable=True),
|
||||
sa.Column("order", sa.Integer, server_default="0"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_case_tasks_case", "case_tasks", ["case_id"])
|
||||
|
||||
op.create_table(
|
||||
"activity_logs",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("entity_type", sa.String(32), nullable=False),
|
||||
sa.Column("entity_id", sa.String(32), nullable=False),
|
||||
sa.Column("action", sa.String(64), nullable=False),
|
||||
sa.Column("details", sa.JSON, nullable=True),
|
||||
sa.Column("user_id", sa.String(32), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
|
||||
)
|
||||
op.create_index("ix_activity_entity", "activity_logs", ["entity_type", "entity_id"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("activity_logs")
|
||||
op.drop_table("case_tasks")
|
||||
op.drop_table("cases")
|
||||
@@ -0,0 +1,63 @@
|
||||
"""add alerts and alert_rules tables
|
||||
|
||||
Revision ID: b4c2d3e5f6a7
|
||||
Revises: a3b1c2d4e5f6
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers
|
||||
revision: str = "b4c2d3e5f6a7"
|
||||
down_revision: Union[str, None] = "a3b1c2d4e5f6"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"alerts",
|
||||
sa.Column("id", sa.String(32), primary_key=True),
|
||||
sa.Column("title", sa.String(512), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("severity", sa.String(16), server_default="medium"),
|
||||
sa.Column("status", sa.String(24), server_default="new"),
|
||||
sa.Column("analyzer", sa.String(64), nullable=False),
|
||||
sa.Column("score", sa.Float, server_default="0"),
|
||||
sa.Column("evidence", sa.JSON, nullable=True),
|
||||
sa.Column("mitre_technique", sa.String(32), nullable=True),
|
||||
sa.Column("tags", sa.JSON, nullable=True),
|
||||
sa.Column("hunt_id", sa.String(32), sa.ForeignKey("hunts.id"), nullable=True),
|
||||
sa.Column("dataset_id", sa.String(32), sa.ForeignKey("datasets.id"), nullable=True),
|
||||
sa.Column("case_id", sa.String(32), sa.ForeignKey("cases.id"), nullable=True),
|
||||
sa.Column("assignee", sa.String(128), nullable=True),
|
||||
sa.Column("acknowledged_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_alerts_severity", "alerts", ["severity"])
|
||||
op.create_index("ix_alerts_status", "alerts", ["status"])
|
||||
op.create_index("ix_alerts_hunt", "alerts", ["hunt_id"])
|
||||
op.create_index("ix_alerts_dataset", "alerts", ["dataset_id"])
|
||||
|
||||
op.create_table(
|
||||
"alert_rules",
|
||||
sa.Column("id", sa.String(32), primary_key=True),
|
||||
sa.Column("name", sa.String(256), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("analyzer", sa.String(64), nullable=False),
|
||||
sa.Column("config", sa.JSON, nullable=True),
|
||||
sa.Column("severity_override", sa.String(16), nullable=True),
|
||||
sa.Column("enabled", sa.Boolean, server_default=sa.text("1")),
|
||||
sa.Column("hunt_id", sa.String(32), sa.ForeignKey("hunts.id"), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_alert_rules_analyzer", "alert_rules", ["analyzer"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("alert_rules")
|
||||
op.drop_table("alerts")
|
||||
@@ -0,0 +1,54 @@
|
||||
"""add notebooks and playbook_runs tables
|
||||
|
||||
Revision ID: c5d3e4f6a7b8
|
||||
Revises: b4c2d3e5f6a7
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision: str = "c5d3e4f6a7b8"
|
||||
down_revision: Union[str, None] = "b4c2d3e5f6a7"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"notebooks",
|
||||
sa.Column("id", sa.String(32), primary_key=True),
|
||||
sa.Column("title", sa.String(512), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("cells", sa.JSON, nullable=True),
|
||||
sa.Column("hunt_id", sa.String(32), sa.ForeignKey("hunts.id"), nullable=True),
|
||||
sa.Column("case_id", sa.String(32), sa.ForeignKey("cases.id"), nullable=True),
|
||||
sa.Column("owner_id", sa.String(32), sa.ForeignKey("users.id"), nullable=True),
|
||||
sa.Column("tags", sa.JSON, nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("ix_notebooks_hunt", "notebooks", ["hunt_id"])
|
||||
|
||||
op.create_table(
|
||||
"playbook_runs",
|
||||
sa.Column("id", sa.String(32), primary_key=True),
|
||||
sa.Column("playbook_name", sa.String(256), nullable=False),
|
||||
sa.Column("status", sa.String(24), server_default="in-progress"),
|
||||
sa.Column("current_step", sa.Integer, server_default="1"),
|
||||
sa.Column("total_steps", sa.Integer, server_default="0"),
|
||||
sa.Column("step_results", sa.JSON, nullable=True),
|
||||
sa.Column("hunt_id", sa.String(32), sa.ForeignKey("hunts.id"), nullable=True),
|
||||
sa.Column("case_id", sa.String(32), sa.ForeignKey("cases.id"), nullable=True),
|
||||
sa.Column("started_by", sa.String(128), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.create_index("ix_playbook_runs_hunt", "playbook_runs", ["hunt_id"])
|
||||
op.create_index("ix_playbook_runs_status", "playbook_runs", ["status"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("playbook_runs")
|
||||
op.drop_table("notebooks")
|
||||
404
backend/app/api/routes/alerts.py
Normal file
@@ -0,0 +1,404 @@
|
||||
"""API routes for alerts — CRUD, analyze triggers, and alert rules."""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select, func, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db import get_db
|
||||
from app.db.models import Alert, AlertRule, _new_id, _utcnow
|
||||
from app.db.repositories.datasets import DatasetRepository
|
||||
from app.services.analyzers import (
|
||||
get_available_analyzers,
|
||||
get_analyzer,
|
||||
run_all_analyzers,
|
||||
AlertCandidate,
|
||||
)
|
||||
from app.services.process_tree import _fetch_rows
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/alerts", tags=["alerts"])
|
||||
|
||||
|
||||
# ── Pydantic models ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class AlertUpdate(BaseModel):
|
||||
status: Optional[str] = None
|
||||
severity: Optional[str] = None
|
||||
assignee: Optional[str] = None
|
||||
case_id: Optional[str] = None
|
||||
tags: Optional[list[str]] = None
|
||||
|
||||
|
||||
class RuleCreate(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
analyzer: str
|
||||
config: Optional[dict] = None
|
||||
severity_override: Optional[str] = None
|
||||
enabled: bool = True
|
||||
hunt_id: Optional[str] = None
|
||||
|
||||
|
||||
class RuleUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
config: Optional[dict] = None
|
||||
severity_override: Optional[str] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class AnalyzeRequest(BaseModel):
|
||||
dataset_id: Optional[str] = None
|
||||
hunt_id: Optional[str] = None
|
||||
analyzers: Optional[list[str]] = None # None = run all
|
||||
config: Optional[dict] = None
|
||||
auto_create: bool = True # automatically persist alerts
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _alert_to_dict(a: Alert) -> dict:
|
||||
return {
|
||||
"id": a.id,
|
||||
"title": a.title,
|
||||
"description": a.description,
|
||||
"severity": a.severity,
|
||||
"status": a.status,
|
||||
"analyzer": a.analyzer,
|
||||
"score": a.score,
|
||||
"evidence": a.evidence or [],
|
||||
"mitre_technique": a.mitre_technique,
|
||||
"tags": a.tags or [],
|
||||
"hunt_id": a.hunt_id,
|
||||
"dataset_id": a.dataset_id,
|
||||
"case_id": a.case_id,
|
||||
"assignee": a.assignee,
|
||||
"acknowledged_at": a.acknowledged_at.isoformat() if a.acknowledged_at else None,
|
||||
"resolved_at": a.resolved_at.isoformat() if a.resolved_at else None,
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
"updated_at": a.updated_at.isoformat() if a.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
def _rule_to_dict(r: AlertRule) -> dict:
|
||||
return {
|
||||
"id": r.id,
|
||||
"name": r.name,
|
||||
"description": r.description,
|
||||
"analyzer": r.analyzer,
|
||||
"config": r.config,
|
||||
"severity_override": r.severity_override,
|
||||
"enabled": r.enabled,
|
||||
"hunt_id": r.hunt_id,
|
||||
"created_at": r.created_at.isoformat() if r.created_at else None,
|
||||
"updated_at": r.updated_at.isoformat() if r.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
# ── Alert CRUD ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("", summary="List alerts")
|
||||
async def list_alerts(
|
||||
status: str | None = Query(None),
|
||||
severity: str | None = Query(None),
|
||||
analyzer: str | None = Query(None),
|
||||
hunt_id: str | None = Query(None),
|
||||
dataset_id: str | None = Query(None),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
stmt = select(Alert)
|
||||
count_stmt = select(func.count(Alert.id))
|
||||
if status:
|
||||
stmt = stmt.where(Alert.status == status)
|
||||
count_stmt = count_stmt.where(Alert.status == status)
|
||||
if severity:
|
||||
stmt = stmt.where(Alert.severity == severity)
|
||||
count_stmt = count_stmt.where(Alert.severity == severity)
|
||||
if analyzer:
|
||||
stmt = stmt.where(Alert.analyzer == analyzer)
|
||||
count_stmt = count_stmt.where(Alert.analyzer == analyzer)
|
||||
if hunt_id:
|
||||
stmt = stmt.where(Alert.hunt_id == hunt_id)
|
||||
count_stmt = count_stmt.where(Alert.hunt_id == hunt_id)
|
||||
if dataset_id:
|
||||
stmt = stmt.where(Alert.dataset_id == dataset_id)
|
||||
count_stmt = count_stmt.where(Alert.dataset_id == dataset_id)
|
||||
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
results = (await db.execute(
|
||||
stmt.order_by(desc(Alert.score), desc(Alert.created_at)).offset(offset).limit(limit)
|
||||
)).scalars().all()
|
||||
|
||||
return {"alerts": [_alert_to_dict(a) for a in results], "total": total}
|
||||
|
||||
|
||||
@router.get("/stats", summary="Alert statistics dashboard")
|
||||
async def alert_stats(
|
||||
hunt_id: str | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Return aggregated alert statistics."""
|
||||
base = select(Alert)
|
||||
if hunt_id:
|
||||
base = base.where(Alert.hunt_id == hunt_id)
|
||||
|
||||
# Severity breakdown
|
||||
sev_stmt = select(Alert.severity, func.count(Alert.id)).group_by(Alert.severity)
|
||||
if hunt_id:
|
||||
sev_stmt = sev_stmt.where(Alert.hunt_id == hunt_id)
|
||||
sev_rows = (await db.execute(sev_stmt)).all()
|
||||
severity_counts = {s: c for s, c in sev_rows}
|
||||
|
||||
# Status breakdown
|
||||
status_stmt = select(Alert.status, func.count(Alert.id)).group_by(Alert.status)
|
||||
if hunt_id:
|
||||
status_stmt = status_stmt.where(Alert.hunt_id == hunt_id)
|
||||
status_rows = (await db.execute(status_stmt)).all()
|
||||
status_counts = {s: c for s, c in status_rows}
|
||||
|
||||
# Analyzer breakdown
|
||||
analyzer_stmt = select(Alert.analyzer, func.count(Alert.id)).group_by(Alert.analyzer)
|
||||
if hunt_id:
|
||||
analyzer_stmt = analyzer_stmt.where(Alert.hunt_id == hunt_id)
|
||||
analyzer_rows = (await db.execute(analyzer_stmt)).all()
|
||||
analyzer_counts = {a: c for a, c in analyzer_rows}
|
||||
|
||||
# Top MITRE techniques
|
||||
mitre_stmt = (
|
||||
select(Alert.mitre_technique, func.count(Alert.id))
|
||||
.where(Alert.mitre_technique.isnot(None))
|
||||
.group_by(Alert.mitre_technique)
|
||||
.order_by(desc(func.count(Alert.id)))
|
||||
.limit(10)
|
||||
)
|
||||
if hunt_id:
|
||||
mitre_stmt = mitre_stmt.where(Alert.hunt_id == hunt_id)
|
||||
mitre_rows = (await db.execute(mitre_stmt)).all()
|
||||
top_mitre = [{"technique": t, "count": c} for t, c in mitre_rows]
|
||||
|
||||
total = sum(severity_counts.values())
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"severity_counts": severity_counts,
|
||||
"status_counts": status_counts,
|
||||
"analyzer_counts": analyzer_counts,
|
||||
"top_mitre": top_mitre,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{alert_id}", summary="Get alert detail")
|
||||
async def get_alert(alert_id: str, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.get(Alert, alert_id)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Alert not found")
|
||||
return _alert_to_dict(result)
|
||||
|
||||
|
||||
@router.put("/{alert_id}", summary="Update alert (status, assignee, etc.)")
|
||||
async def update_alert(
|
||||
alert_id: str, body: AlertUpdate, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
alert = await db.get(Alert, alert_id)
|
||||
if not alert:
|
||||
raise HTTPException(status_code=404, detail="Alert not found")
|
||||
|
||||
if body.status is not None:
|
||||
alert.status = body.status
|
||||
if body.status == "acknowledged" and not alert.acknowledged_at:
|
||||
alert.acknowledged_at = _utcnow()
|
||||
if body.status in ("resolved", "false-positive") and not alert.resolved_at:
|
||||
alert.resolved_at = _utcnow()
|
||||
if body.severity is not None:
|
||||
alert.severity = body.severity
|
||||
if body.assignee is not None:
|
||||
alert.assignee = body.assignee
|
||||
if body.case_id is not None:
|
||||
alert.case_id = body.case_id
|
||||
if body.tags is not None:
|
||||
alert.tags = body.tags
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(alert)
|
||||
return _alert_to_dict(alert)
|
||||
|
||||
|
||||
@router.delete("/{alert_id}", summary="Delete alert")
|
||||
async def delete_alert(alert_id: str, db: AsyncSession = Depends(get_db)):
|
||||
alert = await db.get(Alert, alert_id)
|
||||
if not alert:
|
||||
raise HTTPException(status_code=404, detail="Alert not found")
|
||||
await db.delete(alert)
|
||||
await db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ── Bulk operations ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.post("/bulk-update", summary="Bulk update alert statuses")
|
||||
async def bulk_update_alerts(
|
||||
alert_ids: list[str],
|
||||
status: str = Query(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
updated = 0
|
||||
for aid in alert_ids:
|
||||
alert = await db.get(Alert, aid)
|
||||
if alert:
|
||||
alert.status = status
|
||||
if status == "acknowledged" and not alert.acknowledged_at:
|
||||
alert.acknowledged_at = _utcnow()
|
||||
if status in ("resolved", "false-positive") and not alert.resolved_at:
|
||||
alert.resolved_at = _utcnow()
|
||||
updated += 1
|
||||
await db.commit()
|
||||
return {"updated": updated}
|
||||
|
||||
|
||||
# ── Run Analyzers ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/analyzers/list", summary="List available analyzers")
|
||||
async def list_analyzers():
|
||||
return {"analyzers": get_available_analyzers()}
|
||||
|
||||
|
||||
@router.post("/analyze", summary="Run analyzers on a dataset/hunt and optionally create alerts")
|
||||
async def run_analysis(
|
||||
request: AnalyzeRequest, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
if not request.dataset_id and not request.hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
|
||||
# Load rows
|
||||
rows_objs = await _fetch_rows(
|
||||
db, dataset_id=request.dataset_id, hunt_id=request.hunt_id, limit=10000,
|
||||
)
|
||||
if not rows_objs:
|
||||
raise HTTPException(status_code=404, detail="No rows found")
|
||||
|
||||
rows = [r.normalized_data or r.data for r in rows_objs]
|
||||
|
||||
# Run analyzers
|
||||
candidates = await run_all_analyzers(rows, enabled=request.analyzers, config=request.config)
|
||||
|
||||
created_alerts: list[dict] = []
|
||||
if request.auto_create and candidates:
|
||||
for c in candidates:
|
||||
alert = Alert(
|
||||
id=_new_id(),
|
||||
title=c.title,
|
||||
description=c.description,
|
||||
severity=c.severity,
|
||||
analyzer=c.analyzer,
|
||||
score=c.score,
|
||||
evidence=c.evidence,
|
||||
mitre_technique=c.mitre_technique,
|
||||
tags=c.tags,
|
||||
hunt_id=request.hunt_id,
|
||||
dataset_id=request.dataset_id,
|
||||
)
|
||||
db.add(alert)
|
||||
created_alerts.append(_alert_to_dict(alert))
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"candidates_found": len(candidates),
|
||||
"alerts_created": len(created_alerts),
|
||||
"alerts": created_alerts,
|
||||
"summary": {
|
||||
"by_severity": _count_by(candidates, "severity"),
|
||||
"by_analyzer": _count_by(candidates, "analyzer"),
|
||||
"rows_analyzed": len(rows),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _count_by(items: list[AlertCandidate], attr: str) -> dict[str, int]:
|
||||
counts: dict[str, int] = {}
|
||||
for item in items:
|
||||
key = getattr(item, attr, "unknown")
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
return counts
|
||||
|
||||
|
||||
# ── Alert Rules CRUD ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/rules/list", summary="List alert rules")
|
||||
async def list_rules(
|
||||
enabled: bool | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
stmt = select(AlertRule)
|
||||
if enabled is not None:
|
||||
stmt = stmt.where(AlertRule.enabled == enabled)
|
||||
results = (await db.execute(stmt.order_by(AlertRule.created_at))).scalars().all()
|
||||
return {"rules": [_rule_to_dict(r) for r in results]}
|
||||
|
||||
|
||||
@router.post("/rules", summary="Create alert rule")
|
||||
async def create_rule(body: RuleCreate, db: AsyncSession = Depends(get_db)):
|
||||
# Validate analyzer exists
|
||||
if not get_analyzer(body.analyzer):
|
||||
raise HTTPException(status_code=400, detail=f"Unknown analyzer: {body.analyzer}")
|
||||
|
||||
rule = AlertRule(
|
||||
id=_new_id(),
|
||||
name=body.name,
|
||||
description=body.description,
|
||||
analyzer=body.analyzer,
|
||||
config=body.config,
|
||||
severity_override=body.severity_override,
|
||||
enabled=body.enabled,
|
||||
hunt_id=body.hunt_id,
|
||||
)
|
||||
db.add(rule)
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
return _rule_to_dict(rule)
|
||||
|
||||
|
||||
@router.put("/rules/{rule_id}", summary="Update alert rule")
|
||||
async def update_rule(
|
||||
rule_id: str, body: RuleUpdate, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
rule = await db.get(AlertRule, rule_id)
|
||||
if not rule:
|
||||
raise HTTPException(status_code=404, detail="Rule not found")
|
||||
|
||||
if body.name is not None:
|
||||
rule.name = body.name
|
||||
if body.description is not None:
|
||||
rule.description = body.description
|
||||
if body.config is not None:
|
||||
rule.config = body.config
|
||||
if body.severity_override is not None:
|
||||
rule.severity_override = body.severity_override
|
||||
if body.enabled is not None:
|
||||
rule.enabled = body.enabled
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
return _rule_to_dict(rule)
|
||||
|
||||
|
||||
@router.delete("/rules/{rule_id}", summary="Delete alert rule")
|
||||
async def delete_rule(rule_id: str, db: AsyncSession = Depends(get_db)):
|
||||
rule = await db.get(AlertRule, rule_id)
|
||||
if not rule:
|
||||
raise HTTPException(status_code=404, detail="Rule not found")
|
||||
await db.delete(rule)
|
||||
await db.commit()
|
||||
return {"ok": True}
|
||||
@@ -1,402 +1,295 @@
|
||||
"""Analysis API routes - triage, host profiles, reports, IOC extraction,
|
||||
host grouping, anomaly detection, data query (SSE), and job management."""
|
||||
|
||||
from __future__ import annotations
|
||||
"""API routes for process trees, storyline graphs, risk scoring, LLM analysis, timeline, and field stats."""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import select
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db import get_db
|
||||
from app.db.models import HostProfile, HuntReport, TriageResult
|
||||
from app.db.repositories.datasets import DatasetRepository
|
||||
from app.services.process_tree import (
|
||||
build_process_tree,
|
||||
build_storyline,
|
||||
compute_risk_scores,
|
||||
_fetch_rows,
|
||||
)
|
||||
from app.services.llm_analysis import (
|
||||
AnalysisRequest,
|
||||
AnalysisResult,
|
||||
run_llm_analysis,
|
||||
)
|
||||
from app.services.timeline import (
|
||||
build_timeline_bins,
|
||||
compute_field_stats,
|
||||
search_rows,
|
||||
)
|
||||
from app.services.mitre import (
|
||||
map_to_attack,
|
||||
build_knowledge_graph,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/analysis", tags=["analysis"])
|
||||
|
||||
|
||||
# --- Response models ---
|
||||
|
||||
class TriageResultResponse(BaseModel):
|
||||
id: str
|
||||
dataset_id: str
|
||||
row_start: int
|
||||
row_end: int
|
||||
risk_score: float
|
||||
verdict: str
|
||||
findings: list | None = None
|
||||
suspicious_indicators: list | None = None
|
||||
mitre_techniques: list | None = None
|
||||
model_used: str | None = None
|
||||
node_used: str | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
# ── Response models ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
class HostProfileResponse(BaseModel):
|
||||
id: str
|
||||
hunt_id: str
|
||||
class ProcessTreeResponse(BaseModel):
|
||||
trees: list[dict] = Field(default_factory=list)
|
||||
total_processes: int = 0
|
||||
|
||||
|
||||
class StorylineResponse(BaseModel):
|
||||
nodes: list[dict] = Field(default_factory=list)
|
||||
edges: list[dict] = Field(default_factory=list)
|
||||
summary: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class RiskHostEntry(BaseModel):
|
||||
hostname: str
|
||||
fqdn: str | None = None
|
||||
risk_score: float
|
||||
risk_level: str
|
||||
artifact_summary: dict | None = None
|
||||
timeline_summary: str | None = None
|
||||
suspicious_findings: list | None = None
|
||||
mitre_techniques: list | None = None
|
||||
llm_analysis: str | None = None
|
||||
model_used: str | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
score: int = 0
|
||||
signals: list[str] = Field(default_factory=list)
|
||||
event_count: int = 0
|
||||
process_count: int = 0
|
||||
network_count: int = 0
|
||||
file_count: int = 0
|
||||
|
||||
|
||||
class HuntReportResponse(BaseModel):
|
||||
id: str
|
||||
hunt_id: str
|
||||
status: str
|
||||
exec_summary: str | None = None
|
||||
full_report: str | None = None
|
||||
findings: list | None = None
|
||||
recommendations: list | None = None
|
||||
mitre_mapping: dict | None = None
|
||||
ioc_table: list | None = None
|
||||
host_risk_summary: list | None = None
|
||||
models_used: list | None = None
|
||||
generation_time_ms: int | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
class RiskSummaryResponse(BaseModel):
|
||||
hosts: list[RiskHostEntry] = Field(default_factory=list)
|
||||
overall_score: int = 0
|
||||
total_events: int = 0
|
||||
severity_breakdown: dict[str, int] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class QueryRequest(BaseModel):
|
||||
question: str
|
||||
mode: str = "quick" # quick or deep
|
||||
# ── Routes ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
# --- Triage endpoints ---
|
||||
|
||||
@router.get("/triage/{dataset_id}", response_model=list[TriageResultResponse])
|
||||
async def get_triage_results(
|
||||
dataset_id: str,
|
||||
min_risk: float = Query(0.0, ge=0.0, le=10.0),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(TriageResult)
|
||||
.where(TriageResult.dataset_id == dataset_id)
|
||||
.where(TriageResult.risk_score >= min_risk)
|
||||
.order_by(TriageResult.risk_score.desc())
|
||||
@router.get(
|
||||
"/process-tree",
|
||||
response_model=ProcessTreeResponse,
|
||||
summary="Build process tree from dataset rows",
|
||||
description=(
|
||||
"Extracts parent→child process relationships from dataset rows "
|
||||
"and returns a hierarchical forest of process nodes."
|
||||
),
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@router.post("/triage/{dataset_id}")
|
||||
async def trigger_triage(
|
||||
dataset_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
):
|
||||
async def _run():
|
||||
from app.services.triage import triage_dataset
|
||||
await triage_dataset(dataset_id)
|
||||
|
||||
background_tasks.add_task(_run)
|
||||
return {"status": "triage_started", "dataset_id": dataset_id}
|
||||
|
||||
|
||||
# --- Host profile endpoints ---
|
||||
|
||||
@router.get("/profiles/{hunt_id}", response_model=list[HostProfileResponse])
|
||||
async def get_host_profiles(
|
||||
hunt_id: str,
|
||||
min_risk: float = Query(0.0, ge=0.0, le=10.0),
|
||||
async def get_process_tree(
|
||||
dataset_id: str | None = Query(None, description="Dataset ID"),
|
||||
hunt_id: str | None = Query(None, description="Hunt ID (scans all datasets in hunt)"),
|
||||
hostname: str | None = Query(None, description="Filter by hostname"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(HostProfile)
|
||||
.where(HostProfile.hunt_id == hunt_id)
|
||||
.where(HostProfile.risk_score >= min_risk)
|
||||
.order_by(HostProfile.risk_score.desc())
|
||||
"""Return process tree(s) for a dataset or hunt."""
|
||||
if not dataset_id and not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
|
||||
trees = await build_process_tree(
|
||||
db, dataset_id=dataset_id, hunt_id=hunt_id, hostname_filter=hostname,
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
# Count total processes recursively
|
||||
def _count(node: dict) -> int:
|
||||
return 1 + sum(_count(c) for c in node.get("children", []))
|
||||
|
||||
total = sum(_count(t) for t in trees)
|
||||
|
||||
return ProcessTreeResponse(trees=trees, total_processes=total)
|
||||
|
||||
|
||||
@router.post("/profiles/{hunt_id}")
|
||||
async def trigger_all_profiles(
|
||||
hunt_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
):
|
||||
async def _run():
|
||||
from app.services.host_profiler import profile_all_hosts
|
||||
await profile_all_hosts(hunt_id)
|
||||
|
||||
background_tasks.add_task(_run)
|
||||
return {"status": "profiling_started", "hunt_id": hunt_id}
|
||||
|
||||
|
||||
@router.post("/profiles/{hunt_id}/{hostname}")
|
||||
async def trigger_single_profile(
|
||||
hunt_id: str,
|
||||
hostname: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
):
|
||||
async def _run():
|
||||
from app.services.host_profiler import profile_host
|
||||
await profile_host(hunt_id, hostname)
|
||||
|
||||
background_tasks.add_task(_run)
|
||||
return {"status": "profiling_started", "hunt_id": hunt_id, "hostname": hostname}
|
||||
|
||||
|
||||
# --- Report endpoints ---
|
||||
|
||||
@router.get("/reports/{hunt_id}", response_model=list[HuntReportResponse])
|
||||
async def list_reports(
|
||||
hunt_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(HuntReport)
|
||||
.where(HuntReport.hunt_id == hunt_id)
|
||||
.order_by(HuntReport.created_at.desc())
|
||||
@router.get(
|
||||
"/storyline",
|
||||
response_model=StorylineResponse,
|
||||
summary="Build CrowdStrike-style storyline attack graph",
|
||||
description=(
|
||||
"Creates a Cytoscape-compatible graph of events connected by "
|
||||
"process lineage (spawned) and temporal sequence within each host."
|
||||
),
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@router.get("/reports/{hunt_id}/{report_id}", response_model=HuntReportResponse)
|
||||
async def get_report(
|
||||
hunt_id: str,
|
||||
report_id: str,
|
||||
async def get_storyline(
|
||||
dataset_id: str | None = Query(None, description="Dataset ID"),
|
||||
hunt_id: str | None = Query(None, description="Hunt ID (scans all datasets in hunt)"),
|
||||
hostname: str | None = Query(None, description="Filter by hostname"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(HuntReport)
|
||||
.where(HuntReport.id == report_id)
|
||||
.where(HuntReport.hunt_id == hunt_id)
|
||||
"""Return a storyline graph for a dataset or hunt."""
|
||||
if not dataset_id and not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
|
||||
result = await build_storyline(
|
||||
db, dataset_id=dataset_id, hunt_id=hunt_id, hostname_filter=hostname,
|
||||
)
|
||||
report = result.scalar_one_or_none()
|
||||
if not report:
|
||||
raise HTTPException(status_code=404, detail="Report not found")
|
||||
return report
|
||||
|
||||
return StorylineResponse(**result)
|
||||
|
||||
|
||||
@router.post("/reports/{hunt_id}/generate")
|
||||
async def trigger_report(
|
||||
hunt_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
):
|
||||
async def _run():
|
||||
from app.services.report_generator import generate_report
|
||||
await generate_report(hunt_id)
|
||||
|
||||
background_tasks.add_task(_run)
|
||||
return {"status": "report_generation_started", "hunt_id": hunt_id}
|
||||
|
||||
|
||||
# --- IOC extraction endpoints ---
|
||||
|
||||
@router.get("/iocs/{dataset_id}")
|
||||
async def extract_iocs(
|
||||
dataset_id: str,
|
||||
max_rows: int = Query(5000, ge=1, le=50000),
|
||||
@router.get(
|
||||
"/risk-summary",
|
||||
response_model=RiskSummaryResponse,
|
||||
summary="Compute risk scores per host",
|
||||
description=(
|
||||
"Analyzes dataset rows for suspicious patterns (encoded PowerShell, "
|
||||
"credential dumping, lateral movement) and produces per-host risk scores."
|
||||
),
|
||||
)
|
||||
async def get_risk_summary(
|
||||
hunt_id: str | None = Query(None, description="Hunt ID"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Extract IOCs (IPs, domains, hashes, etc.) from dataset rows."""
|
||||
from app.services.ioc_extractor import extract_iocs_from_dataset
|
||||
iocs = await extract_iocs_from_dataset(dataset_id, db, max_rows=max_rows)
|
||||
total = sum(len(v) for v in iocs.values())
|
||||
return {"dataset_id": dataset_id, "iocs": iocs, "total": total}
|
||||
"""Return risk scores for all hosts in a hunt."""
|
||||
result = await compute_risk_scores(db, hunt_id=hunt_id)
|
||||
return RiskSummaryResponse(**result)
|
||||
|
||||
|
||||
# --- Host grouping endpoints ---
|
||||
# ── LLM Analysis ─────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/hosts/{hunt_id}")
|
||||
async def get_host_groups(
|
||||
hunt_id: str,
|
||||
|
||||
@router.post(
|
||||
"/llm-analyze",
|
||||
response_model=AnalysisResult,
|
||||
summary="Run LLM-powered threat analysis on dataset",
|
||||
description=(
|
||||
"Loads dataset rows server-side, builds a summary, and sends to "
|
||||
"Wile (deep analysis) or Roadrunner (quick) for comprehensive "
|
||||
"threat analysis. Returns structured findings, IOCs, MITRE techniques."
|
||||
),
|
||||
)
|
||||
async def llm_analyze(
|
||||
request: AnalysisRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Group data by hostname across all datasets in a hunt."""
|
||||
from app.services.ioc_extractor import extract_host_groups
|
||||
groups = await extract_host_groups(hunt_id, db)
|
||||
return {"hunt_id": hunt_id, "hosts": groups}
|
||||
"""Run LLM analysis on a dataset or hunt."""
|
||||
if not request.dataset_id and not request.hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
|
||||
# Load rows
|
||||
rows_objs = await _fetch_rows(
|
||||
db,
|
||||
dataset_id=request.dataset_id,
|
||||
hunt_id=request.hunt_id,
|
||||
limit=2000,
|
||||
)
|
||||
|
||||
if not rows_objs:
|
||||
raise HTTPException(status_code=404, detail="No rows found for analysis")
|
||||
|
||||
# Extract data dicts
|
||||
rows = [r.normalized_data or r.data for r in rows_objs]
|
||||
|
||||
# Get dataset name
|
||||
ds_name = "hunt datasets"
|
||||
if request.dataset_id:
|
||||
repo = DatasetRepository(db)
|
||||
ds = await repo.get_dataset(request.dataset_id)
|
||||
if ds:
|
||||
ds_name = ds.name
|
||||
|
||||
result = await run_llm_analysis(rows, request, dataset_name=ds_name)
|
||||
return result
|
||||
|
||||
|
||||
# --- Anomaly detection endpoints ---
|
||||
# ── Timeline ──────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/anomalies/{dataset_id}")
|
||||
async def get_anomalies(
|
||||
dataset_id: str,
|
||||
outliers_only: bool = Query(False),
|
||||
|
||||
@router.get(
|
||||
"/timeline",
|
||||
summary="Get event timeline histogram bins",
|
||||
)
|
||||
async def get_timeline(
|
||||
dataset_id: str | None = Query(None),
|
||||
hunt_id: str | None = Query(None),
|
||||
bins: int = Query(60, ge=10, le=200),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get anomaly detection results for a dataset."""
|
||||
from app.db.models import AnomalyResult
|
||||
stmt = select(AnomalyResult).where(AnomalyResult.dataset_id == dataset_id)
|
||||
if outliers_only:
|
||||
stmt = stmt.where(AnomalyResult.is_outlier == True)
|
||||
stmt = stmt.order_by(AnomalyResult.anomaly_score.desc())
|
||||
result = await db.execute(stmt)
|
||||
rows = result.scalars().all()
|
||||
return [
|
||||
{
|
||||
"id": r.id,
|
||||
"dataset_id": r.dataset_id,
|
||||
"row_id": r.row_id,
|
||||
"anomaly_score": r.anomaly_score,
|
||||
"distance_from_centroid": r.distance_from_centroid,
|
||||
"cluster_id": r.cluster_id,
|
||||
"is_outlier": r.is_outlier,
|
||||
"explanation": r.explanation,
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
if not dataset_id and not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
return await build_timeline_bins(db, dataset_id=dataset_id, hunt_id=hunt_id, bins=bins)
|
||||
|
||||
|
||||
@router.post("/anomalies/{dataset_id}")
|
||||
async def trigger_anomaly_detection(
|
||||
dataset_id: str,
|
||||
k: int = Query(3, ge=2, le=20),
|
||||
threshold: float = Query(0.35, ge=0.1, le=0.9),
|
||||
background_tasks: BackgroundTasks = None,
|
||||
@router.get(
|
||||
"/field-stats",
|
||||
summary="Get per-field value distributions",
|
||||
)
|
||||
async def get_field_stats(
|
||||
dataset_id: str | None = Query(None),
|
||||
hunt_id: str | None = Query(None),
|
||||
fields: str | None = Query(None, description="Comma-separated field names"),
|
||||
top_n: int = Query(20, ge=5, le=100),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Trigger embedding-based anomaly detection on a dataset."""
|
||||
async def _run():
|
||||
from app.services.anomaly_detector import detect_anomalies
|
||||
await detect_anomalies(dataset_id, k=k, outlier_threshold=threshold)
|
||||
|
||||
if background_tasks:
|
||||
background_tasks.add_task(_run)
|
||||
return {"status": "anomaly_detection_started", "dataset_id": dataset_id}
|
||||
else:
|
||||
from app.services.anomaly_detector import detect_anomalies
|
||||
results = await detect_anomalies(dataset_id, k=k, outlier_threshold=threshold)
|
||||
return {"status": "complete", "dataset_id": dataset_id, "count": len(results)}
|
||||
|
||||
|
||||
# --- Natural language data query (SSE streaming) ---
|
||||
|
||||
@router.post("/query/{dataset_id}")
|
||||
async def query_dataset_endpoint(
|
||||
dataset_id: str,
|
||||
body: QueryRequest,
|
||||
):
|
||||
"""Ask a natural language question about a dataset.
|
||||
|
||||
Returns an SSE stream with token-by-token LLM response.
|
||||
Event types: status, metadata, token, error, done
|
||||
"""
|
||||
from app.services.data_query import query_dataset_stream
|
||||
|
||||
return StreamingResponse(
|
||||
query_dataset_stream(dataset_id, body.question, body.mode),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
if not dataset_id and not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
field_list = [f.strip() for f in fields.split(",")] if fields else None
|
||||
return await compute_field_stats(
|
||||
db, dataset_id=dataset_id, hunt_id=hunt_id,
|
||||
fields=field_list, top_n=top_n,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/query/{dataset_id}/sync")
|
||||
async def query_dataset_sync(
|
||||
dataset_id: str,
|
||||
body: QueryRequest,
|
||||
class SearchRequest(BaseModel):
|
||||
dataset_id: Optional[str] = None
|
||||
hunt_id: Optional[str] = None
|
||||
query: str = ""
|
||||
filters: dict[str, str] = Field(default_factory=dict)
|
||||
time_start: Optional[str] = None
|
||||
time_end: Optional[str] = None
|
||||
limit: int = 500
|
||||
offset: int = 0
|
||||
|
||||
|
||||
@router.post(
|
||||
"/search",
|
||||
summary="Search and filter dataset rows",
|
||||
)
|
||||
async def search_dataset_rows(
|
||||
request: SearchRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Non-streaming version of data query."""
|
||||
from app.services.data_query import query_dataset
|
||||
|
||||
try:
|
||||
answer = await query_dataset(dataset_id, body.question, body.mode)
|
||||
return {"dataset_id": dataset_id, "question": body.question, "answer": answer, "mode": body.mode}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Query failed: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# --- Job queue endpoints ---
|
||||
|
||||
@router.get("/jobs")
|
||||
async def list_jobs(
|
||||
status: str | None = Query(None),
|
||||
job_type: str | None = Query(None),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
):
|
||||
"""List all tracked jobs."""
|
||||
from app.services.job_queue import job_queue, JobStatus, JobType
|
||||
|
||||
s = JobStatus(status) if status else None
|
||||
t = JobType(job_type) if job_type else None
|
||||
jobs = job_queue.list_jobs(status=s, job_type=t, limit=limit)
|
||||
stats = job_queue.get_stats()
|
||||
return {"jobs": jobs, "stats": stats}
|
||||
|
||||
|
||||
@router.get("/jobs/{job_id}")
|
||||
async def get_job(job_id: str):
|
||||
"""Get status of a specific job."""
|
||||
from app.services.job_queue import job_queue
|
||||
|
||||
job = job_queue.get_job(job_id)
|
||||
if not job:
|
||||
raise HTTPException(status_code=404, detail="Job not found")
|
||||
return job.to_dict()
|
||||
|
||||
|
||||
@router.delete("/jobs/{job_id}")
|
||||
async def cancel_job(job_id: str):
|
||||
"""Cancel a running or queued job."""
|
||||
from app.services.job_queue import job_queue
|
||||
|
||||
if job_queue.cancel_job(job_id):
|
||||
return {"status": "cancelled", "job_id": job_id}
|
||||
raise HTTPException(status_code=400, detail="Job cannot be cancelled (already complete or not found)")
|
||||
|
||||
|
||||
@router.post("/jobs/submit/{job_type}")
|
||||
async def submit_job(
|
||||
job_type: str,
|
||||
params: dict = {},
|
||||
):
|
||||
"""Submit a new job to the queue.
|
||||
|
||||
Job types: triage, host_profile, report, anomaly, query
|
||||
Params vary by type (e.g., dataset_id, hunt_id, question, mode).
|
||||
"""
|
||||
from app.services.job_queue import job_queue, JobType
|
||||
|
||||
try:
|
||||
jt = JobType(job_type)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid job_type: {job_type}. Valid: {[t.value for t in JobType]}",
|
||||
if not request.dataset_id and not request.hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
return await search_rows(
|
||||
db,
|
||||
dataset_id=request.dataset_id,
|
||||
hunt_id=request.hunt_id,
|
||||
query=request.query,
|
||||
filters=request.filters,
|
||||
time_start=request.time_start,
|
||||
time_end=request.time_end,
|
||||
limit=request.limit,
|
||||
offset=request.offset,
|
||||
)
|
||||
|
||||
job = job_queue.submit(jt, **params)
|
||||
return {"job_id": job.id, "status": job.status.value, "job_type": job_type}
|
||||
|
||||
# ── MITRE ATT&CK ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
# --- Load balancer status ---
|
||||
|
||||
@router.get("/lb/status")
|
||||
async def lb_status():
|
||||
"""Get load balancer status for both nodes."""
|
||||
from app.services.load_balancer import lb
|
||||
return lb.get_status()
|
||||
@router.get(
|
||||
"/mitre-map",
|
||||
summary="Map dataset events to MITRE ATT&CK techniques",
|
||||
)
|
||||
async def get_mitre_map(
|
||||
dataset_id: str | None = Query(None),
|
||||
hunt_id: str | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not dataset_id and not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
return await map_to_attack(db, dataset_id=dataset_id, hunt_id=hunt_id)
|
||||
|
||||
|
||||
@router.post("/lb/check")
|
||||
async def lb_health_check():
|
||||
"""Force a health check of both nodes."""
|
||||
from app.services.load_balancer import lb
|
||||
await lb.check_health()
|
||||
return lb.get_status()
|
||||
@router.get(
|
||||
"/knowledge-graph",
|
||||
summary="Build entity-technique knowledge graph",
|
||||
)
|
||||
async def get_knowledge_graph(
|
||||
dataset_id: str | None = Query(None),
|
||||
hunt_id: str | None = Query(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not dataset_id and not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="Provide dataset_id or hunt_id")
|
||||
return await build_knowledge_graph(db, dataset_id=dataset_id, hunt_id=hunt_id)
|
||||
|
||||
296
backend/app/api/routes/cases.py
Normal file
@@ -0,0 +1,296 @@
|
||||
"""API routes for case management — CRUD for cases, tasks, and activity logs."""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select, func, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db import get_db
|
||||
from app.db.models import Case, CaseTask, ActivityLog, _new_id, _utcnow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/cases", tags=["cases"])
|
||||
|
||||
|
||||
# ── Pydantic models ──────────────────────────────────────────────────
|
||||
|
||||
class CaseCreate(BaseModel):
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
severity: str = "medium"
|
||||
tlp: str = "amber"
|
||||
pap: str = "amber"
|
||||
priority: int = 2
|
||||
assignee: Optional[str] = None
|
||||
tags: Optional[list[str]] = None
|
||||
hunt_id: Optional[str] = None
|
||||
mitre_techniques: Optional[list[str]] = None
|
||||
iocs: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class CaseUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
severity: Optional[str] = None
|
||||
tlp: Optional[str] = None
|
||||
pap: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
priority: Optional[int] = None
|
||||
assignee: Optional[str] = None
|
||||
tags: Optional[list[str]] = None
|
||||
mitre_techniques: Optional[list[str]] = None
|
||||
iocs: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class TaskCreate(BaseModel):
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
assignee: Optional[str] = None
|
||||
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
assignee: Optional[str] = None
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
# ── Helper: log activity ─────────────────────────────────────────────
|
||||
|
||||
async def _log_activity(
|
||||
db: AsyncSession,
|
||||
entity_type: str,
|
||||
entity_id: str,
|
||||
action: str,
|
||||
details: dict | None = None,
|
||||
):
|
||||
log = ActivityLog(
|
||||
entity_type=entity_type,
|
||||
entity_id=entity_id,
|
||||
action=action,
|
||||
details=details,
|
||||
created_at=_utcnow(),
|
||||
)
|
||||
db.add(log)
|
||||
|
||||
|
||||
# ── Case CRUD ─────────────────────────────────────────────────────────
|
||||
|
||||
@router.post("", summary="Create a case")
|
||||
async def create_case(body: CaseCreate, db: AsyncSession = Depends(get_db)):
|
||||
now = _utcnow()
|
||||
case = Case(
|
||||
id=_new_id(),
|
||||
title=body.title,
|
||||
description=body.description,
|
||||
severity=body.severity,
|
||||
tlp=body.tlp,
|
||||
pap=body.pap,
|
||||
priority=body.priority,
|
||||
assignee=body.assignee,
|
||||
tags=body.tags,
|
||||
hunt_id=body.hunt_id,
|
||||
mitre_techniques=body.mitre_techniques,
|
||||
iocs=body.iocs,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(case)
|
||||
await _log_activity(db, "case", case.id, "created", {"title": body.title})
|
||||
await db.commit()
|
||||
await db.refresh(case)
|
||||
return _case_to_dict(case)
|
||||
|
||||
|
||||
@router.get("", summary="List cases")
|
||||
async def list_cases(
|
||||
status: Optional[str] = Query(None),
|
||||
hunt_id: Optional[str] = Query(None),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
q = select(Case).order_by(desc(Case.updated_at))
|
||||
if status:
|
||||
q = q.where(Case.status == status)
|
||||
if hunt_id:
|
||||
q = q.where(Case.hunt_id == hunt_id)
|
||||
q = q.offset(offset).limit(limit)
|
||||
result = await db.execute(q)
|
||||
cases = result.scalars().all()
|
||||
|
||||
count_q = select(func.count(Case.id))
|
||||
if status:
|
||||
count_q = count_q.where(Case.status == status)
|
||||
if hunt_id:
|
||||
count_q = count_q.where(Case.hunt_id == hunt_id)
|
||||
total = (await db.execute(count_q)).scalar() or 0
|
||||
|
||||
return {"cases": [_case_to_dict(c) for c in cases], "total": total}
|
||||
|
||||
|
||||
@router.get("/{case_id}", summary="Get case detail")
|
||||
async def get_case(case_id: str, db: AsyncSession = Depends(get_db)):
|
||||
case = await db.get(Case, case_id)
|
||||
if not case:
|
||||
raise HTTPException(status_code=404, detail="Case not found")
|
||||
return _case_to_dict(case)
|
||||
|
||||
|
||||
@router.put("/{case_id}", summary="Update a case")
|
||||
async def update_case(case_id: str, body: CaseUpdate, db: AsyncSession = Depends(get_db)):
|
||||
case = await db.get(Case, case_id)
|
||||
if not case:
|
||||
raise HTTPException(status_code=404, detail="Case not found")
|
||||
changes = {}
|
||||
for field in ["title", "description", "severity", "tlp", "pap", "status",
|
||||
"priority", "assignee", "tags", "mitre_techniques", "iocs"]:
|
||||
val = getattr(body, field)
|
||||
if val is not None:
|
||||
old = getattr(case, field)
|
||||
setattr(case, field, val)
|
||||
changes[field] = {"old": old, "new": val}
|
||||
if "status" in changes and changes["status"]["new"] == "in-progress" and not case.started_at:
|
||||
case.started_at = _utcnow()
|
||||
if "status" in changes and changes["status"]["new"] in ("resolved", "closed"):
|
||||
case.resolved_at = _utcnow()
|
||||
case.updated_at = _utcnow()
|
||||
await _log_activity(db, "case", case.id, "updated", changes)
|
||||
await db.commit()
|
||||
await db.refresh(case)
|
||||
return _case_to_dict(case)
|
||||
|
||||
|
||||
@router.delete("/{case_id}", summary="Delete a case")
|
||||
async def delete_case(case_id: str, db: AsyncSession = Depends(get_db)):
|
||||
case = await db.get(Case, case_id)
|
||||
if not case:
|
||||
raise HTTPException(status_code=404, detail="Case not found")
|
||||
await db.delete(case)
|
||||
await db.commit()
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
# ── Task CRUD ─────────────────────────────────────────────────────────
|
||||
|
||||
@router.post("/{case_id}/tasks", summary="Add task to case")
|
||||
async def create_task(case_id: str, body: TaskCreate, db: AsyncSession = Depends(get_db)):
|
||||
case = await db.get(Case, case_id)
|
||||
if not case:
|
||||
raise HTTPException(status_code=404, detail="Case not found")
|
||||
now = _utcnow()
|
||||
task = CaseTask(
|
||||
id=_new_id(),
|
||||
case_id=case_id,
|
||||
title=body.title,
|
||||
description=body.description,
|
||||
assignee=body.assignee,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db.add(task)
|
||||
await _log_activity(db, "case", case_id, "task_created", {"title": body.title})
|
||||
await db.commit()
|
||||
await db.refresh(task)
|
||||
return _task_to_dict(task)
|
||||
|
||||
|
||||
@router.put("/{case_id}/tasks/{task_id}", summary="Update a task")
|
||||
async def update_task(case_id: str, task_id: str, body: TaskUpdate, db: AsyncSession = Depends(get_db)):
|
||||
task = await db.get(CaseTask, task_id)
|
||||
if not task or task.case_id != case_id:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
for field in ["title", "description", "status", "assignee", "order"]:
|
||||
val = getattr(body, field)
|
||||
if val is not None:
|
||||
setattr(task, field, val)
|
||||
task.updated_at = _utcnow()
|
||||
await _log_activity(db, "case", case_id, "task_updated", {"task_id": task_id})
|
||||
await db.commit()
|
||||
await db.refresh(task)
|
||||
return _task_to_dict(task)
|
||||
|
||||
|
||||
@router.delete("/{case_id}/tasks/{task_id}", summary="Delete a task")
|
||||
async def delete_task(case_id: str, task_id: str, db: AsyncSession = Depends(get_db)):
|
||||
task = await db.get(CaseTask, task_id)
|
||||
if not task or task.case_id != case_id:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
await db.delete(task)
|
||||
await db.commit()
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
# ── Activity Log ──────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/{case_id}/activity", summary="Get case activity log")
|
||||
async def get_activity(
|
||||
case_id: str,
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
q = (
|
||||
select(ActivityLog)
|
||||
.where(ActivityLog.entity_type == "case", ActivityLog.entity_id == case_id)
|
||||
.order_by(desc(ActivityLog.created_at))
|
||||
.limit(limit)
|
||||
)
|
||||
result = await db.execute(q)
|
||||
logs = result.scalars().all()
|
||||
return {
|
||||
"logs": [
|
||||
{
|
||||
"id": l.id,
|
||||
"action": l.action,
|
||||
"details": l.details,
|
||||
"user_id": l.user_id,
|
||||
"created_at": l.created_at.isoformat() if l.created_at else None,
|
||||
}
|
||||
for l in logs
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
def _case_to_dict(c: Case) -> dict:
|
||||
return {
|
||||
"id": c.id,
|
||||
"title": c.title,
|
||||
"description": c.description,
|
||||
"severity": c.severity,
|
||||
"tlp": c.tlp,
|
||||
"pap": c.pap,
|
||||
"status": c.status,
|
||||
"priority": c.priority,
|
||||
"assignee": c.assignee,
|
||||
"tags": c.tags or [],
|
||||
"hunt_id": c.hunt_id,
|
||||
"owner_id": c.owner_id,
|
||||
"mitre_techniques": c.mitre_techniques or [],
|
||||
"iocs": c.iocs or [],
|
||||
"started_at": c.started_at.isoformat() if c.started_at else None,
|
||||
"resolved_at": c.resolved_at.isoformat() if c.resolved_at else None,
|
||||
"created_at": c.created_at.isoformat() if c.created_at else None,
|
||||
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
|
||||
"tasks": [_task_to_dict(t) for t in (c.tasks or [])],
|
||||
}
|
||||
|
||||
|
||||
def _task_to_dict(t: CaseTask) -> dict:
|
||||
return {
|
||||
"id": t.id,
|
||||
"case_id": t.case_id,
|
||||
"title": t.title,
|
||||
"description": t.description,
|
||||
"status": t.status,
|
||||
"assignee": t.assignee,
|
||||
"order": t.order,
|
||||
"created_at": t.created_at.isoformat() if t.created_at else None,
|
||||
"updated_at": t.updated_at.isoformat() if t.updated_at else None,
|
||||
}
|
||||
@@ -293,3 +293,30 @@ async def delete_dataset(
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail="Dataset not found")
|
||||
return {"message": "Dataset deleted", "id": dataset_id}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/rescan-ioc",
|
||||
summary="Re-scan IOC columns for all datasets",
|
||||
)
|
||||
async def rescan_ioc_columns(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Re-run detect_ioc_columns on every dataset using current detection logic."""
|
||||
repo = DatasetRepository(db)
|
||||
all_ds = await repo.list_datasets(limit=10000)
|
||||
updated = 0
|
||||
for ds in all_ds:
|
||||
columns = list((ds.column_schema or {}).keys())
|
||||
if not columns:
|
||||
continue
|
||||
new_ioc = detect_ioc_columns(
|
||||
columns,
|
||||
ds.column_schema or {},
|
||||
ds.normalized_columns or {},
|
||||
)
|
||||
if new_ioc != (ds.ioc_columns or {}):
|
||||
ds.ioc_columns = new_ioc
|
||||
updated += 1
|
||||
await db.commit()
|
||||
return {"message": f"Rescanned {len(all_ds)} datasets, updated {updated}"}
|
||||
|
||||
@@ -1,28 +1,69 @@
|
||||
"""Network topology API - host inventory endpoint."""
|
||||
"""API routes for Network Picture — deduplicated host inventory."""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db import get_db
|
||||
from app.services.host_inventory import build_host_inventory
|
||||
from app.services.network_inventory import build_network_picture
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/network", tags=["network"])
|
||||
|
||||
|
||||
@router.get("/host-inventory")
|
||||
async def get_host_inventory(
|
||||
hunt_id: str = Query(..., description="Hunt ID to build inventory for"),
|
||||
# ── Response models ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
class HostEntry(BaseModel):
|
||||
hostname: str
|
||||
ips: list[str] = Field(default_factory=list)
|
||||
users: list[str] = Field(default_factory=list)
|
||||
os: list[str] = Field(default_factory=list)
|
||||
mac_addresses: list[str] = Field(default_factory=list)
|
||||
protocols: list[str] = Field(default_factory=list)
|
||||
open_ports: list[str] = Field(default_factory=list)
|
||||
remote_targets: list[str] = Field(default_factory=list)
|
||||
datasets: list[str] = Field(default_factory=list)
|
||||
connection_count: int = 0
|
||||
first_seen: str | None = None
|
||||
last_seen: str | None = None
|
||||
|
||||
|
||||
class PictureSummary(BaseModel):
|
||||
total_hosts: int = 0
|
||||
total_connections: int = 0
|
||||
total_unique_ips: int = 0
|
||||
datasets_scanned: int = 0
|
||||
|
||||
|
||||
class NetworkPictureResponse(BaseModel):
|
||||
hosts: list[HostEntry]
|
||||
summary: PictureSummary
|
||||
|
||||
|
||||
# ── Routes ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get(
|
||||
"/picture",
|
||||
response_model=NetworkPictureResponse,
|
||||
summary="Build deduplicated host inventory for a hunt",
|
||||
description=(
|
||||
"Scans all datasets in the specified hunt, extracts host-identifying "
|
||||
"fields (hostname, IP, username, OS, MAC, ports), deduplicates by "
|
||||
"hostname, and returns a clean one-row-per-host network picture."
|
||||
),
|
||||
)
|
||||
async def get_network_picture(
|
||||
hunt_id: str = Query(..., description="Hunt ID to scan"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Build a deduplicated host inventory from all datasets in a hunt.
|
||||
"""Return a deduplicated network picture for a hunt."""
|
||||
if not hunt_id:
|
||||
raise HTTPException(status_code=400, detail="hunt_id is required")
|
||||
|
||||
Returns unique hosts with hostname, IPs, OS, logged-in users, and
|
||||
network connections derived from netstat/connection data.
|
||||
"""
|
||||
result = await build_host_inventory(hunt_id, db)
|
||||
if result["stats"]["total_hosts"] == 0:
|
||||
return result
|
||||
result = await build_network_picture(db, hunt_id)
|
||||
return result
|
||||
360
backend/app/api/routes/notebooks.py
Normal file
@@ -0,0 +1,360 @@
|
||||
"""API routes for investigation notebooks and playbooks."""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select, func, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db import get_db
|
||||
from app.db.models import Notebook, PlaybookRun, _new_id, _utcnow
|
||||
from app.services.playbook import (
|
||||
get_builtin_playbooks,
|
||||
get_playbook_template,
|
||||
validate_notebook_cells,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/notebooks", tags=["notebooks"])
|
||||
|
||||
|
||||
# ── Pydantic models ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class NotebookCreate(BaseModel):
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
cells: Optional[list[dict]] = None
|
||||
hunt_id: Optional[str] = None
|
||||
case_id: Optional[str] = None
|
||||
tags: Optional[list[str]] = None
|
||||
|
||||
|
||||
class NotebookUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
cells: Optional[list[dict]] = None
|
||||
tags: Optional[list[str]] = None
|
||||
|
||||
|
||||
class CellUpdate(BaseModel):
|
||||
"""Update a single cell or add a new one."""
|
||||
cell_id: str
|
||||
cell_type: Optional[str] = None
|
||||
source: Optional[str] = None
|
||||
output: Optional[str] = None
|
||||
metadata: Optional[dict] = None
|
||||
|
||||
|
||||
class PlaybookStart(BaseModel):
|
||||
playbook_name: str
|
||||
hunt_id: Optional[str] = None
|
||||
case_id: Optional[str] = None
|
||||
started_by: Optional[str] = None
|
||||
|
||||
|
||||
class StepComplete(BaseModel):
|
||||
notes: Optional[str] = None
|
||||
status: str = "completed" # completed | skipped
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _notebook_to_dict(nb: Notebook) -> dict:
|
||||
return {
|
||||
"id": nb.id,
|
||||
"title": nb.title,
|
||||
"description": nb.description,
|
||||
"cells": nb.cells or [],
|
||||
"hunt_id": nb.hunt_id,
|
||||
"case_id": nb.case_id,
|
||||
"owner_id": nb.owner_id,
|
||||
"tags": nb.tags or [],
|
||||
"cell_count": len(nb.cells or []),
|
||||
"created_at": nb.created_at.isoformat() if nb.created_at else None,
|
||||
"updated_at": nb.updated_at.isoformat() if nb.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
def _run_to_dict(run: PlaybookRun) -> dict:
|
||||
return {
|
||||
"id": run.id,
|
||||
"playbook_name": run.playbook_name,
|
||||
"status": run.status,
|
||||
"current_step": run.current_step,
|
||||
"total_steps": run.total_steps,
|
||||
"step_results": run.step_results or [],
|
||||
"hunt_id": run.hunt_id,
|
||||
"case_id": run.case_id,
|
||||
"started_by": run.started_by,
|
||||
"created_at": run.created_at.isoformat() if run.created_at else None,
|
||||
"updated_at": run.updated_at.isoformat() if run.updated_at else None,
|
||||
"completed_at": run.completed_at.isoformat() if run.completed_at else None,
|
||||
}
|
||||
|
||||
|
||||
# ── Notebook CRUD ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("", summary="List notebooks")
|
||||
async def list_notebooks(
|
||||
hunt_id: str | None = Query(None),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
stmt = select(Notebook)
|
||||
count_stmt = select(func.count(Notebook.id))
|
||||
if hunt_id:
|
||||
stmt = stmt.where(Notebook.hunt_id == hunt_id)
|
||||
count_stmt = count_stmt.where(Notebook.hunt_id == hunt_id)
|
||||
|
||||
total = (await db.execute(count_stmt)).scalar() or 0
|
||||
results = (await db.execute(
|
||||
stmt.order_by(desc(Notebook.updated_at)).offset(offset).limit(limit)
|
||||
)).scalars().all()
|
||||
|
||||
return {"notebooks": [_notebook_to_dict(n) for n in results], "total": total}
|
||||
|
||||
|
||||
@router.get("/{notebook_id}", summary="Get notebook")
|
||||
async def get_notebook(notebook_id: str, db: AsyncSession = Depends(get_db)):
|
||||
nb = await db.get(Notebook, notebook_id)
|
||||
if not nb:
|
||||
raise HTTPException(status_code=404, detail="Notebook not found")
|
||||
return _notebook_to_dict(nb)
|
||||
|
||||
|
||||
@router.post("", summary="Create notebook")
|
||||
async def create_notebook(body: NotebookCreate, db: AsyncSession = Depends(get_db)):
|
||||
cells = validate_notebook_cells(body.cells or [])
|
||||
if not cells:
|
||||
# Start with a default markdown cell
|
||||
cells = [{"id": "cell-0", "cell_type": "markdown", "source": "# Investigation Notes\n\nStart documenting your findings here.", "output": None, "metadata": {}}]
|
||||
|
||||
nb = Notebook(
|
||||
id=_new_id(),
|
||||
title=body.title,
|
||||
description=body.description,
|
||||
cells=cells,
|
||||
hunt_id=body.hunt_id,
|
||||
case_id=body.case_id,
|
||||
tags=body.tags,
|
||||
)
|
||||
db.add(nb)
|
||||
await db.commit()
|
||||
await db.refresh(nb)
|
||||
return _notebook_to_dict(nb)
|
||||
|
||||
|
||||
@router.put("/{notebook_id}", summary="Update notebook")
|
||||
async def update_notebook(
|
||||
notebook_id: str, body: NotebookUpdate, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
nb = await db.get(Notebook, notebook_id)
|
||||
if not nb:
|
||||
raise HTTPException(status_code=404, detail="Notebook not found")
|
||||
|
||||
if body.title is not None:
|
||||
nb.title = body.title
|
||||
if body.description is not None:
|
||||
nb.description = body.description
|
||||
if body.cells is not None:
|
||||
nb.cells = validate_notebook_cells(body.cells)
|
||||
if body.tags is not None:
|
||||
nb.tags = body.tags
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(nb)
|
||||
return _notebook_to_dict(nb)
|
||||
|
||||
|
||||
@router.post("/{notebook_id}/cells", summary="Add or update a cell")
|
||||
async def upsert_cell(
|
||||
notebook_id: str, body: CellUpdate, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
nb = await db.get(Notebook, notebook_id)
|
||||
if not nb:
|
||||
raise HTTPException(status_code=404, detail="Notebook not found")
|
||||
|
||||
cells = list(nb.cells or [])
|
||||
found = False
|
||||
for i, c in enumerate(cells):
|
||||
if c.get("id") == body.cell_id:
|
||||
if body.cell_type is not None:
|
||||
cells[i]["cell_type"] = body.cell_type
|
||||
if body.source is not None:
|
||||
cells[i]["source"] = body.source
|
||||
if body.output is not None:
|
||||
cells[i]["output"] = body.output
|
||||
if body.metadata is not None:
|
||||
cells[i]["metadata"] = body.metadata
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
cells.append({
|
||||
"id": body.cell_id,
|
||||
"cell_type": body.cell_type or "markdown",
|
||||
"source": body.source or "",
|
||||
"output": body.output,
|
||||
"metadata": body.metadata or {},
|
||||
})
|
||||
|
||||
nb.cells = cells
|
||||
await db.commit()
|
||||
await db.refresh(nb)
|
||||
return _notebook_to_dict(nb)
|
||||
|
||||
|
||||
@router.delete("/{notebook_id}/cells/{cell_id}", summary="Delete a cell")
|
||||
async def delete_cell(
|
||||
notebook_id: str, cell_id: str, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
nb = await db.get(Notebook, notebook_id)
|
||||
if not nb:
|
||||
raise HTTPException(status_code=404, detail="Notebook not found")
|
||||
|
||||
cells = [c for c in (nb.cells or []) if c.get("id") != cell_id]
|
||||
nb.cells = cells
|
||||
await db.commit()
|
||||
return {"ok": True, "remaining_cells": len(cells)}
|
||||
|
||||
|
||||
@router.delete("/{notebook_id}", summary="Delete notebook")
|
||||
async def delete_notebook(notebook_id: str, db: AsyncSession = Depends(get_db)):
|
||||
nb = await db.get(Notebook, notebook_id)
|
||||
if not nb:
|
||||
raise HTTPException(status_code=404, detail="Notebook not found")
|
||||
await db.delete(nb)
|
||||
await db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ── Playbooks ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/playbooks/templates", summary="List built-in playbook templates")
|
||||
async def list_playbook_templates():
|
||||
templates = get_builtin_playbooks()
|
||||
return {
|
||||
"templates": [
|
||||
{
|
||||
"name": t["name"],
|
||||
"description": t["description"],
|
||||
"category": t["category"],
|
||||
"tags": t["tags"],
|
||||
"step_count": len(t["steps"]),
|
||||
}
|
||||
for t in templates
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/playbooks/templates/{name}", summary="Get playbook template detail")
|
||||
async def get_playbook_template_detail(name: str):
|
||||
template = get_playbook_template(name)
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="Playbook template not found")
|
||||
return template
|
||||
|
||||
|
||||
@router.post("/playbooks/start", summary="Start a playbook run")
|
||||
async def start_playbook(body: PlaybookStart, db: AsyncSession = Depends(get_db)):
|
||||
template = get_playbook_template(body.playbook_name)
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="Playbook template not found")
|
||||
|
||||
run = PlaybookRun(
|
||||
id=_new_id(),
|
||||
playbook_name=body.playbook_name,
|
||||
status="in-progress",
|
||||
current_step=1,
|
||||
total_steps=len(template["steps"]),
|
||||
step_results=[],
|
||||
hunt_id=body.hunt_id,
|
||||
case_id=body.case_id,
|
||||
started_by=body.started_by,
|
||||
)
|
||||
db.add(run)
|
||||
await db.commit()
|
||||
await db.refresh(run)
|
||||
return _run_to_dict(run)
|
||||
|
||||
|
||||
@router.get("/playbooks/runs", summary="List playbook runs")
|
||||
async def list_playbook_runs(
|
||||
status: str | None = Query(None),
|
||||
hunt_id: str | None = Query(None),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
stmt = select(PlaybookRun)
|
||||
if status:
|
||||
stmt = stmt.where(PlaybookRun.status == status)
|
||||
if hunt_id:
|
||||
stmt = stmt.where(PlaybookRun.hunt_id == hunt_id)
|
||||
|
||||
results = (await db.execute(
|
||||
stmt.order_by(desc(PlaybookRun.created_at)).limit(limit)
|
||||
)).scalars().all()
|
||||
|
||||
return {"runs": [_run_to_dict(r) for r in results]}
|
||||
|
||||
|
||||
@router.get("/playbooks/runs/{run_id}", summary="Get playbook run detail")
|
||||
async def get_playbook_run(run_id: str, db: AsyncSession = Depends(get_db)):
|
||||
run = await db.get(PlaybookRun, run_id)
|
||||
if not run:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
|
||||
# Also include the template steps
|
||||
template = get_playbook_template(run.playbook_name)
|
||||
result = _run_to_dict(run)
|
||||
result["steps"] = template["steps"] if template else []
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/playbooks/runs/{run_id}/complete-step", summary="Complete current playbook step")
|
||||
async def complete_step(
|
||||
run_id: str, body: StepComplete, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
run = await db.get(PlaybookRun, run_id)
|
||||
if not run:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
if run.status != "in-progress":
|
||||
raise HTTPException(status_code=400, detail="Run is not in progress")
|
||||
|
||||
step_results = list(run.step_results or [])
|
||||
step_results.append({
|
||||
"step": run.current_step,
|
||||
"status": body.status,
|
||||
"notes": body.notes,
|
||||
"completed_at": _utcnow().isoformat(),
|
||||
})
|
||||
run.step_results = step_results
|
||||
|
||||
if run.current_step >= run.total_steps:
|
||||
run.status = "completed"
|
||||
run.completed_at = _utcnow()
|
||||
else:
|
||||
run.current_step += 1
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(run)
|
||||
return _run_to_dict(run)
|
||||
|
||||
|
||||
@router.post("/playbooks/runs/{run_id}/abort", summary="Abort a playbook run")
|
||||
async def abort_run(run_id: str, db: AsyncSession = Depends(get_db)):
|
||||
run = await db.get(PlaybookRun, run_id)
|
||||
if not run:
|
||||
raise HTTPException(status_code=404, detail="Run not found")
|
||||
run.status = "aborted"
|
||||
run.completed_at = _utcnow()
|
||||
await db.commit()
|
||||
return _run_to_dict(run)
|
||||
@@ -15,7 +15,7 @@ class AppConfig(BaseSettings):
|
||||
|
||||
# ── General ────────────────────────────────────────────────────────
|
||||
APP_NAME: str = "ThreatHunt"
|
||||
APP_VERSION: str = "0.3.0"
|
||||
APP_VERSION: str = "0.4.0"
|
||||
DEBUG: bool = Field(default=False, description="Enable debug mode")
|
||||
|
||||
# ── Database ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -66,8 +66,20 @@ async def get_db() -> AsyncSession: # type: ignore[misc]
|
||||
|
||||
async def init_db() -> None:
|
||||
"""Create all tables (for dev / first-run). In production use Alembic."""
|
||||
from sqlalchemy import inspect as sa_inspect
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
# Only create tables that don't already exist (safe alongside Alembic)
|
||||
def _create_missing(sync_conn):
|
||||
inspector = sa_inspect(sync_conn)
|
||||
existing = set(inspector.get_table_names())
|
||||
tables_to_create = [
|
||||
t for t in Base.metadata.sorted_tables
|
||||
if t.name not in existing
|
||||
]
|
||||
Base.metadata.create_all(sync_conn, tables=tables_to_create)
|
||||
|
||||
await conn.run_sync(_create_missing)
|
||||
|
||||
|
||||
async def dispose_db() -> None:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""SQLAlchemy ORM models for ThreatHunt.
|
||||
|
||||
All persistent entities: datasets, hunts, conversations, annotations,
|
||||
hypotheses, enrichment results, users, and AI analysis tables.
|
||||
hypotheses, enrichment results, and users.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
@@ -32,7 +32,8 @@ def _new_id() -> str:
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
# -- Users ---
|
||||
# ── Users ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
@@ -41,15 +42,17 @@ class User(Base):
|
||||
username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True)
|
||||
email: Mapped[str] = mapped_column(String(256), unique=True, nullable=False)
|
||||
hashed_password: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
role: Mapped[str] = mapped_column(String(16), default="analyst")
|
||||
role: Mapped[str] = mapped_column(String(16), default="analyst") # analyst | admin | viewer
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
# relationships
|
||||
hunts: Mapped[list["Hunt"]] = relationship(back_populates="owner", lazy="selectin")
|
||||
annotations: Mapped[list["Annotation"]] = relationship(back_populates="author", lazy="selectin")
|
||||
|
||||
|
||||
# -- Hunts ---
|
||||
# ── Hunts ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Hunt(Base):
|
||||
__tablename__ = "hunts"
|
||||
@@ -57,7 +60,7 @@ class Hunt(Base):
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(32), default="active")
|
||||
status: Mapped[str] = mapped_column(String(32), default="active") # active | closed | archived
|
||||
owner_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("users.id"), nullable=True
|
||||
)
|
||||
@@ -66,15 +69,15 @@ class Hunt(Base):
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
# relationships
|
||||
owner: Mapped[Optional["User"]] = relationship(back_populates="hunts", lazy="selectin")
|
||||
datasets: Mapped[list["Dataset"]] = relationship(back_populates="hunt", lazy="selectin")
|
||||
conversations: Mapped[list["Conversation"]] = relationship(back_populates="hunt", lazy="selectin")
|
||||
hypotheses: Mapped[list["Hypothesis"]] = relationship(back_populates="hunt", lazy="selectin")
|
||||
host_profiles: Mapped[list["HostProfile"]] = relationship(back_populates="hunt", lazy="noload")
|
||||
reports: Mapped[list["HuntReport"]] = relationship(back_populates="hunt", lazy="noload")
|
||||
|
||||
|
||||
# -- Datasets ---
|
||||
# ── Datasets ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Dataset(Base):
|
||||
__tablename__ = "datasets"
|
||||
@@ -82,44 +85,36 @@ class Dataset(Base):
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False, index=True)
|
||||
filename: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
source_tool: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
source_tool: Mapped[Optional[str]] = mapped_column(String(64), nullable=True) # velociraptor, etc.
|
||||
row_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
column_schema: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
normalized_columns: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
ioc_columns: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
ioc_columns: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # auto-detected IOC columns
|
||||
file_size_bytes: Mapped[int] = mapped_column(Integer, default=0)
|
||||
encoding: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
delimiter: Mapped[Optional[str]] = mapped_column(String(4), nullable=True)
|
||||
time_range_start: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
time_range_end: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# New Phase 1-2 columns
|
||||
processing_status: Mapped[str] = mapped_column(String(20), default="ready")
|
||||
artifact_type: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
file_path: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
|
||||
|
||||
hunt_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id"), nullable=True
|
||||
)
|
||||
uploaded_by: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
# relationships
|
||||
hunt: Mapped[Optional["Hunt"]] = relationship(back_populates="datasets", lazy="selectin")
|
||||
rows: Mapped[list["DatasetRow"]] = relationship(
|
||||
back_populates="dataset", lazy="noload", cascade="all, delete-orphan"
|
||||
)
|
||||
triage_results: Mapped[list["TriageResult"]] = relationship(
|
||||
back_populates="dataset", lazy="noload", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_datasets_hunt", "hunt_id"),
|
||||
Index("ix_datasets_status", "processing_status"),
|
||||
)
|
||||
|
||||
|
||||
class DatasetRow(Base):
|
||||
"""Individual row from a CSV dataset, stored as JSON blob."""
|
||||
__tablename__ = "dataset_rows"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
@@ -130,6 +125,7 @@ class DatasetRow(Base):
|
||||
data: Mapped[dict] = mapped_column(JSON, nullable=False)
|
||||
normalized_data: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
|
||||
# relationships
|
||||
dataset: Mapped["Dataset"] = relationship(back_populates="rows")
|
||||
annotations: Mapped[list["Annotation"]] = relationship(
|
||||
back_populates="row", lazy="noload"
|
||||
@@ -141,7 +137,8 @@ class DatasetRow(Base):
|
||||
)
|
||||
|
||||
|
||||
# -- Conversations ---
|
||||
# ── Conversations ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Conversation(Base):
|
||||
__tablename__ = "conversations"
|
||||
@@ -159,6 +156,7 @@ class Conversation(Base):
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
# relationships
|
||||
hunt: Mapped[Optional["Hunt"]] = relationship(back_populates="conversations", lazy="selectin")
|
||||
messages: Mapped[list["Message"]] = relationship(
|
||||
back_populates="conversation", lazy="selectin", cascade="all, delete-orphan",
|
||||
@@ -173,15 +171,16 @@ class Message(Base):
|
||||
conversation_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
role: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||
role: Mapped[str] = mapped_column(String(16), nullable=False) # user | agent | system
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
model_used: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
node_used: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
node_used: Mapped[Optional[str]] = mapped_column(String(64), nullable=True) # wile | roadrunner | cluster
|
||||
token_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
latency_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
response_meta: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
# relationships
|
||||
conversation: Mapped["Conversation"] = relationship(back_populates="messages")
|
||||
|
||||
__table_args__ = (
|
||||
@@ -189,7 +188,8 @@ class Message(Base):
|
||||
)
|
||||
|
||||
|
||||
# -- Annotations ---
|
||||
# ── Annotations ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Annotation(Base):
|
||||
__tablename__ = "annotations"
|
||||
@@ -205,14 +205,19 @@ class Annotation(Base):
|
||||
String(32), ForeignKey("users.id"), nullable=True
|
||||
)
|
||||
text: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
severity: Mapped[str] = mapped_column(String(16), default="info")
|
||||
tag: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
severity: Mapped[str] = mapped_column(
|
||||
String(16), default="info"
|
||||
) # info | low | medium | high | critical
|
||||
tag: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), nullable=True
|
||||
) # suspicious | benign | needs-review
|
||||
highlight_color: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
# relationships
|
||||
row: Mapped[Optional["DatasetRow"]] = relationship(back_populates="annotations")
|
||||
author: Mapped[Optional["User"]] = relationship(back_populates="annotations")
|
||||
|
||||
@@ -222,7 +227,8 @@ class Annotation(Base):
|
||||
)
|
||||
|
||||
|
||||
# -- Hypotheses ---
|
||||
# ── Hypotheses ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Hypothesis(Base):
|
||||
__tablename__ = "hypotheses"
|
||||
@@ -234,7 +240,9 @@ class Hypothesis(Base):
|
||||
title: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
mitre_technique: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(16), default="draft")
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(16), default="draft"
|
||||
) # draft | active | confirmed | rejected
|
||||
evidence_row_ids: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
evidence_notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
@@ -242,6 +250,7 @@ class Hypothesis(Base):
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
# relationships
|
||||
hunt: Mapped[Optional["Hunt"]] = relationship(back_populates="hypotheses", lazy="selectin")
|
||||
|
||||
__table_args__ = (
|
||||
@@ -249,16 +258,21 @@ class Hypothesis(Base):
|
||||
)
|
||||
|
||||
|
||||
# -- Enrichment Results ---
|
||||
# ── Enrichment Results ────────────────────────────────────────────────
|
||||
|
||||
|
||||
class EnrichmentResult(Base):
|
||||
__tablename__ = "enrichment_results"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
ioc_value: Mapped[str] = mapped_column(String(512), nullable=False, index=True)
|
||||
ioc_type: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
source: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
verdict: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
||||
ioc_type: Mapped[str] = mapped_column(
|
||||
String(32), nullable=False
|
||||
) # ip | hash_md5 | hash_sha1 | hash_sha256 | domain | url
|
||||
source: Mapped[str] = mapped_column(String(32), nullable=False) # virustotal | abuseipdb | shodan | ai
|
||||
verdict: Mapped[Optional[str]] = mapped_column(
|
||||
String(16), nullable=True
|
||||
) # clean | suspicious | malicious | unknown
|
||||
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
raw_result: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
@@ -273,24 +287,28 @@ class EnrichmentResult(Base):
|
||||
)
|
||||
|
||||
|
||||
# -- AUP Keyword Themes & Keywords ---
|
||||
# ── AUP Keyword Themes & Keywords ────────────────────────────────────
|
||||
|
||||
|
||||
class KeywordTheme(Base):
|
||||
"""A named category of keywords for AUP scanning (e.g. gambling, gaming)."""
|
||||
__tablename__ = "keyword_themes"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
name: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True)
|
||||
color: Mapped[str] = mapped_column(String(16), default="#9e9e9e")
|
||||
color: Mapped[str] = mapped_column(String(16), default="#9e9e9e") # hex chip color
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
is_builtin: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
is_builtin: Mapped[bool] = mapped_column(Boolean, default=False) # seed-provided
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
# relationships
|
||||
keywords: Mapped[list["Keyword"]] = relationship(
|
||||
back_populates="theme", lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
|
||||
class Keyword(Base):
|
||||
"""Individual keyword / pattern belonging to a theme."""
|
||||
__tablename__ = "keywords"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
@@ -301,6 +319,7 @@ class Keyword(Base):
|
||||
is_regex: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
# relationships
|
||||
theme: Mapped["KeywordTheme"] = relationship(back_populates="keywords")
|
||||
|
||||
__table_args__ = (
|
||||
@@ -309,94 +328,219 @@ class Keyword(Base):
|
||||
)
|
||||
|
||||
|
||||
# -- AI Analysis Tables (Phase 2) ---
|
||||
# ── Cases ─────────────────────────────────────────────────────────────
|
||||
|
||||
class TriageResult(Base):
|
||||
__tablename__ = "triage_results"
|
||||
|
||||
class Case(Base):
|
||||
"""Incident / investigation case, inspired by TheHive."""
|
||||
__tablename__ = "cases"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
dataset_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
title: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
severity: Mapped[str] = mapped_column(String(16), default="medium") # info|low|medium|high|critical
|
||||
tlp: Mapped[str] = mapped_column(String(16), default="amber") # white|green|amber|red
|
||||
pap: Mapped[str] = mapped_column(String(16), default="amber") # white|green|amber|red
|
||||
status: Mapped[str] = mapped_column(String(24), default="open") # open|in-progress|resolved|closed
|
||||
priority: Mapped[int] = mapped_column(Integer, default=2) # 1(urgent)..4(low)
|
||||
assignee: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
tags: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
hunt_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id"), nullable=True
|
||||
)
|
||||
row_start: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
row_end: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
risk_score: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
verdict: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
findings: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
suspicious_indicators: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
mitre_techniques: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
model_used: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
node_used: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
dataset: Mapped["Dataset"] = relationship(back_populates="triage_results")
|
||||
|
||||
|
||||
class HostProfile(Base):
|
||||
__tablename__ = "host_profiles"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
hunt_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
owner_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("users.id"), nullable=True
|
||||
)
|
||||
hostname: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
fqdn: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
|
||||
client_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
risk_score: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
risk_level: Mapped[str] = mapped_column(String(20), default="unknown")
|
||||
artifact_summary: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
timeline_summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
suspicious_findings: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
mitre_techniques: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
llm_analysis: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
model_used: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
node_used: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
iocs: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # [{type, value, description}]
|
||||
started_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
resolved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
hunt: Mapped["Hunt"] = relationship(back_populates="host_profiles")
|
||||
# relationships
|
||||
tasks: Mapped[list["CaseTask"]] = relationship(
|
||||
back_populates="case", lazy="selectin", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_cases_hunt", "hunt_id"),
|
||||
Index("ix_cases_status", "status"),
|
||||
)
|
||||
|
||||
|
||||
class HuntReport(Base):
|
||||
__tablename__ = "hunt_reports"
|
||||
class CaseTask(Base):
|
||||
"""Task within a case (Kanban board item)."""
|
||||
__tablename__ = "case_tasks"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
hunt_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
case_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("cases.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
exec_summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
full_report: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
findings: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
recommendations: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
mitre_mapping: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
ioc_table: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
host_risk_summary: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
models_used: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
generation_time_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
title: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(24), default="todo") # todo|in-progress|done
|
||||
assignee: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
hunt: Mapped["Hunt"] = relationship(back_populates="reports")
|
||||
# relationships
|
||||
case: Mapped["Case"] = relationship(back_populates="tasks")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_case_tasks_case", "case_id"),
|
||||
)
|
||||
|
||||
|
||||
class AnomalyResult(Base):
|
||||
__tablename__ = "anomaly_results"
|
||||
# ── Activity Log ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class ActivityLog(Base):
|
||||
"""Audit trail / activity log for cases and hunts."""
|
||||
__tablename__ = "activity_logs"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
entity_type: Mapped[str] = mapped_column(String(32), nullable=False) # case|hunt|annotation
|
||||
entity_id: Mapped[str] = mapped_column(String(32), nullable=False)
|
||||
action: Mapped[str] = mapped_column(String(64), nullable=False) # created|updated|status_changed|etc
|
||||
details: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
||||
user_id: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_activity_entity", "entity_type", "entity_id"),
|
||||
)
|
||||
|
||||
|
||||
# ── Alerts ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Alert(Base):
|
||||
"""Security alert generated by analyzers or rules."""
|
||||
__tablename__ = "alerts"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
dataset_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False, index=True
|
||||
title: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
severity: Mapped[str] = mapped_column(String(16), default="medium") # critical|high|medium|low|info
|
||||
status: Mapped[str] = mapped_column(String(24), default="new") # new|acknowledged|in-progress|resolved|false-positive
|
||||
analyzer: Mapped[str] = mapped_column(String(64), nullable=False) # which analyzer produced it
|
||||
score: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
evidence: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # [{row_index, field, value, ...}]
|
||||
mitre_technique: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
tags: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
hunt_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id"), nullable=True
|
||||
)
|
||||
row_id: Mapped[Optional[int]] = mapped_column(
|
||||
Integer, ForeignKey("dataset_rows.id", ondelete="CASCADE"), nullable=True
|
||||
dataset_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("datasets.id"), nullable=True
|
||||
)
|
||||
anomaly_score: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
distance_from_centroid: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
cluster_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
is_outlier: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
explanation: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
case_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("cases.id"), nullable=True
|
||||
)
|
||||
assignee: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
acknowledged_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
resolved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_alerts_severity", "severity"),
|
||||
Index("ix_alerts_status", "status"),
|
||||
Index("ix_alerts_hunt", "hunt_id"),
|
||||
Index("ix_alerts_dataset", "dataset_id"),
|
||||
)
|
||||
|
||||
|
||||
class AlertRule(Base):
|
||||
"""User-defined alert rule (triggers analyzers automatically on upload)."""
|
||||
__tablename__ = "alert_rules"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
name: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
analyzer: Mapped[str] = mapped_column(String(64), nullable=False) # analyzer name
|
||||
config: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) # analyzer config overrides
|
||||
severity_override: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
hunt_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id"), nullable=True
|
||||
) # None = global
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_alert_rules_analyzer", "analyzer"),
|
||||
)
|
||||
|
||||
|
||||
# ── Notebooks ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class Notebook(Base):
|
||||
"""Investigation notebook — cell-based document for analyst notes and queries."""
|
||||
__tablename__ = "notebooks"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
title: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
cells: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # [{id, cell_type, source, output, metadata}]
|
||||
hunt_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id"), nullable=True
|
||||
)
|
||||
case_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("cases.id"), nullable=True
|
||||
)
|
||||
owner_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("users.id"), nullable=True
|
||||
)
|
||||
tags: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_notebooks_hunt", "hunt_id"),
|
||||
)
|
||||
|
||||
|
||||
# ── Playbook Runs ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class PlaybookRun(Base):
|
||||
"""Record of a playbook execution (links a template to a hunt/case)."""
|
||||
__tablename__ = "playbook_runs"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=_new_id)
|
||||
playbook_name: Mapped[str] = mapped_column(String(256), nullable=False)
|
||||
status: Mapped[str] = mapped_column(String(24), default="in-progress") # in-progress | completed | aborted
|
||||
current_step: Mapped[int] = mapped_column(Integer, default=1)
|
||||
total_steps: Mapped[int] = mapped_column(Integer, default=0)
|
||||
step_results: Mapped[Optional[list]] = mapped_column(JSON, nullable=True) # [{step, status, notes, completed_at}]
|
||||
hunt_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("hunts.id"), nullable=True
|
||||
)
|
||||
case_id: Mapped[Optional[str]] = mapped_column(
|
||||
String(32), ForeignKey("cases.id"), nullable=True
|
||||
)
|
||||
started_by: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow
|
||||
)
|
||||
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_playbook_runs_hunt", "hunt_id"),
|
||||
Index("ix_playbook_runs_status", "status"),
|
||||
)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
"""ThreatHunt backend application.
|
||||
|
||||
Wires together: database, CORS, agent routes, dataset routes, hunt routes,
|
||||
annotation/hypothesis routes, analysis routes, network routes, job queue,
|
||||
load balancer. DB tables are auto-created on startup.
|
||||
annotation/hypothesis routes. DB tables are auto-created on startup.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -23,8 +21,11 @@ from app.api.routes.correlation import router as correlation_router
|
||||
from app.api.routes.reports import router as reports_router
|
||||
from app.api.routes.auth import router as auth_router
|
||||
from app.api.routes.keywords import router as keywords_router
|
||||
from app.api.routes.analysis import router as analysis_router
|
||||
from app.api.routes.network import router as network_router
|
||||
from app.api.routes.analysis import router as analysis_router
|
||||
from app.api.routes.cases import router as cases_router
|
||||
from app.api.routes.alerts import router as alerts_router
|
||||
from app.api.routes.notebooks import router as notebooks_router
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,45 +33,17 @@ logger = logging.getLogger(__name__)
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Startup / shutdown lifecycle."""
|
||||
logger.info("Starting ThreatHunt API ...")
|
||||
logger.info("Starting ThreatHunt API …")
|
||||
await init_db()
|
||||
logger.info("Database initialised")
|
||||
|
||||
# Ensure uploads directory exists
|
||||
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
|
||||
logger.info("Upload dir: %s", os.path.abspath(settings.UPLOAD_DIR))
|
||||
|
||||
# Seed default AUP keyword themes
|
||||
from app.db import async_session_factory
|
||||
from app.services.keyword_defaults import seed_defaults
|
||||
async with async_session_factory() as seed_db:
|
||||
await seed_defaults(seed_db)
|
||||
logger.info("AUP keyword defaults checked")
|
||||
|
||||
# Start job queue (Phase 10)
|
||||
from app.services.job_queue import job_queue, register_all_handlers
|
||||
register_all_handlers()
|
||||
await job_queue.start()
|
||||
logger.info("Job queue started (%d workers)", job_queue._max_workers)
|
||||
|
||||
# Start load balancer health loop (Phase 10)
|
||||
from app.services.load_balancer import lb
|
||||
await lb.start_health_loop(interval=30.0)
|
||||
logger.info("Load balancer health loop started")
|
||||
|
||||
yield
|
||||
|
||||
logger.info("Shutting down ...")
|
||||
# Stop job queue
|
||||
from app.services.job_queue import job_queue as jq
|
||||
await jq.stop()
|
||||
logger.info("Job queue stopped")
|
||||
|
||||
# Stop load balancer
|
||||
from app.services.load_balancer import lb as _lb
|
||||
await _lb.stop_health_loop()
|
||||
logger.info("Load balancer stopped")
|
||||
|
||||
logger.info("Shutting down …")
|
||||
from app.agents.providers_v2 import cleanup_client
|
||||
from app.services.enrichment import enrichment_engine
|
||||
await cleanup_client()
|
||||
@@ -78,13 +51,15 @@ async def lifespan(app: FastAPI):
|
||||
await dispose_db()
|
||||
|
||||
|
||||
# Create FastAPI application
|
||||
app = FastAPI(
|
||||
title="ThreatHunt API",
|
||||
description="Analyst-assist threat hunting platform powered by Wile & Roadrunner LLM cluster",
|
||||
version=settings.APP_VERSION,
|
||||
version="0.3.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
@@ -104,12 +79,16 @@ app.include_router(enrichment_router)
|
||||
app.include_router(correlation_router)
|
||||
app.include_router(reports_router)
|
||||
app.include_router(keywords_router)
|
||||
app.include_router(analysis_router)
|
||||
app.include_router(network_router)
|
||||
app.include_router(analysis_router)
|
||||
app.include_router(cases_router)
|
||||
app.include_router(alerts_router)
|
||||
app.include_router(notebooks_router)
|
||||
|
||||
|
||||
@app.get("/", tags=["health"])
|
||||
async def root():
|
||||
"""API health check."""
|
||||
return {
|
||||
"service": "ThreatHunt API",
|
||||
"version": settings.APP_VERSION,
|
||||
|
||||
464
backend/app/services/analyzers.py
Normal file
@@ -0,0 +1,464 @@
|
||||
"""Pluggable Analyzer Framework for ThreatHunt.
|
||||
|
||||
Each analyzer implements a simple protocol:
|
||||
- name / description properties
|
||||
- async analyze(rows, config) -> list[AlertCandidate]
|
||||
|
||||
The AnalyzerRegistry discovers and runs all enabled analyzers against
|
||||
a dataset, producing alert candidates that the alert system can persist.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import Counter, defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional, Sequence
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ── Alert Candidate DTO ──────────────────────────────────────────────
|
||||
|
||||
|
||||
@dataclass
|
||||
class AlertCandidate:
|
||||
"""A single finding from an analyzer, before it becomes a persisted Alert."""
|
||||
analyzer: str
|
||||
title: str
|
||||
severity: str # critical | high | medium | low | info
|
||||
description: str
|
||||
evidence: list[dict] = field(default_factory=list) # [{row_index, field, value, ...}]
|
||||
mitre_technique: Optional[str] = None
|
||||
tags: list[str] = field(default_factory=list)
|
||||
score: float = 0.0 # 0-100
|
||||
|
||||
|
||||
# ── Base Analyzer ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class BaseAnalyzer(ABC):
|
||||
"""Interface every analyzer must implement."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str: ...
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(self) -> str: ...
|
||||
|
||||
@abstractmethod
|
||||
async def analyze(
|
||||
self, rows: list[dict[str, Any]], config: dict[str, Any] | None = None
|
||||
) -> list[AlertCandidate]: ...
|
||||
|
||||
|
||||
# ── Built-in Analyzers ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class EntropyAnalyzer(BaseAnalyzer):
|
||||
"""Detects high-entropy strings (encoded payloads, obfuscated commands)."""
|
||||
|
||||
name = "entropy"
|
||||
description = "Flags fields with high Shannon entropy (possible encoding/obfuscation)"
|
||||
|
||||
ENTROPY_FIELDS = [
|
||||
"command_line", "commandline", "process_command_line", "cmdline",
|
||||
"powershell_command", "script_block", "url", "uri", "path",
|
||||
"file_path", "target_filename", "query", "dns_query",
|
||||
]
|
||||
DEFAULT_THRESHOLD = 4.5
|
||||
|
||||
@staticmethod
|
||||
def _shannon(s: str) -> float:
|
||||
if not s or len(s) < 8:
|
||||
return 0.0
|
||||
freq = Counter(s)
|
||||
length = len(s)
|
||||
return -sum((c / length) * math.log2(c / length) for c in freq.values())
|
||||
|
||||
async def analyze(self, rows, config=None):
|
||||
config = config or {}
|
||||
threshold = config.get("entropy_threshold", self.DEFAULT_THRESHOLD)
|
||||
min_length = config.get("min_length", 20)
|
||||
alerts: list[AlertCandidate] = []
|
||||
|
||||
for idx, row in enumerate(rows):
|
||||
for field_name in self.ENTROPY_FIELDS:
|
||||
val = str(row.get(field_name, ""))
|
||||
if len(val) < min_length:
|
||||
continue
|
||||
ent = self._shannon(val)
|
||||
if ent >= threshold:
|
||||
sev = "critical" if ent > 5.5 else "high" if ent > 5.0 else "medium"
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"High-entropy string in {field_name}",
|
||||
severity=sev,
|
||||
description=f"Shannon entropy {ent:.2f} (threshold {threshold}) in row {idx}, field '{field_name}'",
|
||||
evidence=[{"row_index": idx, "field": field_name, "value": val[:200], "entropy": round(ent, 3)}],
|
||||
mitre_technique="T1027", # Obfuscated Files or Information
|
||||
tags=["obfuscation", "entropy"],
|
||||
score=min(100, ent * 18),
|
||||
))
|
||||
return alerts
|
||||
|
||||
|
||||
class SuspiciousCommandAnalyzer(BaseAnalyzer):
|
||||
"""Detects known-bad command patterns (credential dumping, lateral movement, persistence)."""
|
||||
|
||||
name = "suspicious_commands"
|
||||
description = "Flags processes executing known-suspicious command patterns"
|
||||
|
||||
PATTERNS: list[tuple[str, str, str, str]] = [
|
||||
# (regex, title, severity, mitre_technique)
|
||||
(r"mimikatz|sekurlsa|lsadump|kerberos::list", "Mimikatz / Credential Dumping", "critical", "T1003"),
|
||||
(r"(?i)-enc\s+[A-Za-z0-9+/=]{40,}", "Encoded PowerShell command", "high", "T1059.001"),
|
||||
(r"(?i)invoke-(mimikatz|expression|webrequest|shellcode)", "Suspicious PowerShell Invoke", "high", "T1059.001"),
|
||||
(r"(?i)net\s+(user|localgroup|group)\s+/add", "Local account creation", "high", "T1136.001"),
|
||||
(r"(?i)schtasks\s+/create", "Scheduled task creation", "medium", "T1053.005"),
|
||||
(r"(?i)reg\s+add\s+.*\\run", "Registry Run key persistence", "high", "T1547.001"),
|
||||
(r"(?i)wmic\s+.*(process\s+call|shadowcopy\s+delete)", "WMI abuse / shadow copy deletion", "critical", "T1047"),
|
||||
(r"(?i)psexec|winrm|wmic\s+/node:", "Lateral movement tool", "high", "T1021"),
|
||||
(r"(?i)certutil\s+-urlcache", "Certutil download (LOLBin)", "high", "T1105"),
|
||||
(r"(?i)bitsadmin\s+/transfer", "BITSAdmin download", "medium", "T1197"),
|
||||
(r"(?i)vssadmin\s+delete\s+shadows", "VSS shadow deletion (ransomware)", "critical", "T1490"),
|
||||
(r"(?i)bcdedit.*recoveryenabled.*no", "Boot config tamper (ransomware)", "critical", "T1490"),
|
||||
(r"(?i)attrib\s+\+h\s+\+s", "Hidden file attribute set", "low", "T1564.001"),
|
||||
(r"(?i)netsh\s+advfirewall\s+.*disable", "Firewall disabled", "high", "T1562.004"),
|
||||
(r"(?i)whoami\s*/priv", "Privilege enumeration", "medium", "T1033"),
|
||||
(r"(?i)nltest\s+/dclist", "Domain controller enumeration", "medium", "T1018"),
|
||||
(r"(?i)dsquery|ldapsearch|adfind", "Active Directory enumeration", "medium", "T1087.002"),
|
||||
(r"(?i)procdump.*-ma\s+lsass", "LSASS memory dump", "critical", "T1003.001"),
|
||||
(r"(?i)rundll32.*comsvcs.*MiniDump", "LSASS dump via comsvcs", "critical", "T1003.001"),
|
||||
]
|
||||
|
||||
CMD_FIELDS = [
|
||||
"command_line", "commandline", "process_command_line", "cmdline",
|
||||
"parent_command_line", "powershell_command",
|
||||
]
|
||||
|
||||
async def analyze(self, rows, config=None):
|
||||
alerts: list[AlertCandidate] = []
|
||||
compiled = [(re.compile(p, re.IGNORECASE), t, s, m) for p, t, s, m in self.PATTERNS]
|
||||
|
||||
for idx, row in enumerate(rows):
|
||||
for fld in self.CMD_FIELDS:
|
||||
val = str(row.get(fld, ""))
|
||||
if len(val) < 3:
|
||||
continue
|
||||
for pattern, title, sev, mitre in compiled:
|
||||
if pattern.search(val):
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=title,
|
||||
severity=sev,
|
||||
description=f"Suspicious command pattern in row {idx}: {val[:200]}",
|
||||
evidence=[{"row_index": idx, "field": fld, "value": val[:300]}],
|
||||
mitre_technique=mitre,
|
||||
tags=["command", "suspicious"],
|
||||
score={"critical": 95, "high": 80, "medium": 60, "low": 30}.get(sev, 50),
|
||||
))
|
||||
return alerts
|
||||
|
||||
|
||||
class NetworkAnomalyAnalyzer(BaseAnalyzer):
|
||||
"""Detects anomalous network patterns (beaconing, unusual ports, large transfers)."""
|
||||
|
||||
name = "network_anomaly"
|
||||
description = "Flags anomalous network behavior (beaconing, unusual ports, large transfers)"
|
||||
|
||||
SUSPICIOUS_PORTS = {4444, 5555, 6666, 8888, 9999, 1234, 31337, 12345, 54321, 1337}
|
||||
C2_PORTS = {443, 8443, 8080, 4443, 9443}
|
||||
|
||||
async def analyze(self, rows, config=None):
|
||||
config = config or {}
|
||||
alerts: list[AlertCandidate] = []
|
||||
|
||||
# Track destination IP frequency for beaconing detection
|
||||
dst_freq: dict[str, list[int]] = defaultdict(list)
|
||||
port_hits: list[tuple[int, str, int]] = []
|
||||
|
||||
for idx, row in enumerate(rows):
|
||||
dst_ip = str(row.get("dst_ip", row.get("destination_ip", row.get("dest_ip", ""))))
|
||||
dst_port = row.get("dst_port", row.get("destination_port", row.get("dest_port", "")))
|
||||
|
||||
if dst_ip and dst_ip != "":
|
||||
dst_freq[dst_ip].append(idx)
|
||||
|
||||
if dst_port:
|
||||
try:
|
||||
port_num = int(dst_port)
|
||||
if port_num in self.SUSPICIOUS_PORTS:
|
||||
port_hits.append((idx, dst_ip, port_num))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Large transfer detection
|
||||
bytes_val = row.get("bytes_sent", row.get("bytes_out", row.get("sent_bytes", 0)))
|
||||
try:
|
||||
if int(bytes_val or 0) > config.get("large_transfer_threshold", 10_000_000):
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title="Large data transfer detected",
|
||||
severity="medium",
|
||||
description=f"Row {idx}: {bytes_val} bytes sent to {dst_ip}",
|
||||
evidence=[{"row_index": idx, "dst_ip": dst_ip, "bytes": str(bytes_val)}],
|
||||
mitre_technique="T1048",
|
||||
tags=["exfiltration", "network"],
|
||||
score=65,
|
||||
))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Beaconing: IPs contacted more than threshold times
|
||||
beacon_thresh = config.get("beacon_threshold", 20)
|
||||
for ip, indices in dst_freq.items():
|
||||
if len(indices) >= beacon_thresh:
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"Possible beaconing to {ip}",
|
||||
severity="high",
|
||||
description=f"Destination {ip} contacted {len(indices)} times (threshold: {beacon_thresh})",
|
||||
evidence=[{"dst_ip": ip, "contact_count": len(indices), "sample_rows": indices[:10]}],
|
||||
mitre_technique="T1071",
|
||||
tags=["beaconing", "c2", "network"],
|
||||
score=min(95, 50 + len(indices)),
|
||||
))
|
||||
|
||||
# Suspicious ports
|
||||
for idx, ip, port in port_hits:
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"Connection on suspicious port {port}",
|
||||
severity="medium",
|
||||
description=f"Row {idx}: connection to {ip}:{port}",
|
||||
evidence=[{"row_index": idx, "dst_ip": ip, "dst_port": port}],
|
||||
mitre_technique="T1571",
|
||||
tags=["suspicious_port", "network"],
|
||||
score=55,
|
||||
))
|
||||
|
||||
return alerts
|
||||
|
||||
|
||||
class FrequencyAnomalyAnalyzer(BaseAnalyzer):
|
||||
"""Detects statistically rare values that may indicate anomalies."""
|
||||
|
||||
name = "frequency_anomaly"
|
||||
description = "Flags statistically rare field values (potential anomalies)"
|
||||
|
||||
FIELDS_TO_CHECK = [
|
||||
"process_name", "image_name", "parent_process_name",
|
||||
"user", "username", "user_name",
|
||||
"event_type", "action", "status",
|
||||
]
|
||||
|
||||
async def analyze(self, rows, config=None):
|
||||
config = config or {}
|
||||
rarity_threshold = config.get("rarity_threshold", 0.01) # <1% occurrence
|
||||
min_rows = config.get("min_rows", 50)
|
||||
alerts: list[AlertCandidate] = []
|
||||
|
||||
if len(rows) < min_rows:
|
||||
return alerts
|
||||
|
||||
for fld in self.FIELDS_TO_CHECK:
|
||||
values = [str(row.get(fld, "")) for row in rows if row.get(fld)]
|
||||
if not values:
|
||||
continue
|
||||
counts = Counter(values)
|
||||
total = len(values)
|
||||
|
||||
for val, cnt in counts.items():
|
||||
pct = cnt / total
|
||||
if pct <= rarity_threshold and cnt <= 3:
|
||||
# Find row indices
|
||||
indices = [i for i, r in enumerate(rows) if str(r.get(fld, "")) == val]
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"Rare {fld}: {val[:80]}",
|
||||
severity="low",
|
||||
description=f"'{val}' appears {cnt}/{total} times ({pct:.2%}) in field '{fld}'",
|
||||
evidence=[{"field": fld, "value": val[:200], "count": cnt, "total": total, "rows": indices[:5]}],
|
||||
tags=["anomaly", "rare"],
|
||||
score=max(20, 50 - (pct * 5000)),
|
||||
))
|
||||
|
||||
return alerts
|
||||
|
||||
|
||||
class AuthAnomalyAnalyzer(BaseAnalyzer):
|
||||
"""Detects authentication anomalies (brute force, unusual logon types)."""
|
||||
|
||||
name = "auth_anomaly"
|
||||
description = "Flags authentication anomalies (failed logins, unusual logon types)"
|
||||
|
||||
async def analyze(self, rows, config=None):
|
||||
config = config or {}
|
||||
alerts: list[AlertCandidate] = []
|
||||
|
||||
# Track failed logins per user
|
||||
failed_by_user: dict[str, list[int]] = defaultdict(list)
|
||||
logon_types: dict[str, list[int]] = defaultdict(list)
|
||||
|
||||
for idx, row in enumerate(rows):
|
||||
event_type = str(row.get("event_type", row.get("action", ""))).lower()
|
||||
status = str(row.get("status", row.get("result", ""))).lower()
|
||||
user = str(row.get("username", row.get("user", row.get("user_name", ""))))
|
||||
logon_type = str(row.get("logon_type", ""))
|
||||
|
||||
if "logon" in event_type or "auth" in event_type or "login" in event_type:
|
||||
if "fail" in status or "4625" in str(row.get("event_id", "")):
|
||||
if user:
|
||||
failed_by_user[user].append(idx)
|
||||
|
||||
if logon_type in ("3", "10"): # Network/RemoteInteractive
|
||||
logon_types[logon_type].append(idx)
|
||||
|
||||
# Brute force: >5 failed logins for same user
|
||||
brute_thresh = config.get("brute_force_threshold", 5)
|
||||
for user, indices in failed_by_user.items():
|
||||
if len(indices) >= brute_thresh:
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"Possible brute force: {user}",
|
||||
severity="high",
|
||||
description=f"User '{user}' had {len(indices)} failed logins",
|
||||
evidence=[{"user": user, "failed_count": len(indices), "rows": indices[:10]}],
|
||||
mitre_technique="T1110",
|
||||
tags=["brute_force", "authentication"],
|
||||
score=min(90, 50 + len(indices) * 3),
|
||||
))
|
||||
|
||||
# Unusual logon types
|
||||
for ltype, indices in logon_types.items():
|
||||
label = "Network logon (Type 3)" if ltype == "3" else "Remote Desktop (Type 10)"
|
||||
if len(indices) >= 3:
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"{label} detected",
|
||||
severity="medium" if ltype == "3" else "high",
|
||||
description=f"{len(indices)} {label} events detected",
|
||||
evidence=[{"logon_type": ltype, "count": len(indices), "rows": indices[:10]}],
|
||||
mitre_technique="T1021",
|
||||
tags=["authentication", "lateral_movement"],
|
||||
score=55 if ltype == "3" else 70,
|
||||
))
|
||||
|
||||
return alerts
|
||||
|
||||
|
||||
class PersistenceAnalyzer(BaseAnalyzer):
|
||||
"""Detects persistence mechanisms (registry keys, services, scheduled tasks)."""
|
||||
|
||||
name = "persistence"
|
||||
description = "Flags persistence mechanism installations"
|
||||
|
||||
REGISTRY_PATTERNS = [
|
||||
(r"(?i)\\CurrentVersion\\Run", "Run key persistence", "T1547.001"),
|
||||
(r"(?i)\\Services\\", "Service installation", "T1543.003"),
|
||||
(r"(?i)\\Winlogon\\", "Winlogon persistence", "T1547.004"),
|
||||
(r"(?i)\\Image File Execution Options\\", "IFEO debugger persistence", "T1546.012"),
|
||||
(r"(?i)\\Explorer\\Shell Folders", "Shell folder hijack", "T1547.001"),
|
||||
]
|
||||
|
||||
async def analyze(self, rows, config=None):
|
||||
alerts: list[AlertCandidate] = []
|
||||
compiled = [(re.compile(p), t, m) for p, t, m in self.REGISTRY_PATTERNS]
|
||||
|
||||
for idx, row in enumerate(rows):
|
||||
# Check registry paths
|
||||
reg_path = str(row.get("registry_key", row.get("target_object", row.get("registry_path", ""))))
|
||||
for pattern, title, mitre in compiled:
|
||||
if pattern.search(reg_path):
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=title,
|
||||
severity="high",
|
||||
description=f"Row {idx}: {reg_path[:200]}",
|
||||
evidence=[{"row_index": idx, "registry_key": reg_path[:300]}],
|
||||
mitre_technique=mitre,
|
||||
tags=["persistence", "registry"],
|
||||
score=75,
|
||||
))
|
||||
|
||||
# Check for service creation events
|
||||
event_type = str(row.get("event_type", "")).lower()
|
||||
if "service" in event_type and "creat" in event_type:
|
||||
svc_name = row.get("service_name", row.get("target_filename", "unknown"))
|
||||
alerts.append(AlertCandidate(
|
||||
analyzer=self.name,
|
||||
title=f"Service created: {svc_name}",
|
||||
severity="medium",
|
||||
description=f"Row {idx}: New service '{svc_name}' created",
|
||||
evidence=[{"row_index": idx, "service_name": str(svc_name)}],
|
||||
mitre_technique="T1543.003",
|
||||
tags=["persistence", "service"],
|
||||
score=60,
|
||||
))
|
||||
|
||||
return alerts
|
||||
|
||||
|
||||
# ── Analyzer Registry ────────────────────────────────────────────────
|
||||
|
||||
|
||||
_ALL_ANALYZERS: list[BaseAnalyzer] = [
|
||||
EntropyAnalyzer(),
|
||||
SuspiciousCommandAnalyzer(),
|
||||
NetworkAnomalyAnalyzer(),
|
||||
FrequencyAnomalyAnalyzer(),
|
||||
AuthAnomalyAnalyzer(),
|
||||
PersistenceAnalyzer(),
|
||||
]
|
||||
|
||||
|
||||
def get_available_analyzers() -> list[dict[str, str]]:
|
||||
"""Return metadata about all registered analyzers."""
|
||||
return [{"name": a.name, "description": a.description} for a in _ALL_ANALYZERS]
|
||||
|
||||
|
||||
def get_analyzer(name: str) -> BaseAnalyzer | None:
|
||||
"""Get an analyzer by name."""
|
||||
for a in _ALL_ANALYZERS:
|
||||
if a.name == name:
|
||||
return a
|
||||
return None
|
||||
|
||||
|
||||
async def run_all_analyzers(
|
||||
rows: list[dict[str, Any]],
|
||||
enabled: list[str] | None = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
) -> list[AlertCandidate]:
|
||||
"""Run all (or selected) analyzers and return combined alert candidates.
|
||||
|
||||
Args:
|
||||
rows: Flat list of row dicts (normalized_data or data from DatasetRow).
|
||||
enabled: Optional list of analyzer names to run. Runs all if None.
|
||||
config: Optional config overrides passed to each analyzer.
|
||||
|
||||
Returns:
|
||||
Combined list of AlertCandidate from all analyzers, sorted by score desc.
|
||||
"""
|
||||
config = config or {}
|
||||
results: list[AlertCandidate] = []
|
||||
|
||||
for analyzer in _ALL_ANALYZERS:
|
||||
if enabled and analyzer.name not in enabled:
|
||||
continue
|
||||
try:
|
||||
candidates = await analyzer.analyze(rows, config)
|
||||
results.extend(candidates)
|
||||
logger.info("Analyzer %s produced %d alerts", analyzer.name, len(candidates))
|
||||
except Exception:
|
||||
logger.exception("Analyzer %s failed", analyzer.name)
|
||||
|
||||
# Sort by score descending
|
||||
results.sort(key=lambda a: a.score, reverse=True)
|
||||
return results
|
||||
322
backend/app/services/llm_analysis.py
Normal file
@@ -0,0 +1,322 @@
|
||||
"""LLM-powered dataset analysis — replaces manual IOC enrichment.
|
||||
|
||||
Loads dataset rows server-side, builds a concise summary, and sends it
|
||||
to Wile (70B heavy) or Roadrunner (fast) for threat analysis.
|
||||
Supports both single-dataset and hunt-wide analysis.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from collections import Counter, defaultdict
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.config import settings
|
||||
from app.agents.providers_v2 import OllamaProvider
|
||||
from app.agents.router import TaskType, task_router
|
||||
from app.services.sans_rag import sans_rag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ── Request / Response models ─────────────────────────────────────────
|
||||
|
||||
|
||||
class AnalysisRequest(BaseModel):
|
||||
"""Request for LLM-powered analysis of a dataset."""
|
||||
dataset_id: Optional[str] = None
|
||||
hunt_id: Optional[str] = None
|
||||
question: str = Field(
|
||||
default="Perform a comprehensive threat analysis of this dataset. "
|
||||
"Identify anomalies, suspicious patterns, potential IOCs, and recommend "
|
||||
"next steps for the analyst.",
|
||||
description="Specific question or general analysis request",
|
||||
)
|
||||
mode: str = Field(default="deep", description="quick | deep")
|
||||
focus: Optional[str] = Field(
|
||||
None,
|
||||
description="Focus area: threats, anomalies, lateral_movement, exfil, persistence, recon",
|
||||
)
|
||||
|
||||
|
||||
class AnalysisResult(BaseModel):
|
||||
"""LLM analysis result."""
|
||||
analysis: str = Field(..., description="Full analysis text (markdown)")
|
||||
confidence: float = Field(default=0.0, description="0-1 confidence")
|
||||
key_findings: list[str] = Field(default_factory=list)
|
||||
iocs_identified: list[dict] = Field(default_factory=list)
|
||||
recommended_actions: list[str] = Field(default_factory=list)
|
||||
mitre_techniques: list[str] = Field(default_factory=list)
|
||||
risk_score: int = Field(default=0, description="0-100 risk score")
|
||||
model_used: str = ""
|
||||
node_used: str = ""
|
||||
latency_ms: int = 0
|
||||
rows_analyzed: int = 0
|
||||
dataset_summary: str = ""
|
||||
|
||||
|
||||
# ── Analysis prompts ──────────────────────────────────────────────────
|
||||
|
||||
ANALYSIS_SYSTEM = """You are an expert threat hunter and incident response analyst.
|
||||
You are analyzing CSV log data from forensic tools (Velociraptor, Sysmon, etc.).
|
||||
|
||||
Your task: Perform deep threat analysis of the data provided and produce actionable findings.
|
||||
|
||||
RESPOND WITH VALID JSON ONLY:
|
||||
{
|
||||
"analysis": "Detailed markdown analysis with headers and bullet points",
|
||||
"confidence": 0.85,
|
||||
"key_findings": ["Finding 1", "Finding 2"],
|
||||
"iocs_identified": [{"type": "ip", "value": "1.2.3.4", "context": "C2 traffic"}],
|
||||
"recommended_actions": ["Action 1", "Action 2"],
|
||||
"mitre_techniques": ["T1059.001 - PowerShell", "T1071 - Application Layer Protocol"],
|
||||
"risk_score": 65
|
||||
}
|
||||
"""
|
||||
|
||||
FOCUS_PROMPTS = {
|
||||
"threats": "Focus on identifying active threats, malware indicators, and attack patterns.",
|
||||
"anomalies": "Focus on statistical anomalies, outliers, and unusual behavior patterns.",
|
||||
"lateral_movement": "Focus on evidence of lateral movement: PsExec, WMI, RDP, SMB, pass-the-hash.",
|
||||
"exfil": "Focus on data exfiltration indicators: large transfers, DNS tunneling, unusual destinations.",
|
||||
"persistence": "Focus on persistence mechanisms: scheduled tasks, services, registry, startup items.",
|
||||
"recon": "Focus on reconnaissance activity: scanning, enumeration, discovery commands.",
|
||||
}
|
||||
|
||||
|
||||
# ── Data summarizer ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
def summarize_dataset_rows(
|
||||
rows: list[dict],
|
||||
columns: list[str] | None = None,
|
||||
max_sample: int = 20,
|
||||
max_chars: int = 6000,
|
||||
) -> str:
|
||||
"""Build a concise text summary of dataset rows for LLM consumption.
|
||||
|
||||
Includes:
|
||||
- Column headers and types
|
||||
- Statistical summary (unique values, top values per column)
|
||||
- Sample rows (first N)
|
||||
- Detected patterns of interest
|
||||
"""
|
||||
if not rows:
|
||||
return "Empty dataset — no rows to analyze."
|
||||
|
||||
cols = columns or list(rows[0].keys())
|
||||
n_rows = len(rows)
|
||||
|
||||
parts: list[str] = []
|
||||
parts.append(f"## Dataset Summary: {n_rows} rows, {len(cols)} columns")
|
||||
parts.append(f"Columns: {', '.join(cols)}")
|
||||
|
||||
# Per-column stats
|
||||
parts.append("\n### Column Statistics:")
|
||||
for col in cols[:30]: # limit to first 30 cols
|
||||
values = [str(r.get(col, "")) for r in rows if r.get(col) not in (None, "", "N/A")]
|
||||
if not values:
|
||||
continue
|
||||
unique = len(set(values))
|
||||
counter = Counter(values)
|
||||
top3 = counter.most_common(3)
|
||||
top_str = ", ".join(f"{v} ({c}x)" for v, c in top3)
|
||||
parts.append(f"- **{col}**: {len(values)} non-null, {unique} unique. Top: {top_str}")
|
||||
|
||||
# Sample rows
|
||||
sample = rows[:max_sample]
|
||||
parts.append(f"\n### Sample Rows (first {len(sample)}):")
|
||||
for i, row in enumerate(sample):
|
||||
row_str = " | ".join(f"{k}={v}" for k, v in row.items() if v not in (None, "", "N/A"))
|
||||
parts.append(f"{i+1}. {row_str}")
|
||||
|
||||
# Detect interesting patterns
|
||||
patterns: list[str] = []
|
||||
all_cmds = [str(r.get("command_line", "")).lower() for r in rows if r.get("command_line")]
|
||||
sus_cmds = [c for c in all_cmds if any(
|
||||
k in c for k in ["powershell -enc", "certutil", "bitsadmin", "mshta",
|
||||
"regsvr32", "invoke-", "mimikatz", "psexec"]
|
||||
)]
|
||||
if sus_cmds:
|
||||
patterns.append(f"⚠️ {len(sus_cmds)} suspicious command lines detected")
|
||||
|
||||
all_ips = [str(r.get("dst_ip", "")) for r in rows if r.get("dst_ip")]
|
||||
ext_ips = [ip for ip in all_ips if ip and not ip.startswith(("10.", "192.168.", "172.", "127."))]
|
||||
if ext_ips:
|
||||
unique_ext = len(set(ext_ips))
|
||||
patterns.append(f"🌐 {unique_ext} unique external destination IPs")
|
||||
|
||||
if patterns:
|
||||
parts.append("\n### Detected Patterns:")
|
||||
for p in patterns:
|
||||
parts.append(f"- {p}")
|
||||
|
||||
text = "\n".join(parts)
|
||||
if len(text) > max_chars:
|
||||
text = text[:max_chars] + "\n... (truncated)"
|
||||
return text
|
||||
|
||||
|
||||
# ── LLM analysis engine ──────────────────────────────────────────────
|
||||
|
||||
|
||||
async def run_llm_analysis(
|
||||
rows: list[dict],
|
||||
request: AnalysisRequest,
|
||||
dataset_name: str = "unknown",
|
||||
) -> AnalysisResult:
|
||||
"""Run LLM analysis on dataset rows."""
|
||||
start = time.monotonic()
|
||||
|
||||
# Build summary
|
||||
summary = summarize_dataset_rows(rows)
|
||||
|
||||
# Route to appropriate model
|
||||
task_type = TaskType.DEEP_ANALYSIS if request.mode == "deep" else TaskType.QUICK_CHAT
|
||||
decision = task_router.route(task_type)
|
||||
|
||||
# Build prompt
|
||||
focus_text = FOCUS_PROMPTS.get(request.focus or "", "")
|
||||
prompt = f"""Analyze the following forensic dataset from '{dataset_name}'.
|
||||
|
||||
{focus_text}
|
||||
|
||||
Analyst question: {request.question}
|
||||
|
||||
{summary}
|
||||
"""
|
||||
|
||||
# Enrich with SANS RAG
|
||||
try:
|
||||
rag_context = await sans_rag.enrich_prompt(
|
||||
request.question,
|
||||
investigation_context=f"Analyzing {len(rows)} rows from {dataset_name}",
|
||||
)
|
||||
if rag_context:
|
||||
prompt = f"{prompt}\n\n{rag_context}"
|
||||
except Exception as e:
|
||||
logger.warning(f"SANS RAG enrichment failed: {e}")
|
||||
|
||||
# Call LLM
|
||||
provider = task_router.get_provider(decision)
|
||||
messages = [
|
||||
{"role": "system", "content": ANALYSIS_SYSTEM},
|
||||
{"role": "user", "content": prompt},
|
||||
]
|
||||
|
||||
try:
|
||||
raw = await asyncio.wait_for(
|
||||
provider.generate(
|
||||
prompt=prompt,
|
||||
system=ANALYSIS_SYSTEM,
|
||||
max_tokens=settings.AGENT_MAX_TOKENS * 2, # longer for analysis
|
||||
temperature=0.3,
|
||||
),
|
||||
timeout=300, # 5 min hard limit
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("LLM analysis timed out after 300s")
|
||||
return AnalysisResult(
|
||||
analysis="Analysis timed out after 5 minutes. Try a smaller dataset or 'quick' mode.",
|
||||
model_used=decision.model,
|
||||
node_used=decision.node,
|
||||
latency_ms=int((time.monotonic() - start) * 1000),
|
||||
rows_analyzed=len(rows),
|
||||
dataset_summary=summary,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"LLM analysis failed: {e}")
|
||||
return AnalysisResult(
|
||||
analysis=f"Analysis failed: {str(e)}",
|
||||
model_used=decision.model,
|
||||
node_used=decision.node,
|
||||
latency_ms=int((time.monotonic() - start) * 1000),
|
||||
rows_analyzed=len(rows),
|
||||
dataset_summary=summary,
|
||||
)
|
||||
|
||||
elapsed = int((time.monotonic() - start) * 1000)
|
||||
|
||||
# Parse JSON response
|
||||
result = _parse_analysis(raw)
|
||||
result.model_used = decision.model
|
||||
result.node_used = decision.node
|
||||
result.latency_ms = elapsed
|
||||
result.rows_analyzed = len(rows)
|
||||
result.dataset_summary = summary
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _parse_analysis(raw) -> AnalysisResult:
|
||||
"""Try to parse LLM output as JSON, fall back to plain text.
|
||||
|
||||
raw may be:
|
||||
- A dict from OllamaProvider.generate() with key "response" containing LLM text
|
||||
- A plain string from other providers
|
||||
"""
|
||||
# Ollama provider returns {"response": "<llm text>", "model": ..., ...}
|
||||
if isinstance(raw, dict):
|
||||
text = raw.get("response") or raw.get("analysis") or str(raw)
|
||||
logger.info(f"_parse_analysis: extracted text from dict, len={len(text)}, first 200 chars: {text[:200]}")
|
||||
else:
|
||||
text = str(raw)
|
||||
logger.info(f"_parse_analysis: raw is str, len={len(text)}, first 200 chars: {text[:200]}")
|
||||
|
||||
text = text.strip()
|
||||
|
||||
# Strip markdown code fences
|
||||
if text.startswith("```"):
|
||||
lines = text.split("\n")
|
||||
lines = [l for l in lines if not l.strip().startswith("```")]
|
||||
text = "\n".join(lines).strip()
|
||||
|
||||
# Try direct JSON parse first
|
||||
for candidate in _extract_json_candidates(text):
|
||||
try:
|
||||
data = json.loads(candidate)
|
||||
if isinstance(data, dict):
|
||||
logger.info(f"_parse_analysis: parsed JSON OK, keys={list(data.keys())}")
|
||||
return AnalysisResult(
|
||||
analysis=data.get("analysis", text),
|
||||
confidence=float(data.get("confidence", 0.5)),
|
||||
key_findings=data.get("key_findings", []),
|
||||
iocs_identified=data.get("iocs_identified", []),
|
||||
recommended_actions=data.get("recommended_actions", []),
|
||||
mitre_techniques=data.get("mitre_techniques", []),
|
||||
risk_score=int(data.get("risk_score", 0)),
|
||||
)
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning(f"_parse_analysis: JSON parse failed: {e}, candidate len={len(candidate)}, first 100: {candidate[:100]}")
|
||||
continue
|
||||
|
||||
# Fallback: plain text
|
||||
logger.warning(f"_parse_analysis: all JSON parse attempts failed, falling back to plain text")
|
||||
return AnalysisResult(
|
||||
analysis=text,
|
||||
confidence=0.5,
|
||||
)
|
||||
|
||||
|
||||
def _extract_json_candidates(text: str):
|
||||
"""Yield JSON candidate strings from text, trying progressively more aggressive extraction."""
|
||||
import re
|
||||
|
||||
# 1. The whole text as-is
|
||||
yield text
|
||||
|
||||
# 2. Find outermost { ... } block
|
||||
start = text.find("{")
|
||||
end = text.rfind("}")
|
||||
if start != -1 and end > start:
|
||||
block = text[start:end + 1]
|
||||
yield block
|
||||
|
||||
# 3. Try to fix common LLM JSON issues:
|
||||
# - trailing commas before ] or }
|
||||
fixed = re.sub(r',\s*([}\]])', r'\1', block)
|
||||
if fixed != block:
|
||||
yield fixed
|
||||
484
backend/app/services/mitre.py
Normal file
@@ -0,0 +1,484 @@
|
||||
"""
|
||||
MITRE ATT&CK mapping service.
|
||||
|
||||
Maps dataset events to ATT&CK techniques using pattern-based heuristics.
|
||||
Uses the enterprise-attack matrix (embedded patterns for offline use).
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from collections import Counter, defaultdict
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import Dataset, DatasetRow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ── ATT&CK Technique Patterns ────────────────────────────────────────
|
||||
# Subset of enterprise-attack techniques with detection patterns.
|
||||
# Each entry: (technique_id, name, tactic, patterns_list)
|
||||
|
||||
TECHNIQUE_PATTERNS: list[tuple[str, str, str, list[str]]] = [
|
||||
# Initial Access
|
||||
("T1566", "Phishing", "initial-access", [
|
||||
r"phish", r"\.hta\b", r"\.lnk\b", r"mshta\.exe", r"outlook.*attachment",
|
||||
]),
|
||||
("T1190", "Exploit Public-Facing Application", "initial-access", [
|
||||
r"exploit", r"CVE-\d{4}", r"vulnerability", r"webshell",
|
||||
]),
|
||||
|
||||
# Execution
|
||||
("T1059.001", "PowerShell", "execution", [
|
||||
r"powershell", r"pwsh", r"-enc\b", r"-encodedcommand",
|
||||
r"invoke-expression", r"iex\b", r"bypass\b.*execution",
|
||||
]),
|
||||
("T1059.003", "Windows Command Shell", "execution", [
|
||||
r"cmd\.exe", r"/c\s+", r"command\.com",
|
||||
]),
|
||||
("T1059.005", "Visual Basic", "execution", [
|
||||
r"wscript", r"cscript", r"\.vbs\b", r"\.vbe\b",
|
||||
]),
|
||||
("T1047", "Windows Management Instrumentation", "execution", [
|
||||
r"wmic\b", r"winmgmt", r"wmi\b",
|
||||
]),
|
||||
("T1053.005", "Scheduled Task", "execution", [
|
||||
r"schtasks", r"at\.exe", r"taskschd",
|
||||
]),
|
||||
("T1204", "User Execution", "execution", [
|
||||
r"user.*click", r"open.*attachment", r"macro",
|
||||
]),
|
||||
|
||||
# Persistence
|
||||
("T1547.001", "Registry Run Keys", "persistence", [
|
||||
r"CurrentVersion\\Run", r"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||
r"reg\s+add.*\\Run",
|
||||
]),
|
||||
("T1543.003", "Windows Service", "persistence", [
|
||||
r"sc\s+create", r"new-service", r"service.*install",
|
||||
]),
|
||||
("T1136", "Create Account", "persistence", [
|
||||
r"net\s+user\s+/add", r"new-localuser", r"useradd",
|
||||
]),
|
||||
("T1053.005", "Scheduled Task/Job", "persistence", [
|
||||
r"schtasks\s+/create", r"crontab",
|
||||
]),
|
||||
|
||||
# Privilege Escalation
|
||||
("T1548.002", "Bypass User Access Control", "privilege-escalation", [
|
||||
r"eventvwr", r"fodhelper", r"uac.*bypass", r"computerdefaults",
|
||||
]),
|
||||
("T1134", "Access Token Manipulation", "privilege-escalation", [
|
||||
r"token.*impersonat", r"runas", r"adjusttokenprivileges",
|
||||
]),
|
||||
|
||||
# Defense Evasion
|
||||
("T1070.001", "Clear Windows Event Logs", "defense-evasion", [
|
||||
r"wevtutil\s+cl", r"clear-eventlog", r"clearlog",
|
||||
]),
|
||||
("T1562.001", "Disable or Modify Tools", "defense-evasion", [
|
||||
r"tamper.*protection", r"disable.*defender", r"set-mppreference",
|
||||
r"disable.*firewall",
|
||||
]),
|
||||
("T1027", "Obfuscated Files or Information", "defense-evasion", [
|
||||
r"base64", r"-enc\b", r"certutil.*-decode", r"frombase64",
|
||||
]),
|
||||
("T1036", "Masquerading", "defense-evasion", [
|
||||
r"rename.*\.exe", r"masquerad", r"svchost.*unusual",
|
||||
]),
|
||||
("T1055", "Process Injection", "defense-evasion", [
|
||||
r"inject", r"createremotethread", r"ntcreatethreadex",
|
||||
r"virtualalloc", r"writeprocessmemory",
|
||||
]),
|
||||
|
||||
# Credential Access
|
||||
("T1003.001", "LSASS Memory", "credential-access", [
|
||||
r"mimikatz", r"sekurlsa", r"lsass", r"procdump.*lsass",
|
||||
]),
|
||||
("T1003.003", "NTDS", "credential-access", [
|
||||
r"ntds\.dit", r"vssadmin.*shadow", r"ntdsutil",
|
||||
]),
|
||||
("T1110", "Brute Force", "credential-access", [
|
||||
r"brute.*force", r"failed.*login.*\d{3,}", r"hydra", r"medusa",
|
||||
]),
|
||||
("T1558.003", "Kerberoasting", "credential-access", [
|
||||
r"kerberoast", r"invoke-kerberoast", r"GetUserSPNs",
|
||||
]),
|
||||
|
||||
# Discovery
|
||||
("T1087", "Account Discovery", "discovery", [
|
||||
r"net\s+user", r"net\s+localgroup", r"get-aduser",
|
||||
]),
|
||||
("T1082", "System Information Discovery", "discovery", [
|
||||
r"systeminfo", r"hostname", r"ver\b",
|
||||
]),
|
||||
("T1083", "File and Directory Discovery", "discovery", [
|
||||
r"dir\s+/s", r"tree\s+/f", r"get-childitem.*-recurse",
|
||||
]),
|
||||
("T1057", "Process Discovery", "discovery", [
|
||||
r"tasklist", r"get-process", r"ps\s+aux",
|
||||
]),
|
||||
("T1018", "Remote System Discovery", "discovery", [
|
||||
r"net\s+view", r"ping\s+-", r"arp\s+-a", r"nslookup",
|
||||
]),
|
||||
("T1016", "System Network Configuration Discovery", "discovery", [
|
||||
r"ipconfig", r"ifconfig", r"netstat",
|
||||
]),
|
||||
|
||||
# Lateral Movement
|
||||
("T1021.001", "Remote Desktop Protocol", "lateral-movement", [
|
||||
r"rdp\b", r"mstsc", r"3389", r"remote\s+desktop",
|
||||
]),
|
||||
("T1021.002", "SMB/Windows Admin Shares", "lateral-movement", [
|
||||
r"\\\\.*\\(c|admin)\$", r"psexec", r"smbclient", r"net\s+use",
|
||||
]),
|
||||
("T1021.006", "Windows Remote Management", "lateral-movement", [
|
||||
r"winrm", r"enter-pssession", r"invoke-command.*-computername",
|
||||
r"wsman", r"5985|5986",
|
||||
]),
|
||||
("T1570", "Lateral Tool Transfer", "lateral-movement", [
|
||||
r"copy.*\\\\", r"xcopy.*\\\\", r"robocopy",
|
||||
]),
|
||||
|
||||
# Collection
|
||||
("T1560", "Archive Collected Data", "collection", [
|
||||
r"compress-archive", r"7z\.exe", r"rar\s+a", r"tar\s+-[cz]",
|
||||
]),
|
||||
("T1005", "Data from Local System", "collection", [
|
||||
r"type\s+.*password", r"findstr.*password", r"select-string.*credential",
|
||||
]),
|
||||
|
||||
# Command and Control
|
||||
("T1071.001", "Web Protocols", "command-and-control", [
|
||||
r"http[s]?://\d+\.\d+\.\d+\.\d+", r"curl\b", r"wget\b",
|
||||
r"invoke-webrequest", r"beacon",
|
||||
]),
|
||||
("T1573", "Encrypted Channel", "command-and-control", [
|
||||
r"ssl\b", r"tls\b", r"encrypted.*tunnel", r"stunnel",
|
||||
]),
|
||||
("T1105", "Ingress Tool Transfer", "command-and-control", [
|
||||
r"certutil.*-urlcache", r"bitsadmin.*transfer",
|
||||
r"downloadfile", r"invoke-webrequest.*-outfile",
|
||||
]),
|
||||
("T1219", "Remote Access Software", "command-and-control", [
|
||||
r"teamviewer", r"anydesk", r"logmein", r"vnc",
|
||||
]),
|
||||
|
||||
# Exfiltration
|
||||
("T1048", "Exfiltration Over Alternative Protocol", "exfiltration", [
|
||||
r"dns.*tunnel", r"exfil", r"icmp.*tunnel",
|
||||
]),
|
||||
("T1041", "Exfiltration Over C2 Channel", "exfiltration", [
|
||||
r"upload.*c2", r"exfil.*http",
|
||||
]),
|
||||
("T1567", "Exfiltration Over Web Service", "exfiltration", [
|
||||
r"mega\.nz", r"dropbox", r"pastebin", r"transfer\.sh",
|
||||
]),
|
||||
|
||||
# Impact
|
||||
("T1486", "Data Encrypted for Impact", "impact", [
|
||||
r"ransomware", r"encrypt.*files", r"\.locked\b", r"ransom",
|
||||
]),
|
||||
("T1489", "Service Stop", "impact", [
|
||||
r"sc\s+stop", r"net\s+stop", r"stop-service",
|
||||
]),
|
||||
("T1529", "System Shutdown/Reboot", "impact", [
|
||||
r"shutdown\s+/[rs]", r"restart-computer",
|
||||
]),
|
||||
]
|
||||
|
||||
# Tactic display names and kill-chain order
|
||||
TACTIC_ORDER = [
|
||||
"initial-access", "execution", "persistence", "privilege-escalation",
|
||||
"defense-evasion", "credential-access", "discovery", "lateral-movement",
|
||||
"collection", "command-and-control", "exfiltration", "impact",
|
||||
]
|
||||
TACTIC_NAMES = {
|
||||
"initial-access": "Initial Access",
|
||||
"execution": "Execution",
|
||||
"persistence": "Persistence",
|
||||
"privilege-escalation": "Privilege Escalation",
|
||||
"defense-evasion": "Defense Evasion",
|
||||
"credential-access": "Credential Access",
|
||||
"discovery": "Discovery",
|
||||
"lateral-movement": "Lateral Movement",
|
||||
"collection": "Collection",
|
||||
"command-and-control": "Command and Control",
|
||||
"exfiltration": "Exfiltration",
|
||||
"impact": "Impact",
|
||||
}
|
||||
|
||||
|
||||
# ── Row fetcher ───────────────────────────────────────────────────────
|
||||
|
||||
async def _fetch_rows(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
limit: int = 5000,
|
||||
) -> list[dict[str, Any]]:
|
||||
q = select(DatasetRow).join(Dataset)
|
||||
if dataset_id:
|
||||
q = q.where(DatasetRow.dataset_id == dataset_id)
|
||||
elif hunt_id:
|
||||
q = q.where(Dataset.hunt_id == hunt_id)
|
||||
q = q.limit(limit)
|
||||
result = await db.execute(q)
|
||||
return [r.data for r in result.scalars().all()]
|
||||
|
||||
|
||||
# ── Main functions ────────────────────────────────────────────────────
|
||||
|
||||
async def map_to_attack(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Map dataset rows to MITRE ATT&CK techniques.
|
||||
Returns a matrix-style structure + evidence list.
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id, hunt_id)
|
||||
if not rows:
|
||||
return {"tactics": [], "techniques": [], "evidence": [], "coverage": {}, "total_rows": 0}
|
||||
|
||||
# Flatten all string values per row for matching
|
||||
row_texts: list[str] = []
|
||||
for row in rows:
|
||||
parts = []
|
||||
for v in row.values():
|
||||
if v is not None:
|
||||
parts.append(str(v).lower())
|
||||
row_texts.append(" ".join(parts))
|
||||
|
||||
# Match techniques
|
||||
technique_hits: dict[str, list[dict]] = defaultdict(list) # tech_id -> evidence rows
|
||||
technique_meta: dict[str, tuple[str, str]] = {} # tech_id -> (name, tactic)
|
||||
row_techniques: list[set[str]] = [set() for _ in rows]
|
||||
|
||||
for tech_id, tech_name, tactic, patterns in TECHNIQUE_PATTERNS:
|
||||
compiled = [re.compile(p, re.IGNORECASE) for p in patterns]
|
||||
technique_meta[tech_id] = (tech_name, tactic)
|
||||
for i, text in enumerate(row_texts):
|
||||
for pat in compiled:
|
||||
if pat.search(text):
|
||||
row_techniques[i].add(tech_id)
|
||||
if len(technique_hits[tech_id]) < 10: # limit evidence
|
||||
# find matching field
|
||||
matched_field = ""
|
||||
matched_value = ""
|
||||
for k, v in rows[i].items():
|
||||
if v and pat.search(str(v).lower()):
|
||||
matched_field = k
|
||||
matched_value = str(v)[:200]
|
||||
break
|
||||
technique_hits[tech_id].append({
|
||||
"row_index": i,
|
||||
"field": matched_field,
|
||||
"value": matched_value,
|
||||
"pattern": pat.pattern,
|
||||
})
|
||||
break # one pattern match per technique per row is enough
|
||||
|
||||
# Build tactic → technique structure
|
||||
tactic_techniques: dict[str, list[dict]] = defaultdict(list)
|
||||
for tech_id, evidence_list in technique_hits.items():
|
||||
name, tactic = technique_meta[tech_id]
|
||||
tactic_techniques[tactic].append({
|
||||
"id": tech_id,
|
||||
"name": name,
|
||||
"count": len(evidence_list),
|
||||
"evidence": evidence_list[:5],
|
||||
})
|
||||
|
||||
# Build ordered tactics list
|
||||
tactics = []
|
||||
for tactic_key in TACTIC_ORDER:
|
||||
techs = tactic_techniques.get(tactic_key, [])
|
||||
tactics.append({
|
||||
"id": tactic_key,
|
||||
"name": TACTIC_NAMES.get(tactic_key, tactic_key),
|
||||
"techniques": sorted(techs, key=lambda t: -t["count"]),
|
||||
"total_hits": sum(t["count"] for t in techs),
|
||||
})
|
||||
|
||||
# Coverage stats
|
||||
covered_tactics = sum(1 for t in tactics if t["total_hits"] > 0)
|
||||
total_technique_hits = sum(t["total_hits"] for t in tactics)
|
||||
|
||||
return {
|
||||
"tactics": tactics,
|
||||
"coverage": {
|
||||
"tactics_covered": covered_tactics,
|
||||
"tactics_total": len(TACTIC_ORDER),
|
||||
"techniques_matched": len(technique_hits),
|
||||
"total_evidence": total_technique_hits,
|
||||
},
|
||||
"total_rows": len(rows),
|
||||
}
|
||||
|
||||
|
||||
async def build_knowledge_graph(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build a knowledge graph connecting entities (hosts, users, processes, IPs)
|
||||
to MITRE techniques and tactics.
|
||||
Returns Cytoscape-compatible nodes + edges.
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id, hunt_id)
|
||||
if not rows:
|
||||
return {"nodes": [], "edges": [], "stats": {}}
|
||||
|
||||
# Extract entities
|
||||
entities: dict[str, set[str]] = defaultdict(set) # type -> set of values
|
||||
row_entity_map: list[list[tuple[str, str]]] = [] # per-row list of (type, value)
|
||||
|
||||
# Field name patterns for entity extraction
|
||||
HOST_FIELDS = re.compile(r"hostname|computer|host|machine", re.I)
|
||||
USER_FIELDS = re.compile(r"user|account|logon.*name|subject.*name", re.I)
|
||||
IP_FIELDS = re.compile(r"src.*ip|dst.*ip|ip.*addr|source.*ip|dest.*ip|remote.*addr", re.I)
|
||||
PROC_FIELDS = re.compile(r"process.*name|image|parent.*image|executable|command", re.I)
|
||||
|
||||
for row in rows:
|
||||
row_ents: list[tuple[str, str]] = []
|
||||
for k, v in row.items():
|
||||
if not v or str(v).strip() in ('', '-', 'N/A', 'None'):
|
||||
continue
|
||||
val = str(v).strip()
|
||||
if HOST_FIELDS.search(k):
|
||||
entities["host"].add(val)
|
||||
row_ents.append(("host", val))
|
||||
elif USER_FIELDS.search(k):
|
||||
entities["user"].add(val)
|
||||
row_ents.append(("user", val))
|
||||
elif IP_FIELDS.search(k):
|
||||
entities["ip"].add(val)
|
||||
row_ents.append(("ip", val))
|
||||
elif PROC_FIELDS.search(k):
|
||||
# Clean process name
|
||||
pname = val.split("\\")[-1].split("/")[-1][:60]
|
||||
entities["process"].add(pname)
|
||||
row_ents.append(("process", pname))
|
||||
row_entity_map.append(row_ents)
|
||||
|
||||
# Map rows to techniques
|
||||
row_texts = [" ".join(str(v).lower() for v in row.values() if v) for row in rows]
|
||||
row_techniques: list[set[str]] = [set() for _ in rows]
|
||||
tech_meta: dict[str, tuple[str, str]] = {}
|
||||
|
||||
for tech_id, tech_name, tactic, patterns in TECHNIQUE_PATTERNS:
|
||||
compiled = [re.compile(p, re.I) for p in patterns]
|
||||
tech_meta[tech_id] = (tech_name, tactic)
|
||||
for i, text in enumerate(row_texts):
|
||||
for pat in compiled:
|
||||
if pat.search(text):
|
||||
row_techniques[i].add(tech_id)
|
||||
break
|
||||
|
||||
# Build graph
|
||||
nodes: list[dict] = []
|
||||
edges: list[dict] = []
|
||||
node_ids: set[str] = set()
|
||||
edge_counter: Counter = Counter()
|
||||
|
||||
# Entity nodes
|
||||
TYPE_COLORS = {
|
||||
"host": "#3b82f6",
|
||||
"user": "#10b981",
|
||||
"ip": "#8b5cf6",
|
||||
"process": "#f59e0b",
|
||||
"technique": "#ef4444",
|
||||
"tactic": "#6366f1",
|
||||
}
|
||||
TYPE_SHAPES = {
|
||||
"host": "roundrectangle",
|
||||
"user": "ellipse",
|
||||
"ip": "diamond",
|
||||
"process": "hexagon",
|
||||
"technique": "tag",
|
||||
"tactic": "round-rectangle",
|
||||
}
|
||||
|
||||
for ent_type, values in entities.items():
|
||||
for val in list(values)[:50]: # limit nodes
|
||||
nid = f"{ent_type}:{val}"
|
||||
if nid not in node_ids:
|
||||
node_ids.add(nid)
|
||||
nodes.append({
|
||||
"data": {
|
||||
"id": nid,
|
||||
"label": val[:40],
|
||||
"type": ent_type,
|
||||
"color": TYPE_COLORS.get(ent_type, "#666"),
|
||||
"shape": TYPE_SHAPES.get(ent_type, "ellipse"),
|
||||
},
|
||||
})
|
||||
|
||||
# Technique nodes
|
||||
seen_techniques: set[str] = set()
|
||||
for tech_set in row_techniques:
|
||||
seen_techniques.update(tech_set)
|
||||
|
||||
for tech_id in seen_techniques:
|
||||
name, tactic = tech_meta.get(tech_id, (tech_id, "unknown"))
|
||||
nid = f"technique:{tech_id}"
|
||||
if nid not in node_ids:
|
||||
node_ids.add(nid)
|
||||
nodes.append({
|
||||
"data": {
|
||||
"id": nid,
|
||||
"label": f"{tech_id}\n{name}",
|
||||
"type": "technique",
|
||||
"color": TYPE_COLORS["technique"],
|
||||
"shape": TYPE_SHAPES["technique"],
|
||||
"tactic": tactic,
|
||||
},
|
||||
})
|
||||
|
||||
# Edges: entity → technique (based on co-occurrence in rows)
|
||||
for i, row_ents in enumerate(row_entity_map):
|
||||
for ent_type, ent_val in row_ents:
|
||||
for tech_id in row_techniques[i]:
|
||||
src = f"{ent_type}:{ent_val}"
|
||||
tgt = f"technique:{tech_id}"
|
||||
if src in node_ids and tgt in node_ids:
|
||||
edge_key = (src, tgt)
|
||||
edge_counter[edge_key] += 1
|
||||
|
||||
# Edges: entity → entity (based on co-occurrence)
|
||||
for row_ents in row_entity_map:
|
||||
for j in range(len(row_ents)):
|
||||
for k in range(j + 1, len(row_ents)):
|
||||
src = f"{row_ents[j][0]}:{row_ents[j][1]}"
|
||||
tgt = f"{row_ents[k][0]}:{row_ents[k][1]}"
|
||||
if src in node_ids and tgt in node_ids and src != tgt:
|
||||
edge_counter[(src, tgt)] += 1
|
||||
|
||||
# Build edge list (filter low-weight edges)
|
||||
for (src, tgt), weight in edge_counter.most_common(500):
|
||||
if weight < 1:
|
||||
continue
|
||||
edges.append({
|
||||
"data": {
|
||||
"source": src,
|
||||
"target": tgt,
|
||||
"weight": weight,
|
||||
"label": str(weight) if weight > 2 else "",
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
"nodes": nodes,
|
||||
"edges": edges,
|
||||
"stats": {
|
||||
"total_nodes": len(nodes),
|
||||
"total_edges": len(edges),
|
||||
"entity_counts": {t: len(v) for t, v in entities.items()},
|
||||
"techniques_found": len(seen_techniques),
|
||||
},
|
||||
}
|
||||
252
backend/app/services/network_inventory.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""Network Picture — deduplicated host inventory built from dataset rows.
|
||||
|
||||
Scans all datasets in a hunt, extracts host-identifying fields from
|
||||
normalized data, and groups by hostname (or src_ip fallback) to produce
|
||||
a clean one-row-per-host inventory. Uses sets for deduplication —
|
||||
if an IP appears 900 times, it shows once.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Sequence
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import Dataset, DatasetRow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Canonical column names we extract per row
|
||||
_HOST_KEYS = ("hostname",)
|
||||
_IP_KEYS = ("src_ip", "dst_ip", "ip_address")
|
||||
_USER_KEYS = ("username",)
|
||||
_OS_KEYS = ("os",)
|
||||
_MAC_KEYS = ("mac_address",)
|
||||
_PORT_SRC_KEYS = ("src_port",)
|
||||
_PORT_DST_KEYS = ("dst_port",)
|
||||
_PROTO_KEYS = ("protocol",)
|
||||
_STATE_KEYS = ("connection_state",)
|
||||
_TS_KEYS = ("timestamp",)
|
||||
|
||||
# Junk values to skip
|
||||
_JUNK = frozenset({"", "-", "0.0.0.0", "::", "0", "127.0.0.1", "::1", "localhost", "unknown", "n/a", "none", "null"})
|
||||
|
||||
ROW_BATCH = 1000 # rows fetched per DB query
|
||||
MAX_HOSTS = 1000 # hard cap on returned hosts
|
||||
|
||||
|
||||
def _clean(val: Any) -> str:
|
||||
"""Normalise a cell value to a clean string or empty."""
|
||||
s = (val if isinstance(val, str) else str(val) if val is not None else "").strip()
|
||||
return "" if s.lower() in _JUNK else s
|
||||
|
||||
|
||||
def _try_parse_ts(val: str) -> datetime | None:
|
||||
"""Best-effort timestamp parse (subset of common formats)."""
|
||||
for fmt in (
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y-%m-%d %H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
):
|
||||
try:
|
||||
return datetime.strptime(val.strip(), fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
class _HostBucket:
|
||||
"""Mutable accumulator for a single host."""
|
||||
|
||||
__slots__ = (
|
||||
"hostname", "ips", "users", "os_versions", "mac_addresses",
|
||||
"protocols", "open_ports", "remote_targets", "datasets",
|
||||
"connection_count", "first_seen", "last_seen",
|
||||
)
|
||||
|
||||
def __init__(self, hostname: str):
|
||||
self.hostname = hostname
|
||||
self.ips: set[str] = set()
|
||||
self.users: set[str] = set()
|
||||
self.os_versions: set[str] = set()
|
||||
self.mac_addresses: set[str] = set()
|
||||
self.protocols: set[str] = set()
|
||||
self.open_ports: set[str] = set()
|
||||
self.remote_targets: set[str] = set()
|
||||
self.datasets: set[str] = set()
|
||||
self.connection_count: int = 0
|
||||
self.first_seen: datetime | None = None
|
||||
self.last_seen: datetime | None = None
|
||||
|
||||
def ingest(self, row: dict[str, Any], ds_name: str) -> None:
|
||||
"""Merge one normalised row into this bucket."""
|
||||
self.connection_count += 1
|
||||
self.datasets.add(ds_name)
|
||||
|
||||
for k in _IP_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v:
|
||||
self.ips.add(v)
|
||||
|
||||
for k in _USER_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v:
|
||||
self.users.add(v)
|
||||
|
||||
for k in _OS_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v:
|
||||
self.os_versions.add(v)
|
||||
|
||||
for k in _MAC_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v:
|
||||
self.mac_addresses.add(v)
|
||||
|
||||
for k in _PROTO_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v:
|
||||
self.protocols.add(v.upper())
|
||||
|
||||
# Open ports = local (src) ports
|
||||
for k in _PORT_SRC_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v and v != "0":
|
||||
self.open_ports.add(v)
|
||||
|
||||
# Remote targets = dst IPs
|
||||
dst = _clean(row.get("dst_ip"))
|
||||
if dst:
|
||||
self.remote_targets.add(dst)
|
||||
|
||||
# Timestamps
|
||||
for k in _TS_KEYS:
|
||||
v = _clean(row.get(k))
|
||||
if v:
|
||||
ts = _try_parse_ts(v)
|
||||
if ts:
|
||||
if self.first_seen is None or ts < self.first_seen:
|
||||
self.first_seen = ts
|
||||
if self.last_seen is None or ts > self.last_seen:
|
||||
self.last_seen = ts
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"hostname": self.hostname,
|
||||
"ips": sorted(self.ips),
|
||||
"users": sorted(self.users),
|
||||
"os": sorted(self.os_versions),
|
||||
"mac_addresses": sorted(self.mac_addresses),
|
||||
"protocols": sorted(self.protocols),
|
||||
"open_ports": sorted(self.open_ports, key=lambda p: int(p) if p.isdigit() else 0),
|
||||
"remote_targets": sorted(self.remote_targets),
|
||||
"datasets": sorted(self.datasets),
|
||||
"connection_count": self.connection_count,
|
||||
"first_seen": self.first_seen.isoformat() if self.first_seen else None,
|
||||
"last_seen": self.last_seen.isoformat() if self.last_seen else None,
|
||||
}
|
||||
|
||||
|
||||
async def build_network_picture(
|
||||
db: AsyncSession,
|
||||
hunt_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a deduplicated host inventory for all datasets in a hunt.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"hosts": [ {hostname, ips[], users[], os[], ...}, ... ],
|
||||
"summary": { total_hosts, total_connections, total_unique_ips, datasets_scanned }
|
||||
}
|
||||
"""
|
||||
# 1. Get all datasets in this hunt
|
||||
ds_result = await db.execute(
|
||||
select(Dataset)
|
||||
.where(Dataset.hunt_id == hunt_id)
|
||||
.order_by(Dataset.created_at)
|
||||
)
|
||||
ds_list: Sequence[Dataset] = ds_result.scalars().all()
|
||||
|
||||
if not ds_list:
|
||||
return {
|
||||
"hosts": [],
|
||||
"summary": {
|
||||
"total_hosts": 0,
|
||||
"total_connections": 0,
|
||||
"total_unique_ips": 0,
|
||||
"datasets_scanned": 0,
|
||||
},
|
||||
}
|
||||
|
||||
# 2. Stream rows and aggregate into host buckets
|
||||
buckets: dict[str, _HostBucket] = {} # key = uppercase hostname or IP
|
||||
|
||||
for ds in ds_list:
|
||||
ds_name = ds.name or ds.filename
|
||||
offset = 0
|
||||
while True:
|
||||
stmt = (
|
||||
select(DatasetRow)
|
||||
.where(DatasetRow.dataset_id == ds.id)
|
||||
.order_by(DatasetRow.row_index)
|
||||
.limit(ROW_BATCH)
|
||||
.offset(offset)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
rows: Sequence[DatasetRow] = result.scalars().all()
|
||||
if not rows:
|
||||
break
|
||||
|
||||
for dr in rows:
|
||||
norm = dr.normalized_data or dr.data or {}
|
||||
|
||||
# Determine grouping key: hostname preferred, else src_ip/ip_address
|
||||
host_val = ""
|
||||
for k in _HOST_KEYS:
|
||||
host_val = _clean(norm.get(k))
|
||||
if host_val:
|
||||
break
|
||||
if not host_val:
|
||||
for k in ("src_ip", "ip_address"):
|
||||
host_val = _clean(norm.get(k))
|
||||
if host_val:
|
||||
break
|
||||
if not host_val:
|
||||
# Row has no host identifier — skip
|
||||
continue
|
||||
|
||||
bucket_key = host_val.upper()
|
||||
if bucket_key not in buckets:
|
||||
buckets[bucket_key] = _HostBucket(host_val)
|
||||
|
||||
buckets[bucket_key].ingest(norm, ds_name)
|
||||
|
||||
offset += ROW_BATCH
|
||||
|
||||
# 3. Convert to sorted list (by connection count descending)
|
||||
hosts_raw = sorted(buckets.values(), key=lambda b: b.connection_count, reverse=True)
|
||||
if len(hosts_raw) > MAX_HOSTS:
|
||||
hosts_raw = hosts_raw[:MAX_HOSTS]
|
||||
|
||||
hosts = [b.to_dict() for b in hosts_raw]
|
||||
|
||||
# 4. Summary stats
|
||||
all_ips: set[str] = set()
|
||||
total_conns = 0
|
||||
for b in hosts_raw:
|
||||
all_ips.update(b.ips)
|
||||
total_conns += b.connection_count
|
||||
|
||||
return {
|
||||
"hosts": hosts,
|
||||
"summary": {
|
||||
"total_hosts": len(hosts),
|
||||
"total_connections": total_conns,
|
||||
"total_unique_ips": len(all_ips),
|
||||
"datasets_scanned": len(ds_list),
|
||||
},
|
||||
}
|
||||
@@ -23,12 +23,12 @@ COLUMN_MAPPINGS: list[tuple[str, str]] = [
|
||||
# Operating system
|
||||
(r"^(os|operating_?system|os_?version|os_?name|platform|os_?type)$", "os"),
|
||||
# Source / destination IPs
|
||||
(r"^(source_?ip|src_?ip|srcaddr|local_?address|sourceaddress)$", "src_ip"),
|
||||
(r"^(dest_?ip|dst_?ip|dstaddr|remote_?address|destinationaddress|destaddress)$", "dst_ip"),
|
||||
(r"^(source_?ip|src_?ip|srcaddr|local_?address|sourceaddress|sourceip|laddr\.?ip)$", "src_ip"),
|
||||
(r"^(dest_?ip|dst_?ip|dstaddr|remote_?address|destinationaddress|destaddress|destination_?ip|destinationip|raddr\.?ip)$", "dst_ip"),
|
||||
(r"^(ip_?address|ipaddress|ip)$", "ip_address"),
|
||||
# Ports
|
||||
(r"^(source_?port|src_?port|localport)$", "src_port"),
|
||||
(r"^(dest_?port|dst_?port|remoteport|destinationport)$", "dst_port"),
|
||||
(r"^(source_?port|src_?port|localport|laddr\.?port)$", "src_port"),
|
||||
(r"^(dest_?port|dst_?port|remoteport|destinationport|raddr\.?port)$", "dst_port"),
|
||||
# Process info
|
||||
(r"^(process_?name|name|image|exe|executable|binary)$", "process_name"),
|
||||
(r"^(pid|process_?id)$", "pid"),
|
||||
@@ -51,6 +51,10 @@ COLUMN_MAPPINGS: list[tuple[str, str]] = [
|
||||
(r"^(protocol|proto)$", "protocol"),
|
||||
(r"^(domain|dns_?name|query_?name|queriedname)$", "domain"),
|
||||
(r"^(url|uri|request_?url)$", "url"),
|
||||
# MAC address
|
||||
(r"^(mac|mac_?address|physical_?address|mac_?addr|hw_?addr|ethernet)$", "mac_address"),
|
||||
# Connection state (netstat)
|
||||
(r"^(state|status|tcp_?state|conn_?state)$", "connection_state"),
|
||||
# Event info
|
||||
(r"^(event_?id|eventid|eid)$", "event_id"),
|
||||
(r"^(event_?type|eventtype|category|action)$", "event_type"),
|
||||
@@ -120,13 +124,27 @@ def detect_ioc_columns(
|
||||
"domain": "domain",
|
||||
}
|
||||
|
||||
# Canonical names that should NEVER be treated as IOCs even if values
|
||||
# match a pattern (e.g. process_name "svchost.exe" matching domain regex).
|
||||
_non_ioc_canonicals = frozenset({
|
||||
"process_name", "file_name", "file_path", "command_line",
|
||||
"parent_command_line", "description", "event_type", "registry_key",
|
||||
"registry_value", "severity", "os",
|
||||
"title", "netmask", "gateway", "connection_state",
|
||||
})
|
||||
|
||||
for col in columns:
|
||||
canonical = column_mapping.get(col, "")
|
||||
|
||||
# Skip columns whose canonical meaning is obviously not an IOC
|
||||
if canonical in _non_ioc_canonicals:
|
||||
continue
|
||||
|
||||
col_type = column_types.get(col)
|
||||
if col_type in ioc_type_map:
|
||||
ioc_columns[col] = ioc_type_map[col_type]
|
||||
|
||||
# Also check canonical name
|
||||
canonical = column_mapping.get(col, "")
|
||||
if canonical in ("src_ip", "dst_ip", "ip_address"):
|
||||
ioc_columns[col] = "ip"
|
||||
elif canonical == "hash_md5":
|
||||
@@ -139,6 +157,8 @@ def detect_ioc_columns(
|
||||
ioc_columns[col] = "domain"
|
||||
elif canonical == "url":
|
||||
ioc_columns[col] = "url"
|
||||
elif canonical == "hostname":
|
||||
ioc_columns[col] = "hostname"
|
||||
|
||||
return ioc_columns
|
||||
|
||||
|
||||
269
backend/app/services/playbook.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""Investigation Notebook & Playbook Engine for ThreatHunt.
|
||||
|
||||
Notebooks: Analyst-facing, cell-based documents (markdown + code cells)
|
||||
stored as JSON in the database. Each cell can contain free-form
|
||||
markdown notes *or* a structured query/command that the backend
|
||||
evaluates against datasets.
|
||||
|
||||
Playbooks: Pre-defined, step-by-step investigation workflows. Each step
|
||||
defines an action (query, analyze, enrich, tag) and expected
|
||||
outcomes. Analysts can run through playbooks interactively or
|
||||
trigger them automatically on new alerts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ── Notebook helpers ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@dataclass
|
||||
class NotebookCell:
|
||||
id: str
|
||||
cell_type: str # markdown | query | code
|
||||
source: str
|
||||
output: Optional[str] = None
|
||||
metadata: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
def validate_notebook_cells(cells: list[dict]) -> list[dict]:
|
||||
"""Ensure each cell has required keys."""
|
||||
cleaned: list[dict] = []
|
||||
for i, c in enumerate(cells):
|
||||
cleaned.append({
|
||||
"id": c.get("id", f"cell-{i}"),
|
||||
"cell_type": c.get("cell_type", "markdown"),
|
||||
"source": c.get("source", ""),
|
||||
"output": c.get("output"),
|
||||
"metadata": c.get("metadata", {}),
|
||||
})
|
||||
return cleaned
|
||||
|
||||
|
||||
# ── Built-in Playbook Templates ──────────────────────────────────────
|
||||
|
||||
|
||||
BUILT_IN_PLAYBOOKS: list[dict[str, Any]] = [
|
||||
{
|
||||
"name": "Suspicious Process Investigation",
|
||||
"description": "Step-by-step investigation of a potentially malicious process execution.",
|
||||
"category": "incident_response",
|
||||
"tags": ["process", "malware", "T1059"],
|
||||
"steps": [
|
||||
{
|
||||
"order": 1,
|
||||
"title": "Identify the suspicious process",
|
||||
"description": "Search for the process name/PID across all datasets. Note the command line, parent, and user context.",
|
||||
"action": "search",
|
||||
"action_config": {"fields": ["process_name", "command_line", "parent_process_name", "username"]},
|
||||
"expected_outcome": "Process details, parent chain, and execution context identified.",
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"title": "Build process tree",
|
||||
"description": "View the full parent→child process tree to understand the execution chain.",
|
||||
"action": "process_tree",
|
||||
"action_config": {},
|
||||
"expected_outcome": "Complete process lineage showing how the suspicious process was spawned.",
|
||||
},
|
||||
{
|
||||
"order": 3,
|
||||
"title": "Check network connections",
|
||||
"description": "Search for network events associated with the same host and timeframe.",
|
||||
"action": "search",
|
||||
"action_config": {"fields": ["src_ip", "dst_ip", "dst_port", "protocol"]},
|
||||
"expected_outcome": "Network connections revealing potential C2 or data exfiltration.",
|
||||
},
|
||||
{
|
||||
"order": 4,
|
||||
"title": "Run analyzers",
|
||||
"description": "Execute the suspicious_commands and entropy analyzers against the dataset.",
|
||||
"action": "analyze",
|
||||
"action_config": {"analyzers": ["suspicious_commands", "entropy"]},
|
||||
"expected_outcome": "Automated detection of known-bad patterns.",
|
||||
},
|
||||
{
|
||||
"order": 5,
|
||||
"title": "Map to MITRE ATT&CK",
|
||||
"description": "Check which MITRE techniques the process behavior maps to.",
|
||||
"action": "mitre_map",
|
||||
"action_config": {},
|
||||
"expected_outcome": "MITRE technique mappings for the suspicious activity.",
|
||||
},
|
||||
{
|
||||
"order": 6,
|
||||
"title": "Document findings & create case",
|
||||
"description": "Summarize investigation findings, annotate key evidence, and create a case if warranted.",
|
||||
"action": "create_case",
|
||||
"action_config": {},
|
||||
"expected_outcome": "Investigation documented with annotations and optionally escalated.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Lateral Movement Hunt",
|
||||
"description": "Systematic hunt for lateral movement indicators across the environment.",
|
||||
"category": "threat_hunting",
|
||||
"tags": ["lateral_movement", "T1021", "T1047"],
|
||||
"steps": [
|
||||
{
|
||||
"order": 1,
|
||||
"title": "Search for remote access tools",
|
||||
"description": "Look for PsExec, WMI, WinRM, RDP, and SSH usage across datasets.",
|
||||
"action": "search",
|
||||
"action_config": {"query": "psexec|wmic|winrm|rdp|ssh"},
|
||||
"expected_outcome": "Identify all remote access tool usage.",
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"title": "Analyze authentication events",
|
||||
"description": "Run the auth anomaly analyzer to find brute force, unusual logon types.",
|
||||
"action": "analyze",
|
||||
"action_config": {"analyzers": ["auth_anomaly"]},
|
||||
"expected_outcome": "Authentication anomalies detected.",
|
||||
},
|
||||
{
|
||||
"order": 3,
|
||||
"title": "Check network anomalies",
|
||||
"description": "Run network anomaly analyzer for beaconing and suspicious connections.",
|
||||
"action": "analyze",
|
||||
"action_config": {"analyzers": ["network_anomaly"]},
|
||||
"expected_outcome": "Beaconing or unusual network patterns identified.",
|
||||
},
|
||||
{
|
||||
"order": 4,
|
||||
"title": "Build knowledge graph",
|
||||
"description": "Visualize entity relationships to identify pivot paths.",
|
||||
"action": "knowledge_graph",
|
||||
"action_config": {},
|
||||
"expected_outcome": "Entity relationship graph showing lateral movement paths.",
|
||||
},
|
||||
{
|
||||
"order": 5,
|
||||
"title": "Document and escalate",
|
||||
"description": "Create annotations for key findings and escalate to case if needed.",
|
||||
"action": "create_case",
|
||||
"action_config": {"tags": ["lateral_movement"]},
|
||||
"expected_outcome": "Findings documented and case created.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Data Exfiltration Check",
|
||||
"description": "Investigate potential data exfiltration activity.",
|
||||
"category": "incident_response",
|
||||
"tags": ["exfiltration", "T1048", "T1567"],
|
||||
"steps": [
|
||||
{
|
||||
"order": 1,
|
||||
"title": "Identify large transfers",
|
||||
"description": "Search for network events with unusually high byte counts.",
|
||||
"action": "analyze",
|
||||
"action_config": {"analyzers": ["network_anomaly"], "config": {"large_transfer_threshold": 5000000}},
|
||||
"expected_outcome": "Large data transfers identified.",
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"title": "Check DNS anomalies",
|
||||
"description": "Look for DNS tunneling or unusual DNS query patterns.",
|
||||
"action": "search",
|
||||
"action_config": {"fields": ["dns_query", "query_length"]},
|
||||
"expected_outcome": "Suspicious DNS activity identified.",
|
||||
},
|
||||
{
|
||||
"order": 3,
|
||||
"title": "Timeline analysis",
|
||||
"description": "Examine the timeline for data staging and exfiltration windows.",
|
||||
"action": "timeline",
|
||||
"action_config": {},
|
||||
"expected_outcome": "Time windows of suspicious activity identified.",
|
||||
},
|
||||
{
|
||||
"order": 4,
|
||||
"title": "Correlate with process activity",
|
||||
"description": "Match network exfiltration with process execution times.",
|
||||
"action": "search",
|
||||
"action_config": {"fields": ["process_name", "dst_ip", "bytes_sent"]},
|
||||
"expected_outcome": "Process responsible for data transfer identified.",
|
||||
},
|
||||
{
|
||||
"order": 5,
|
||||
"title": "MITRE mapping & documentation",
|
||||
"description": "Map findings to MITRE exfiltration techniques and document.",
|
||||
"action": "mitre_map",
|
||||
"action_config": {},
|
||||
"expected_outcome": "Complete exfiltration investigation documented.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Ransomware Triage",
|
||||
"description": "Rapid triage of potential ransomware activity.",
|
||||
"category": "incident_response",
|
||||
"tags": ["ransomware", "T1486", "T1490"],
|
||||
"steps": [
|
||||
{
|
||||
"order": 1,
|
||||
"title": "Search for ransomware indicators",
|
||||
"description": "Look for shadow copy deletion, boot config changes, encryption activity.",
|
||||
"action": "search",
|
||||
"action_config": {"query": "vssadmin|bcdedit|cipher|.encrypted|.locked|ransom"},
|
||||
"expected_outcome": "Ransomware indicators identified.",
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"title": "Run all analyzers",
|
||||
"description": "Execute all analyzers to get comprehensive threat picture.",
|
||||
"action": "analyze",
|
||||
"action_config": {},
|
||||
"expected_outcome": "Full automated analysis of ransomware indicators.",
|
||||
},
|
||||
{
|
||||
"order": 3,
|
||||
"title": "Check persistence mechanisms",
|
||||
"description": "Look for persistence that may indicate pre-ransomware staging.",
|
||||
"action": "analyze",
|
||||
"action_config": {"analyzers": ["persistence"]},
|
||||
"expected_outcome": "Persistence mechanisms identified.",
|
||||
},
|
||||
{
|
||||
"order": 4,
|
||||
"title": "LLM deep analysis",
|
||||
"description": "Run deep LLM analysis for comprehensive ransomware assessment.",
|
||||
"action": "llm_analyze",
|
||||
"action_config": {"mode": "deep", "focus": "ransomware"},
|
||||
"expected_outcome": "AI-powered ransomware analysis with recommendations.",
|
||||
},
|
||||
{
|
||||
"order": 5,
|
||||
"title": "Create critical case",
|
||||
"description": "Immediately create a critical-severity case for the ransomware incident.",
|
||||
"action": "create_case",
|
||||
"action_config": {"severity": "critical", "tags": ["ransomware"]},
|
||||
"expected_outcome": "Critical case created for incident response.",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_builtin_playbooks() -> list[dict]:
|
||||
"""Return list of all built-in playbook templates."""
|
||||
return BUILT_IN_PLAYBOOKS
|
||||
|
||||
|
||||
def get_playbook_template(name: str) -> dict | None:
|
||||
"""Get a specific built-in playbook by name."""
|
||||
for pb in BUILT_IN_PLAYBOOKS:
|
||||
if pb["name"] == name:
|
||||
return pb
|
||||
return None
|
||||
447
backend/app/services/process_tree.py
Normal file
@@ -0,0 +1,447 @@
|
||||
"""Process tree and storyline graph builder.
|
||||
|
||||
Extracts parent→child process relationships from dataset rows and builds
|
||||
hierarchical trees. Also builds attack-storyline graphs connecting events
|
||||
by host → process → network activity → file activity chains.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import Any, Sequence
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.db.models import Dataset, DatasetRow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
_JUNK = frozenset({"", "N/A", "n/a", "-", "—", "null", "None", "none", "unknown"})
|
||||
|
||||
|
||||
def _clean(val: Any) -> str | None:
|
||||
"""Return cleaned string or None for junk values."""
|
||||
if val is None:
|
||||
return None
|
||||
s = str(val).strip()
|
||||
return None if s in _JUNK else s
|
||||
|
||||
|
||||
# ── Process Tree ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class ProcessNode:
|
||||
"""A single process in the tree."""
|
||||
|
||||
__slots__ = (
|
||||
"pid", "ppid", "name", "command_line", "username", "hostname",
|
||||
"timestamp", "dataset_name", "row_index", "children", "extra",
|
||||
)
|
||||
|
||||
def __init__(self, **kw: Any):
|
||||
self.pid: str = kw.get("pid", "")
|
||||
self.ppid: str = kw.get("ppid", "")
|
||||
self.name: str = kw.get("name", "")
|
||||
self.command_line: str = kw.get("command_line", "")
|
||||
self.username: str = kw.get("username", "")
|
||||
self.hostname: str = kw.get("hostname", "")
|
||||
self.timestamp: str = kw.get("timestamp", "")
|
||||
self.dataset_name: str = kw.get("dataset_name", "")
|
||||
self.row_index: int = kw.get("row_index", -1)
|
||||
self.children: list["ProcessNode"] = []
|
||||
self.extra: dict = kw.get("extra", {})
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"pid": self.pid,
|
||||
"ppid": self.ppid,
|
||||
"name": self.name,
|
||||
"command_line": self.command_line,
|
||||
"username": self.username,
|
||||
"hostname": self.hostname,
|
||||
"timestamp": self.timestamp,
|
||||
"dataset_name": self.dataset_name,
|
||||
"row_index": self.row_index,
|
||||
"children": [c.to_dict() for c in self.children],
|
||||
"extra": self.extra,
|
||||
}
|
||||
|
||||
|
||||
async def build_process_tree(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
hostname_filter: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""Build process trees from dataset rows.
|
||||
|
||||
Returns a list of root-level process nodes (forest).
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id=dataset_id, hunt_id=hunt_id)
|
||||
if not rows:
|
||||
return []
|
||||
|
||||
# Group processes by (hostname, pid) → node
|
||||
nodes_by_key: dict[tuple[str, str], ProcessNode] = {}
|
||||
nodes_list: list[ProcessNode] = []
|
||||
|
||||
for row_obj in rows:
|
||||
data = row_obj.normalized_data or row_obj.data
|
||||
pid = _clean(data.get("pid"))
|
||||
if not pid:
|
||||
continue
|
||||
|
||||
ppid = _clean(data.get("ppid")) or ""
|
||||
hostname = _clean(data.get("hostname")) or "unknown"
|
||||
|
||||
if hostname_filter and hostname.lower() != hostname_filter.lower():
|
||||
continue
|
||||
|
||||
node = ProcessNode(
|
||||
pid=pid,
|
||||
ppid=ppid,
|
||||
name=_clean(data.get("process_name")) or _clean(data.get("name")) or "",
|
||||
command_line=_clean(data.get("command_line")) or "",
|
||||
username=_clean(data.get("username")) or "",
|
||||
hostname=hostname,
|
||||
timestamp=_clean(data.get("timestamp")) or "",
|
||||
dataset_name=row_obj.dataset.name if row_obj.dataset else "",
|
||||
row_index=row_obj.row_index,
|
||||
extra={
|
||||
k: str(v)
|
||||
for k, v in data.items()
|
||||
if k not in {"pid", "ppid", "process_name", "name", "command_line",
|
||||
"username", "hostname", "timestamp"}
|
||||
and v is not None and str(v).strip() not in _JUNK
|
||||
},
|
||||
)
|
||||
|
||||
key = (hostname, pid)
|
||||
# Keep the first occurrence (earlier in data) or overwrite if deeper info
|
||||
if key not in nodes_by_key:
|
||||
nodes_by_key[key] = node
|
||||
nodes_list.append(node)
|
||||
|
||||
# Link parent → child
|
||||
roots: list[ProcessNode] = []
|
||||
for node in nodes_list:
|
||||
parent_key = (node.hostname, node.ppid)
|
||||
parent = nodes_by_key.get(parent_key)
|
||||
if parent and parent is not node:
|
||||
parent.children.append(node)
|
||||
else:
|
||||
roots.append(node)
|
||||
|
||||
return [r.to_dict() for r in roots]
|
||||
|
||||
|
||||
# ── Storyline / Attack Graph ─────────────────────────────────────────
|
||||
|
||||
|
||||
async def build_storyline(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
hostname_filter: str | None = None,
|
||||
) -> dict:
|
||||
"""Build a CrowdStrike-style storyline graph.
|
||||
|
||||
Nodes represent events (process start, network connection, file write, etc.)
|
||||
Edges represent causal / temporal relationships.
|
||||
|
||||
Returns a Cytoscape-compatible elements dict {nodes: [...], edges: [...]}.
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id=dataset_id, hunt_id=hunt_id)
|
||||
if not rows:
|
||||
return {"nodes": [], "edges": [], "summary": {}}
|
||||
|
||||
nodes: list[dict] = []
|
||||
edges: list[dict] = []
|
||||
seen_ids: set[str] = set()
|
||||
host_events: dict[str, list[dict]] = defaultdict(list)
|
||||
|
||||
for row_obj in rows:
|
||||
data = row_obj.normalized_data or row_obj.data
|
||||
hostname = _clean(data.get("hostname")) or "unknown"
|
||||
|
||||
if hostname_filter and hostname.lower() != hostname_filter.lower():
|
||||
continue
|
||||
|
||||
event_type = _classify_event(data)
|
||||
node_id = f"{row_obj.dataset_id}_{row_obj.row_index}"
|
||||
if node_id in seen_ids:
|
||||
continue
|
||||
seen_ids.add(node_id)
|
||||
|
||||
label = _build_label(data, event_type)
|
||||
severity = _estimate_severity(data, event_type)
|
||||
|
||||
node = {
|
||||
"data": {
|
||||
"id": node_id,
|
||||
"label": label,
|
||||
"event_type": event_type,
|
||||
"hostname": hostname,
|
||||
"timestamp": _clean(data.get("timestamp")) or "",
|
||||
"pid": _clean(data.get("pid")) or "",
|
||||
"ppid": _clean(data.get("ppid")) or "",
|
||||
"process_name": _clean(data.get("process_name")) or "",
|
||||
"command_line": _clean(data.get("command_line")) or "",
|
||||
"username": _clean(data.get("username")) or "",
|
||||
"src_ip": _clean(data.get("src_ip")) or "",
|
||||
"dst_ip": _clean(data.get("dst_ip")) or "",
|
||||
"dst_port": _clean(data.get("dst_port")) or "",
|
||||
"file_path": _clean(data.get("file_path")) or "",
|
||||
"severity": severity,
|
||||
"dataset_id": row_obj.dataset_id,
|
||||
"row_index": row_obj.row_index,
|
||||
},
|
||||
}
|
||||
nodes.append(node)
|
||||
host_events[hostname].append(node["data"])
|
||||
|
||||
# Build edges: parent→child (by pid/ppid) and temporal sequence per host
|
||||
pid_lookup: dict[tuple[str, str], str] = {} # (host, pid) → node_id
|
||||
for node in nodes:
|
||||
d = node["data"]
|
||||
if d["pid"]:
|
||||
pid_lookup[(d["hostname"], d["pid"])] = d["id"]
|
||||
|
||||
for node in nodes:
|
||||
d = node["data"]
|
||||
if d["ppid"]:
|
||||
parent_id = pid_lookup.get((d["hostname"], d["ppid"]))
|
||||
if parent_id and parent_id != d["id"]:
|
||||
edges.append({
|
||||
"data": {
|
||||
"id": f"e_{parent_id}_{d['id']}",
|
||||
"source": parent_id,
|
||||
"target": d["id"],
|
||||
"relationship": "spawned",
|
||||
}
|
||||
})
|
||||
|
||||
# Temporal edges within each host (sorted by timestamp)
|
||||
for hostname, events in host_events.items():
|
||||
sorted_events = sorted(events, key=lambda e: e.get("timestamp", ""))
|
||||
for i in range(len(sorted_events) - 1):
|
||||
src = sorted_events[i]
|
||||
tgt = sorted_events[i + 1]
|
||||
edge_id = f"t_{src['id']}_{tgt['id']}"
|
||||
# Avoid duplicate edges
|
||||
if not any(e["data"]["id"] == edge_id for e in edges):
|
||||
edges.append({
|
||||
"data": {
|
||||
"id": edge_id,
|
||||
"source": src["id"],
|
||||
"target": tgt["id"],
|
||||
"relationship": "temporal",
|
||||
}
|
||||
})
|
||||
|
||||
# Summary stats
|
||||
type_counts: dict[str, int] = defaultdict(int)
|
||||
for n in nodes:
|
||||
type_counts[n["data"]["event_type"]] += 1
|
||||
|
||||
summary = {
|
||||
"total_events": len(nodes),
|
||||
"total_edges": len(edges),
|
||||
"hosts": list(host_events.keys()),
|
||||
"event_types": dict(type_counts),
|
||||
}
|
||||
|
||||
return {"nodes": nodes, "edges": edges, "summary": summary}
|
||||
|
||||
|
||||
# ── Risk scoring for dashboard ────────────────────────────────────────
|
||||
|
||||
async def compute_risk_scores(
|
||||
db: AsyncSession,
|
||||
hunt_id: str | None = None,
|
||||
) -> dict:
|
||||
"""Compute per-host risk scores from anomaly signals in datasets.
|
||||
|
||||
Returns {hosts: [{hostname, score, signals, ...}], overall_score, ...}
|
||||
"""
|
||||
rows = await _fetch_rows(db, hunt_id=hunt_id)
|
||||
if not rows:
|
||||
return {"hosts": [], "overall_score": 0, "total_events": 0,
|
||||
"severity_breakdown": {}}
|
||||
|
||||
host_signals: dict[str, dict] = defaultdict(
|
||||
lambda: {"hostname": "", "score": 0, "signals": [],
|
||||
"event_count": 0, "process_count": 0,
|
||||
"network_count": 0, "file_count": 0}
|
||||
)
|
||||
|
||||
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
|
||||
|
||||
for row_obj in rows:
|
||||
data = row_obj.normalized_data or row_obj.data
|
||||
hostname = _clean(data.get("hostname")) or "unknown"
|
||||
entry = host_signals[hostname]
|
||||
entry["hostname"] = hostname
|
||||
entry["event_count"] += 1
|
||||
|
||||
event_type = _classify_event(data)
|
||||
severity = _estimate_severity(data, event_type)
|
||||
severity_counts[severity] = severity_counts.get(severity, 0) + 1
|
||||
|
||||
# Count event types
|
||||
if event_type == "process":
|
||||
entry["process_count"] += 1
|
||||
elif event_type == "network":
|
||||
entry["network_count"] += 1
|
||||
elif event_type == "file":
|
||||
entry["file_count"] += 1
|
||||
|
||||
# Risk signals
|
||||
cmd = (_clean(data.get("command_line")) or "").lower()
|
||||
proc = (_clean(data.get("process_name")) or "").lower()
|
||||
|
||||
# Detect suspicious patterns
|
||||
sus_patterns = [
|
||||
("powershell -enc", 8, "Encoded PowerShell"),
|
||||
("invoke-expression", 7, "PowerShell IEX"),
|
||||
("invoke-webrequest", 6, "PowerShell WebRequest"),
|
||||
("certutil -urlcache", 8, "Certutil download"),
|
||||
("bitsadmin /transfer", 7, "BITS transfer"),
|
||||
("regsvr32 /s /n /u", 8, "Regsvr32 squiblydoo"),
|
||||
("mshta ", 7, "MSHTA execution"),
|
||||
("wmic process", 6, "WMIC process enum"),
|
||||
("net user", 5, "User enumeration"),
|
||||
("whoami", 4, "Whoami recon"),
|
||||
("mimikatz", 10, "Mimikatz detected"),
|
||||
("procdump", 7, "Process dumping"),
|
||||
("psexec", 7, "PsExec lateral movement"),
|
||||
]
|
||||
|
||||
for pattern, score_add, signal_name in sus_patterns:
|
||||
if pattern in cmd or pattern in proc:
|
||||
entry["score"] += score_add
|
||||
if signal_name not in entry["signals"]:
|
||||
entry["signals"].append(signal_name)
|
||||
|
||||
# External connections score
|
||||
dst_ip = _clean(data.get("dst_ip")) or ""
|
||||
if dst_ip and not dst_ip.startswith(("10.", "192.168.", "172.")):
|
||||
entry["score"] += 1
|
||||
if "External connections" not in entry["signals"]:
|
||||
entry["signals"].append("External connections")
|
||||
|
||||
# Normalize scores (0-100)
|
||||
max_score = max((h["score"] for h in host_signals.values()), default=1)
|
||||
if max_score > 0:
|
||||
for entry in host_signals.values():
|
||||
entry["score"] = min(round((entry["score"] / max_score) * 100), 100)
|
||||
|
||||
hosts = sorted(host_signals.values(), key=lambda h: h["score"], reverse=True)
|
||||
overall = round(sum(h["score"] for h in hosts) / max(len(hosts), 1))
|
||||
|
||||
return {
|
||||
"hosts": hosts,
|
||||
"overall_score": overall,
|
||||
"total_events": sum(h["event_count"] for h in hosts),
|
||||
"severity_breakdown": severity_counts,
|
||||
}
|
||||
|
||||
|
||||
# ── Internal helpers ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def _fetch_rows(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
limit: int = 50_000,
|
||||
) -> Sequence[DatasetRow]:
|
||||
"""Fetch dataset rows, optionally filtered by dataset or hunt."""
|
||||
stmt = (
|
||||
select(DatasetRow)
|
||||
.join(Dataset)
|
||||
.options(selectinload(DatasetRow.dataset))
|
||||
)
|
||||
|
||||
if dataset_id:
|
||||
stmt = stmt.where(DatasetRow.dataset_id == dataset_id)
|
||||
elif hunt_id:
|
||||
stmt = stmt.where(Dataset.hunt_id == hunt_id)
|
||||
else:
|
||||
# No filter — limit to prevent OOM
|
||||
pass
|
||||
|
||||
stmt = stmt.order_by(DatasetRow.row_index).limit(limit)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
def _classify_event(data: dict) -> str:
|
||||
"""Classify a row as process / network / file / registry / other."""
|
||||
if _clean(data.get("pid")) or _clean(data.get("process_name")):
|
||||
if _clean(data.get("dst_ip")) or _clean(data.get("dst_port")):
|
||||
return "network"
|
||||
if _clean(data.get("file_path")):
|
||||
return "file"
|
||||
return "process"
|
||||
if _clean(data.get("dst_ip")) or _clean(data.get("src_ip")):
|
||||
return "network"
|
||||
if _clean(data.get("file_path")):
|
||||
return "file"
|
||||
if _clean(data.get("registry_key")):
|
||||
return "registry"
|
||||
return "other"
|
||||
|
||||
|
||||
def _build_label(data: dict, event_type: str) -> str:
|
||||
"""Build a concise node label for storyline display."""
|
||||
name = _clean(data.get("process_name")) or ""
|
||||
pid = _clean(data.get("pid")) or ""
|
||||
dst = _clean(data.get("dst_ip")) or ""
|
||||
port = _clean(data.get("dst_port")) or ""
|
||||
fpath = _clean(data.get("file_path")) or ""
|
||||
|
||||
if event_type == "process":
|
||||
return f"{name} (PID {pid})" if pid else name or "process"
|
||||
elif event_type == "network":
|
||||
target = f"{dst}:{port}" if dst and port else dst or port
|
||||
return f"{name} → {target}" if name else target or "network"
|
||||
elif event_type == "file":
|
||||
fname = fpath.split("\\")[-1].split("/")[-1] if fpath else ""
|
||||
return f"{name} → {fname}" if name else fname or "file"
|
||||
elif event_type == "registry":
|
||||
return _clean(data.get("registry_key")) or "registry"
|
||||
return name or "event"
|
||||
|
||||
|
||||
def _estimate_severity(data: dict, event_type: str) -> str:
|
||||
"""Rough heuristic severity estimate."""
|
||||
cmd = (_clean(data.get("command_line")) or "").lower()
|
||||
proc = (_clean(data.get("process_name")) or "").lower()
|
||||
|
||||
# Critical indicators
|
||||
critical_kw = ["mimikatz", "cobalt", "meterpreter", "empire", "bloodhound"]
|
||||
if any(k in cmd or k in proc for k in critical_kw):
|
||||
return "critical"
|
||||
|
||||
# High indicators
|
||||
high_kw = ["powershell -enc", "certutil -urlcache", "regsvr32", "mshta",
|
||||
"bitsadmin", "psexec", "procdump"]
|
||||
if any(k in cmd for k in high_kw):
|
||||
return "high"
|
||||
|
||||
# Medium indicators
|
||||
medium_kw = ["invoke-", "wmic", "net user", "net group", "schtasks",
|
||||
"reg add", "sc create"]
|
||||
if any(k in cmd for k in medium_kw):
|
||||
return "medium"
|
||||
|
||||
# Low: recon
|
||||
low_kw = ["whoami", "ipconfig", "systeminfo", "tasklist", "netstat"]
|
||||
if any(k in cmd for k in low_kw):
|
||||
return "low"
|
||||
|
||||
return "info"
|
||||
254
backend/app/services/timeline.py
Normal file
@@ -0,0 +1,254 @@
|
||||
"""Timeline and field-statistics service.
|
||||
|
||||
Provides temporal histogram bins and per-field distribution stats
|
||||
for dataset rows — used by the TimelineScrubber and QueryBar components.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import Counter, defaultdict
|
||||
from datetime import datetime
|
||||
from typing import Any, Sequence
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import Dataset, DatasetRow
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ── Timeline bins ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def build_timeline_bins(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
bins: int = 60,
|
||||
) -> dict:
|
||||
"""Create histogram bins of events over time.
|
||||
|
||||
Returns {bins: [{start, end, count, events_by_type}], total, range}.
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id=dataset_id, hunt_id=hunt_id)
|
||||
if not rows:
|
||||
return {"bins": [], "total": 0, "range": None}
|
||||
|
||||
# Extract timestamps
|
||||
events: list[dict] = []
|
||||
for r in rows:
|
||||
data = r.normalized_data or r.data
|
||||
ts_str = data.get("timestamp", "")
|
||||
if not ts_str:
|
||||
continue
|
||||
ts = _parse_ts(str(ts_str))
|
||||
if ts:
|
||||
events.append({
|
||||
"timestamp": ts,
|
||||
"event_type": _classify_type(data),
|
||||
"hostname": data.get("hostname", ""),
|
||||
})
|
||||
|
||||
if not events:
|
||||
return {"bins": [], "total": len(rows), "range": None}
|
||||
|
||||
events.sort(key=lambda e: e["timestamp"])
|
||||
ts_min = events[0]["timestamp"]
|
||||
ts_max = events[-1]["timestamp"]
|
||||
|
||||
if ts_min == ts_max:
|
||||
return {
|
||||
"bins": [{"start": ts_min.isoformat(), "end": ts_max.isoformat(),
|
||||
"count": len(events), "events_by_type": {}}],
|
||||
"total": len(events),
|
||||
"range": {"start": ts_min.isoformat(), "end": ts_max.isoformat()},
|
||||
}
|
||||
|
||||
delta = (ts_max - ts_min) / bins
|
||||
result_bins: list[dict] = []
|
||||
|
||||
for i in range(bins):
|
||||
bin_start = ts_min + delta * i
|
||||
bin_end = ts_min + delta * (i + 1)
|
||||
bin_events = [e for e in events
|
||||
if bin_start <= e["timestamp"] < bin_end
|
||||
or (i == bins - 1 and e["timestamp"] == ts_max)]
|
||||
type_counts: dict[str, int] = Counter(e["event_type"] for e in bin_events)
|
||||
result_bins.append({
|
||||
"start": bin_start.isoformat(),
|
||||
"end": bin_end.isoformat(),
|
||||
"count": len(bin_events),
|
||||
"events_by_type": dict(type_counts),
|
||||
})
|
||||
|
||||
return {
|
||||
"bins": result_bins,
|
||||
"total": len(events),
|
||||
"range": {"start": ts_min.isoformat(), "end": ts_max.isoformat()},
|
||||
}
|
||||
|
||||
|
||||
# ── Field stats ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def compute_field_stats(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
fields: list[str] | None = None,
|
||||
top_n: int = 20,
|
||||
) -> dict:
|
||||
"""Compute per-field value distributions.
|
||||
|
||||
Returns {fields: {field_name: {total, unique, top: [{value, count}]}}}
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id=dataset_id, hunt_id=hunt_id)
|
||||
if not rows:
|
||||
return {"fields": {}, "total_rows": 0}
|
||||
|
||||
# Determine which fields to analyze
|
||||
sample_data = rows[0].normalized_data or rows[0].data
|
||||
all_fields = list(sample_data.keys())
|
||||
target_fields = fields if fields else all_fields[:30]
|
||||
|
||||
stats: dict[str, dict] = {}
|
||||
for field in target_fields:
|
||||
values = []
|
||||
for r in rows:
|
||||
data = r.normalized_data or r.data
|
||||
v = data.get(field)
|
||||
if v is not None and str(v).strip() not in ("", "N/A", "n/a", "-", "None"):
|
||||
values.append(str(v))
|
||||
|
||||
counter = Counter(values)
|
||||
top = [{"value": v, "count": c} for v, c in counter.most_common(top_n)]
|
||||
stats[field] = {
|
||||
"total": len(values),
|
||||
"unique": len(counter),
|
||||
"top": top,
|
||||
}
|
||||
|
||||
return {
|
||||
"fields": stats,
|
||||
"total_rows": len(rows),
|
||||
"available_fields": all_fields,
|
||||
}
|
||||
|
||||
|
||||
# ── Row search with filters ──────────────────────────────────────────
|
||||
|
||||
|
||||
async def search_rows(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
query: str = "",
|
||||
filters: dict[str, str] | None = None,
|
||||
time_start: str | None = None,
|
||||
time_end: str | None = None,
|
||||
limit: int = 500,
|
||||
offset: int = 0,
|
||||
) -> dict:
|
||||
"""Search/filter dataset rows.
|
||||
|
||||
Supports:
|
||||
- Free-text search across all fields
|
||||
- Field-specific filters {field: value}
|
||||
- Time range filters
|
||||
"""
|
||||
rows = await _fetch_rows(db, dataset_id=dataset_id, hunt_id=hunt_id, limit=50000)
|
||||
if not rows:
|
||||
return {"rows": [], "total": 0, "offset": offset, "limit": limit}
|
||||
|
||||
results: list[dict] = []
|
||||
ts_start = _parse_ts(time_start) if time_start else None
|
||||
ts_end = _parse_ts(time_end) if time_end else None
|
||||
|
||||
for r in rows:
|
||||
data = r.normalized_data or r.data
|
||||
|
||||
# Time filter
|
||||
if ts_start or ts_end:
|
||||
ts = _parse_ts(str(data.get("timestamp", "")))
|
||||
if ts:
|
||||
if ts_start and ts < ts_start:
|
||||
continue
|
||||
if ts_end and ts > ts_end:
|
||||
continue
|
||||
|
||||
# Field filters
|
||||
if filters:
|
||||
match = True
|
||||
for field, value in filters.items():
|
||||
field_val = str(data.get(field, "")).lower()
|
||||
if value.lower() not in field_val:
|
||||
match = False
|
||||
break
|
||||
if not match:
|
||||
continue
|
||||
|
||||
# Free-text search
|
||||
if query:
|
||||
q = query.lower()
|
||||
found = any(q in str(v).lower() for v in data.values())
|
||||
if not found:
|
||||
continue
|
||||
|
||||
results.append(data)
|
||||
|
||||
total = len(results)
|
||||
paged = results[offset:offset + limit]
|
||||
|
||||
return {"rows": paged, "total": total, "offset": offset, "limit": limit}
|
||||
|
||||
|
||||
# ── Internal helpers ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def _fetch_rows(
|
||||
db: AsyncSession,
|
||||
dataset_id: str | None = None,
|
||||
hunt_id: str | None = None,
|
||||
limit: int = 50_000,
|
||||
) -> Sequence[DatasetRow]:
|
||||
stmt = select(DatasetRow).join(Dataset)
|
||||
if dataset_id:
|
||||
stmt = stmt.where(DatasetRow.dataset_id == dataset_id)
|
||||
elif hunt_id:
|
||||
stmt = stmt.where(Dataset.hunt_id == hunt_id)
|
||||
stmt = stmt.order_by(DatasetRow.row_index).limit(limit)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
def _parse_ts(ts_str: str | None) -> datetime | None:
|
||||
"""Best-effort timestamp parsing."""
|
||||
if not ts_str:
|
||||
return None
|
||||
for fmt in (
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
"%Y-%m-%dT%H:%M:%S.%f",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y-%m-%d %H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%m/%d/%Y %H:%M:%S",
|
||||
"%m/%d/%Y %I:%M:%S %p",
|
||||
):
|
||||
try:
|
||||
return datetime.strptime(ts_str.strip(), fmt)
|
||||
except (ValueError, AttributeError):
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _classify_type(data: dict) -> str:
|
||||
if data.get("pid") or data.get("process_name"):
|
||||
if data.get("dst_ip") or data.get("dst_port"):
|
||||
return "network"
|
||||
return "process"
|
||||
if data.get("dst_ip") or data.get("src_ip"):
|
||||
return "network"
|
||||
if data.get("file_path"):
|
||||
return "file"
|
||||
return "other"
|
||||
589
backend/tests/generate_test_csvs.py
Normal file
@@ -0,0 +1,589 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate 12 realistic Velociraptor-style CSV test files.
|
||||
|
||||
Mock network: 75 hosts, 10 users, 3 subnets.
|
||||
Sprinkles AUP-triggering keywords across DNS, URL, and process data.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import os
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
random.seed(42)
|
||||
|
||||
OUT = Path(__file__).parent / "test_csvs"
|
||||
OUT.mkdir(exist_ok=True)
|
||||
|
||||
# ── Shared network inventory ──────────────────────────────────────────
|
||||
|
||||
SUBNETS = ["10.10.1", "10.10.2", "10.10.3"]
|
||||
DEPARTMENTS = ["IT", "HR", "Finance", "Sales", "Engineering", "Legal", "Marketing", "Exec"]
|
||||
OS_LIST = ["Windows 10 Enterprise", "Windows 11 Enterprise", "Windows Server 2022", "Windows Server 2019"]
|
||||
DOMAIN = "acme.local"
|
||||
|
||||
HOSTS = []
|
||||
for i in range(1, 76):
|
||||
subnet = SUBNETS[i % 3]
|
||||
ip = f"{subnet}.{100 + i}"
|
||||
dept = DEPARTMENTS[i % len(DEPARTMENTS)]
|
||||
prefix = {"IT": "IT-WS", "HR": "HR-WS", "Finance": "FIN-WS", "Sales": "SLS-WS",
|
||||
"Engineering": "ENG-WS", "Legal": "LEG-WS", "Marketing": "MKT-WS", "Exec": "EXEC-WS"}
|
||||
hostname = f"{prefix.get(dept, 'WS')}-{i:03d}"
|
||||
os_ver = OS_LIST[i % len(OS_LIST)]
|
||||
mac = f"00:1A:2B:{i:02X}:{(i*3)%256:02X}:{(i*7)%256:02X}"
|
||||
HOSTS.append({"hostname": hostname, "ip": ip, "os": os_ver, "mac": mac, "dept": dept})
|
||||
|
||||
SERVERS = [
|
||||
{"hostname": "DC-01", "ip": "10.10.1.10", "os": "Windows Server 2022", "mac": "00:1A:2B:AA:01:01"},
|
||||
{"hostname": "DC-02", "ip": "10.10.2.10", "os": "Windows Server 2022", "mac": "00:1A:2B:AA:02:02"},
|
||||
{"hostname": "FILE-01", "ip": "10.10.1.11", "os": "Windows Server 2019", "mac": "00:1A:2B:AA:03:03"},
|
||||
{"hostname": "EXCH-01", "ip": "10.10.1.12", "os": "Windows Server 2022", "mac": "00:1A:2B:AA:04:04"},
|
||||
{"hostname": "WEB-01", "ip": "10.10.3.10", "os": "Windows Server 2022", "mac": "00:1A:2B:AA:05:05"},
|
||||
{"hostname": "SQL-01", "ip": "10.10.2.11", "os": "Windows Server 2019", "mac": "00:1A:2B:AA:06:06"},
|
||||
{"hostname": "PROXY-01", "ip": "10.10.1.13", "os": "Windows Server 2022", "mac": "00:1A:2B:AA:07:07"},
|
||||
]
|
||||
ALL_HOSTS = HOSTS + SERVERS
|
||||
|
||||
USERS = [
|
||||
"jsmith", "agarcia", "bwilson", "cjohnson", "dlee",
|
||||
"emartinez", "fthompson", "gwhite", "hbrown", "idavis",
|
||||
"admin", "svc_backup", "svc_sql", "svc_web",
|
||||
]
|
||||
|
||||
# Base time range: 2-week window
|
||||
BASE_TIME = datetime(2026, 2, 10, 8, 0, 0)
|
||||
END_TIME = datetime(2026, 2, 20, 18, 0, 0)
|
||||
|
||||
def rand_ts():
|
||||
delta = (END_TIME - BASE_TIME).total_seconds()
|
||||
return BASE_TIME + timedelta(seconds=random.uniform(0, delta))
|
||||
|
||||
def ts_str(dt=None):
|
||||
return (dt or rand_ts()).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
||||
|
||||
def rand_host():
|
||||
return random.choice(ALL_HOSTS)
|
||||
|
||||
def rand_user():
|
||||
return random.choice(USERS)
|
||||
|
||||
def rand_ext_ip():
|
||||
return f"{random.randint(1,223)}.{random.randint(0,255)}.{random.randint(0,255)}.{random.randint(1,254)}"
|
||||
|
||||
# AUP trigger domains/URLs (will be sprinkled into DNS and proxy logs)
|
||||
AUP_DOMAINS = [
|
||||
"www.bet365.com", "pokerstars.com", "draftkings.com", # Gambling
|
||||
"store.steampowered.com", "steamcommunity.com", "discord.gg", # Gaming
|
||||
"www.netflix.com", "hulu.com", "open.spotify.com", # Streaming
|
||||
"thepiratebay.org", "1337x.to", "fitgirl-repacks.site", # Piracy
|
||||
"www.pornhub.com", "onlyfans.com", "xvideos.com", # Adult
|
||||
"www.facebook.com", "www.tiktok.com", "www.reddit.com", # Social Media
|
||||
"www.indeed.com", "www.glassdoor.com", "www.linkedin.com/jobs", # Job Search
|
||||
"www.amazon.com", "www.ebay.com", "www.shein.com", # Shopping
|
||||
]
|
||||
|
||||
AUP_PROCESSES = [
|
||||
"utorrent.exe", "qbittorrent.exe", "steam.exe", "discord.exe",
|
||||
"spotify.exe", "epicgameslauncher.exe",
|
||||
]
|
||||
|
||||
LEGIT_DOMAINS = [
|
||||
"login.microsoftonline.com", "outlook.office365.com", "teams.microsoft.com",
|
||||
"graph.microsoft.com", "update.microsoft.com", "windowsupdate.com",
|
||||
"acme.sharepoint.com", "acme.local", "dc-01.acme.local", "dc-02.acme.local",
|
||||
"file-01.acme.local", "exch-01.acme.local", "github.com", "stackoverflow.com",
|
||||
"cdn.jsdelivr.net", "pypi.org", "npmjs.com", "google.com", "googleapis.com",
|
||||
"cloudflare.com", "aws.amazon.com", "akamai.net", "time.windows.com",
|
||||
]
|
||||
|
||||
LEGIT_PROCESSES = [
|
||||
"svchost.exe", "explorer.exe", "chrome.exe", "msedge.exe", "outlook.exe",
|
||||
"teams.exe", "code.exe", "powershell.exe", "cmd.exe", "notepad.exe",
|
||||
"taskhostw.exe", "RuntimeBroker.exe", "SearchHost.exe", "lsass.exe",
|
||||
"csrss.exe", "winlogon.exe", "dwm.exe", "System", "smss.exe",
|
||||
"services.exe", "spoolsv.exe", "MsMpEng.exe", "OneDrive.exe",
|
||||
]
|
||||
|
||||
STATES = ["ESTABLISHED", "LISTEN", "TIME_WAIT", "CLOSE_WAIT", "SYN_SENT"]
|
||||
PROTOCOLS = ["TCP", "UDP", "TCP", "TCP", "TCP"]
|
||||
|
||||
def write_csv(filename, headers, rows):
|
||||
path = OUT / filename
|
||||
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||
w = csv.DictWriter(f, fieldnames=headers)
|
||||
w.writeheader()
|
||||
w.writerows(rows)
|
||||
print(f" {filename}: {len(rows)} rows")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 1. Netstat connections (Velociraptor: Windows.Network.Netstat)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_netstat():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
n_conns = random.randint(8, 40)
|
||||
for _ in range(n_conns):
|
||||
proc = random.choice(LEGIT_PROCESSES + (AUP_PROCESSES if random.random() < 0.08 else []))
|
||||
state = random.choice(STATES)
|
||||
proto = random.choice(PROTOCOLS)
|
||||
local_port = random.choice([80, 443, 445, 135, 139, 3389, 5985, 8080, 53, 88, 389, 636,
|
||||
random.randint(49152, 65535)])
|
||||
if state == "LISTEN":
|
||||
remote_ip = "0.0.0.0"
|
||||
remote_port = 0
|
||||
else:
|
||||
remote_ip = random.choice([rand_ext_ip(), random.choice(ALL_HOSTS)["ip"]])
|
||||
remote_port = random.choice([80, 443, 8080, 3389, 445, 53, 88, 389, 636,
|
||||
random.randint(1024, 65535)])
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"Timestamp": ts_str(),
|
||||
"Pid": random.randint(100, 65000),
|
||||
"Name": proc,
|
||||
"Status": state,
|
||||
"Protocol": proto,
|
||||
"Laddr.IP": host["ip"],
|
||||
"Laddr.Port": local_port,
|
||||
"Raddr.IP": remote_ip,
|
||||
"Raddr.Port": remote_port,
|
||||
"Username": rand_user(),
|
||||
})
|
||||
return rows
|
||||
|
||||
print("Generating Velociraptor test CSVs...")
|
||||
rows = gen_netstat()
|
||||
write_csv("01_netstat_connections.csv",
|
||||
["Hostname", "Timestamp", "Pid", "Name", "Status", "Protocol",
|
||||
"Laddr.IP", "Laddr.Port", "Raddr.IP", "Raddr.Port", "Username"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 2. DNS queries (Velociraptor: Windows.Network.DNS)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_dns():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
n_queries = random.randint(15, 60)
|
||||
for _ in range(n_queries):
|
||||
if random.random() < 0.12:
|
||||
domain = random.choice(AUP_DOMAINS)
|
||||
else:
|
||||
domain = random.choice(LEGIT_DOMAINS)
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"EventTime": ts_str(),
|
||||
"QueryName": domain,
|
||||
"QueryType": random.choice(["A", "AAAA", "CNAME", "MX", "TXT", "A", "A"]),
|
||||
"ResponseCode": random.choice(["NOERROR", "NOERROR", "NOERROR", "NXDOMAIN", "SERVFAIL"]),
|
||||
"SourceIP": host["ip"],
|
||||
"AnswerIP": rand_ext_ip() if random.random() > 0.2 else "",
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_dns()
|
||||
write_csv("02_dns_queries.csv",
|
||||
["Hostname", "EventTime", "QueryName", "QueryType", "ResponseCode", "SourceIP", "AnswerIP"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 3. Process listing (Velociraptor: Windows.System.Pslist)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_pslist():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
user = rand_user()
|
||||
for proc in LEGIT_PROCESSES:
|
||||
pid = random.randint(100, 65000)
|
||||
ppid = random.randint(1, 20) if proc in ("svchost.exe", "csrss.exe", "lsass.exe") else random.randint(100, 60000)
|
||||
rows.append({
|
||||
"ComputerName": host["hostname"],
|
||||
"CreateTime": ts_str(),
|
||||
"Pid": pid,
|
||||
"PPid": ppid,
|
||||
"Name": proc,
|
||||
"CommandLine": f"C:\\Windows\\System32\\{proc}" if proc != "System" else "System",
|
||||
"Username": f"ACME\\{user}" if proc not in ("System", "smss.exe", "csrss.exe") else "NT AUTHORITY\\SYSTEM",
|
||||
"MemoryUsage": random.randint(1024, 500000),
|
||||
})
|
||||
# Sprinkle AUP processes on ~15% of hosts
|
||||
if random.random() < 0.15:
|
||||
aup_proc = random.choice(AUP_PROCESSES)
|
||||
rows.append({
|
||||
"ComputerName": host["hostname"],
|
||||
"CreateTime": ts_str(),
|
||||
"Pid": random.randint(10000, 65000),
|
||||
"PPid": random.randint(100, 60000),
|
||||
"Name": aup_proc,
|
||||
"CommandLine": f"C:\\Users\\{user}\\AppData\\Local\\{aup_proc.replace('.exe', '')}\\{aup_proc}",
|
||||
"Username": f"ACME\\{user}",
|
||||
"MemoryUsage": random.randint(50000, 400000),
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_pslist()
|
||||
write_csv("03_process_listing.csv",
|
||||
["ComputerName", "CreateTime", "Pid", "PPid", "Name", "CommandLine", "Username", "MemoryUsage"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 4. Network interfaces (Velociraptor: Windows.Network.Interfaces)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_interfaces():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
h = host
|
||||
rows.append({
|
||||
"Hostname": h["hostname"],
|
||||
"Timestamp": ts_str(),
|
||||
"Name": "Ethernet0",
|
||||
"MacAddress": h.get("mac", f"00:1A:2B:{random.randint(0,255):02X}:{random.randint(0,255):02X}:{random.randint(0,255):02X}"),
|
||||
"IP": h["ip"],
|
||||
"Netmask": "255.255.255.0",
|
||||
"Gateway": h["ip"].rsplit(".", 1)[0] + ".1",
|
||||
"DNSServer": "10.10.1.10",
|
||||
"DHCPEnabled": random.choice(["True", "False"]),
|
||||
"Status": "Up",
|
||||
})
|
||||
# Some hosts have a secondary NIC
|
||||
if random.random() < 0.15:
|
||||
rows.append({
|
||||
"Hostname": h["hostname"],
|
||||
"Timestamp": ts_str(),
|
||||
"Name": "WiFi",
|
||||
"MacAddress": f"00:1A:2B:{random.randint(0,255):02X}:{random.randint(0,255):02X}:{random.randint(0,255):02X}",
|
||||
"IP": f"192.168.1.{random.randint(100,254)}",
|
||||
"Netmask": "255.255.255.0",
|
||||
"Gateway": "192.168.1.1",
|
||||
"DNSServer": "192.168.1.1",
|
||||
"DHCPEnabled": "True",
|
||||
"Status": "Up",
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_interfaces()
|
||||
write_csv("04_network_interfaces.csv",
|
||||
["Hostname", "Timestamp", "Name", "MacAddress", "IP", "Netmask", "Gateway", "DNSServer", "DHCPEnabled", "Status"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 5. Logged-in users (Velociraptor: Windows.Sys.Users)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_logged_users():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
n_users = random.randint(1, 3)
|
||||
used = set()
|
||||
for _ in range(n_users):
|
||||
u = rand_user()
|
||||
while u in used:
|
||||
u = rand_user()
|
||||
used.add(u)
|
||||
logon_ts = rand_ts()
|
||||
rows.append({
|
||||
"ComputerName": host["hostname"],
|
||||
"SourceIP": host["ip"],
|
||||
"User": f"ACME\\{u}",
|
||||
"LogonType": random.choice([2, 3, 10, 10, 2]), # 2=Interactive, 3=Network, 10=RDP
|
||||
"LogonTime": ts_str(logon_ts),
|
||||
"LogoffTime": ts_str(logon_ts + timedelta(hours=random.uniform(0.5, 10))) if random.random() > 0.3 else "",
|
||||
"OS": host.get("os", "Windows 10 Enterprise"),
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_logged_users()
|
||||
write_csv("05_logged_in_users.csv",
|
||||
["ComputerName", "SourceIP", "User", "LogonType", "LogonTime", "LogoffTime", "OS"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 6. Scheduled tasks (Velociraptor: Windows.System.TaskScheduler)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
SCHED_TASKS = [
|
||||
("\\Microsoft\\Windows\\UpdateOrchestrator\\Schedule Scan", "C:\\Windows\\System32\\usoclient.exe StartScan"),
|
||||
("\\Microsoft\\Windows\\Defrag\\ScheduledDefrag", "C:\\Windows\\System32\\defrag.exe -c -h -o"),
|
||||
("\\Microsoft\\Windows\\WindowsUpdate\\Automatic App Update", "C:\\Windows\\System32\\UsoClient.exe"),
|
||||
("\\ACME\\Backup", "C:\\Tools\\backup.ps1"),
|
||||
("\\ACME\\Inventory", "C:\\Tools\\inventory.exe --scan"),
|
||||
]
|
||||
|
||||
def gen_sched_tasks():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
for task_name, cmd in SCHED_TASKS:
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"IP": host["ip"],
|
||||
"TaskName": task_name,
|
||||
"CommandLine": cmd,
|
||||
"Enabled": random.choice(["True", "True", "True", "False"]),
|
||||
"LastRunTime": ts_str(),
|
||||
"NextRunTime": ts_str(),
|
||||
"Username": "NT AUTHORITY\\SYSTEM" if "Microsoft" in task_name else f"ACME\\svc_backup",
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_sched_tasks()
|
||||
write_csv("06_scheduled_tasks.csv",
|
||||
["Hostname", "IP", "TaskName", "CommandLine", "Enabled", "LastRunTime", "NextRunTime", "Username"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 7. Browser history (Velociraptor: Windows.Application.Chrome.History)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_browser_history():
|
||||
rows = []
|
||||
for host in HOSTS: # only workstations
|
||||
user = rand_user()
|
||||
n_entries = random.randint(10, 35)
|
||||
for _ in range(n_entries):
|
||||
if random.random() < 0.15:
|
||||
domain = random.choice(AUP_DOMAINS)
|
||||
url = f"https://{domain}/{random.choice(['', 'home', 'watch', 'play', 'search?q=free+movies', 'category/popular'])}"
|
||||
title_map = {
|
||||
"bet365": "Bet365 - Sports Betting",
|
||||
"pokerstars": "PokerStars - Online Poker",
|
||||
"netflix": "Netflix - Watch TV Shows",
|
||||
"steam": "Steam Store",
|
||||
"piratebay": "The Pirate Bay",
|
||||
"pornhub": "Pornhub",
|
||||
"facebook": "Facebook - Log In",
|
||||
"tiktok": "TikTok - Make Your Day",
|
||||
"indeed": "Indeed - Job Search",
|
||||
"amazon": "Amazon.com - Shopping",
|
||||
}
|
||||
title = next((v for k, v in title_map.items() if k in domain), domain)
|
||||
else:
|
||||
domain = random.choice(LEGIT_DOMAINS)
|
||||
url = f"https://{domain}/{random.choice(['', 'docs', 'api', 'search', 'dashboard', 'inbox'])}"
|
||||
title = domain
|
||||
rows.append({
|
||||
"ComputerName": host["hostname"],
|
||||
"SourceAddress": host["ip"],
|
||||
"User": user,
|
||||
"URL": url,
|
||||
"Title": title,
|
||||
"VisitTime": ts_str(),
|
||||
"VisitCount": random.randint(1, 20),
|
||||
"Browser": random.choice(["Chrome", "Edge", "Chrome", "Chrome"]),
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_browser_history()
|
||||
write_csv("07_browser_history.csv",
|
||||
["ComputerName", "SourceAddress", "User", "URL", "Title", "VisitTime", "VisitCount", "Browser"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 8. Sysmon network connections (Sysmon Event ID 3)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_sysmon_network():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
n_events = random.randint(10, 50)
|
||||
user = rand_user()
|
||||
for _ in range(n_events):
|
||||
proc = random.choice(LEGIT_PROCESSES + (["chrome.exe", "msedge.exe"] * 3))
|
||||
dst_ip = random.choice([rand_ext_ip(), random.choice(ALL_HOSTS)["ip"]])
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"EventTime": ts_str(),
|
||||
"EventID": 3,
|
||||
"Image": f"C:\\Windows\\System32\\{proc}" if proc not in ("chrome.exe", "msedge.exe") else f"C:\\Program Files\\{proc}",
|
||||
"User": f"ACME\\{user}",
|
||||
"Protocol": random.choice(["tcp", "udp"]),
|
||||
"SourceIp": host["ip"],
|
||||
"SourcePort": random.randint(49152, 65535),
|
||||
"DestinationIp": dst_ip,
|
||||
"DestinationPort": random.choice([80, 443, 53, 445, 389, 3389, 8080]),
|
||||
"DestinationHostname": random.choice(LEGIT_DOMAINS + AUP_DOMAINS[:3]) if random.random() < 0.1 else "",
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_sysmon_network()
|
||||
write_csv("08_sysmon_network.csv",
|
||||
["Hostname", "EventTime", "EventID", "Image", "User", "Protocol",
|
||||
"SourceIp", "SourcePort", "DestinationIp", "DestinationPort", "DestinationHostname"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 9. Autoruns (Velociraptor: Windows.Sys.AutoRuns)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
AUTORUN_ENTRIES = [
|
||||
("MicrosoftEdgeAutoLaunch", "C:\\Program Files\\Microsoft\\Edge\\msedge.exe --no-startup-window"),
|
||||
("SecurityHealth", "C:\\Windows\\System32\\SecurityHealthSystray.exe"),
|
||||
("OneDrive", "C:\\Users\\{user}\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe /background"),
|
||||
("WindowsDefender", "C:\\ProgramData\\Microsoft\\Windows Defender\\MsMpEng.exe"),
|
||||
]
|
||||
|
||||
AUP_AUTORUNS = [
|
||||
("Steam", "C:\\Program Files (x86)\\Steam\\steam.exe -silent"),
|
||||
("Discord", "C:\\Users\\{user}\\AppData\\Local\\Discord\\Update.exe --processStart Discord.exe"),
|
||||
("Spotify", "C:\\Users\\{user}\\AppData\\Roaming\\Spotify\\Spotify.exe /minimized"),
|
||||
("uTorrent", "C:\\Users\\{user}\\AppData\\Roaming\\uTorrent\\uTorrent.exe /minimized"),
|
||||
]
|
||||
|
||||
def gen_autoruns():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
user = rand_user()
|
||||
for name, cmd in AUTORUN_ENTRIES:
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"IP": host["ip"],
|
||||
"EntryName": name,
|
||||
"EntryPath": cmd.replace("{user}", user),
|
||||
"Category": "Logon",
|
||||
"Enabled": "True",
|
||||
"Signer": "Microsoft Corporation" if "Microsoft" in cmd or "Windows" in cmd else "(Not Signed)",
|
||||
"MD5": f"{random.randint(0, 2**128):032x}",
|
||||
"Timestamp": ts_str(),
|
||||
})
|
||||
# ~20% of workstations have AUP autoruns
|
||||
if host in HOSTS and random.random() < 0.20:
|
||||
entry = random.choice(AUP_AUTORUNS)
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"IP": host["ip"],
|
||||
"EntryName": entry[0],
|
||||
"EntryPath": entry[1].replace("{user}", user),
|
||||
"Category": "Logon",
|
||||
"Enabled": "True",
|
||||
"Signer": "(Not Signed)",
|
||||
"MD5": f"{random.randint(0, 2**128):032x}",
|
||||
"Timestamp": ts_str(),
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_autoruns()
|
||||
write_csv("09_autoruns.csv",
|
||||
["Hostname", "IP", "EntryName", "EntryPath", "Category", "Enabled", "Signer", "MD5", "Timestamp"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 10. Windows Event Logs — Logon events (Event IDs 4624/4625)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_logon_events():
|
||||
rows = []
|
||||
for host in ALL_HOSTS:
|
||||
n_events = random.randint(8, 30)
|
||||
for _ in range(n_events):
|
||||
event_id = random.choice([4624, 4624, 4624, 4624, 4625])
|
||||
logon_type = random.choice([2, 3, 7, 10, 3, 3])
|
||||
user = rand_user()
|
||||
src_ip = random.choice([host["ip"], random.choice(ALL_HOSTS)["ip"], "127.0.0.1"])
|
||||
rows.append({
|
||||
"ComputerName": host["hostname"],
|
||||
"System.TimeCreated": ts_str(),
|
||||
"EventID": event_id,
|
||||
"LogonType": logon_type,
|
||||
"SubjectUserName": user,
|
||||
"SubjectUserSid": f"S-1-5-21-{random.randint(1000000,9999999)}-{random.randint(1000,9999)}",
|
||||
"SourceAddress": src_ip,
|
||||
"SourcePort": random.randint(1024, 65535),
|
||||
"Status": "0x0" if event_id == 4624 else "0xC000006D",
|
||||
"FailureReason": "" if event_id == 4624 else "Unknown user name or bad password",
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_logon_events()
|
||||
write_csv("10_logon_events.csv",
|
||||
["ComputerName", "System.TimeCreated", "EventID", "LogonType",
|
||||
"SubjectUserName", "SubjectUserSid", "SourceAddress", "SourcePort",
|
||||
"Status", "FailureReason"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 11. Proxy / web filter logs
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
def gen_proxy_logs():
|
||||
rows = []
|
||||
for host in HOSTS: # workstations only
|
||||
n_entries = random.randint(15, 50)
|
||||
user = rand_user()
|
||||
for _ in range(n_entries):
|
||||
if random.random() < 0.10:
|
||||
dom = random.choice(AUP_DOMAINS)
|
||||
url = f"https://{dom}/"
|
||||
action = random.choice(["BLOCKED", "ALLOWED", "ALLOWED"])
|
||||
category = random.choice(["Gambling", "Gaming", "Streaming", "Adult", "Social Media", "Shopping", "Piracy"])
|
||||
else:
|
||||
dom = random.choice(LEGIT_DOMAINS)
|
||||
url = f"https://{dom}/api/v1/resource"
|
||||
action = "ALLOWED"
|
||||
category = random.choice(["Business", "Technology", "Cloud Services", "Productivity"])
|
||||
rows.append({
|
||||
"Timestamp": ts_str(),
|
||||
"Hostname": host["hostname"],
|
||||
"SourceIP": host["ip"],
|
||||
"Username": f"ACME\\{user}",
|
||||
"URL": url,
|
||||
"Domain": dom,
|
||||
"Action": action,
|
||||
"Category": category,
|
||||
"Method": random.choice(["GET", "POST", "GET", "GET"]),
|
||||
"ResponseCode": random.choice([200, 200, 200, 301, 403, 404]) if action == "ALLOWED" else 403,
|
||||
"BytesSent": random.randint(100, 50000),
|
||||
"BytesReceived": random.randint(500, 500000),
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_proxy_logs()
|
||||
write_csv("11_proxy_logs.csv",
|
||||
["Timestamp", "Hostname", "SourceIP", "Username", "URL", "Domain",
|
||||
"Action", "Category", "Method", "ResponseCode", "BytesSent", "BytesReceived"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 12. File listing — suspicious downloads (Velociraptor: Windows.Search.FileFinder)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
DOWNLOAD_FILES = [
|
||||
("Q1_Budget_2026.xlsx", 245000, "a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6"),
|
||||
("meeting_notes.docx", 89000, "b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7"),
|
||||
("vpn_config.ovpn", 1200, "c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8"),
|
||||
("project_plan.pptx", 1500000, "d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9"),
|
||||
("setup.exe", 45000000, "e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0"),
|
||||
("crack_photoshop.exe", 12000000, "f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1"), # AUP
|
||||
("keygen_v2.exe", 500000, "a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2"), # AUP
|
||||
("steam_installer.exe", 3500000, "b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3"), # AUP
|
||||
("free_movie_2026.torrent", 45000, "c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4"), # AUP
|
||||
("salary_comparison.pdf", 320000, "d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5"),
|
||||
]
|
||||
|
||||
def gen_file_listing():
|
||||
rows = []
|
||||
for host in HOSTS:
|
||||
user = rand_user()
|
||||
n_files = random.randint(3, 8)
|
||||
selected = random.sample(DOWNLOAD_FILES, min(n_files, len(DOWNLOAD_FILES)))
|
||||
for fname, size, md5 in selected:
|
||||
rows.append({
|
||||
"Hostname": host["hostname"],
|
||||
"SourceIP": host["ip"],
|
||||
"FullPath": f"C:\\Users\\{user}\\Downloads\\{fname}",
|
||||
"FileName": fname,
|
||||
"Size": size,
|
||||
"MD5": md5,
|
||||
"SHA256": f"{random.randint(0, 2**256):064x}",
|
||||
"MTime": ts_str(),
|
||||
"CTime": ts_str(),
|
||||
"Username": f"ACME\\{user}",
|
||||
"OS": host.get("os", "Windows 10 Enterprise"),
|
||||
})
|
||||
return rows
|
||||
|
||||
rows = gen_file_listing()
|
||||
write_csv("12_file_listing.csv",
|
||||
["Hostname", "SourceIP", "FullPath", "FileName", "Size", "MD5", "SHA256",
|
||||
"MTime", "CTime", "Username", "OS"], rows)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Summary
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
total = sum(len(list((OUT / f).open())) - 1 for f in os.listdir(OUT) if f.endswith(".csv"))
|
||||
print(f"\nDone! 12 CSV files in {OUT}")
|
||||
print(f"Network: {len(ALL_HOSTS)} hosts, {len(USERS)} users, {len(SUBNETS)} subnets")
|
||||
print(f"AUP triggers in: DNS, browser history, proxy logs, autoruns, file listing, process list, sysmon")
|
||||
2013
backend/tests/test_csvs/01_netstat_connections.csv
Normal file
2965
backend/tests/test_csvs/02_dns_queries.csv
Normal file
1897
backend/tests/test_csvs/03_process_listing.csv
Normal file
95
backend/tests/test_csvs/04_network_interfaces.csv
Normal file
@@ -0,0 +1,95 @@
|
||||
Hostname,Timestamp,Name,MacAddress,IP,Netmask,Gateway,DNSServer,DHCPEnabled,Status
|
||||
HR-WS-001,2026-02-10T16:24:26.900Z,Ethernet0,00:1A:2B:01:03:07,10.10.2.101,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
FIN-WS-002,2026-02-12T21:40:50.754Z,Ethernet0,00:1A:2B:02:06:0E,10.10.3.102,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
SLS-WS-003,2026-02-18T04:41:53.395Z,Ethernet0,00:1A:2B:03:09:15,10.10.1.103,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
ENG-WS-004,2026-02-10T09:17:24.303Z,Ethernet0,00:1A:2B:04:0C:1C,10.10.2.104,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
LEG-WS-005,2026-02-17T19:14:58.917Z,Ethernet0,00:1A:2B:05:0F:23,10.10.3.105,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
MKT-WS-006,2026-02-14T17:52:45.679Z,Ethernet0,00:1A:2B:06:12:2A,10.10.1.106,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
EXEC-WS-007,2026-02-12T06:47:59.653Z,Ethernet0,00:1A:2B:07:15:31,10.10.2.107,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
IT-WS-008,2026-02-13T00:45:43.505Z,Ethernet0,00:1A:2B:08:18:38,10.10.3.108,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
HR-WS-009,2026-02-16T10:06:59.053Z,Ethernet0,00:1A:2B:09:1B:3F,10.10.1.109,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
FIN-WS-010,2026-02-15T17:42:42.094Z,Ethernet0,00:1A:2B:0A:1E:46,10.10.2.110,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
SLS-WS-011,2026-02-17T22:02:06.433Z,Ethernet0,00:1A:2B:0B:21:4D,10.10.3.111,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
ENG-WS-012,2026-02-19T05:25:25.672Z,Ethernet0,00:1A:2B:0C:24:54,10.10.1.112,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
LEG-WS-013,2026-02-16T20:31:43.770Z,Ethernet0,00:1A:2B:0D:27:5B,10.10.2.113,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
MKT-WS-014,2026-02-17T09:41:02.510Z,Ethernet0,00:1A:2B:0E:2A:62,10.10.3.114,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
EXEC-WS-015,2026-02-15T10:28:26.740Z,Ethernet0,00:1A:2B:0F:2D:69,10.10.1.115,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
EXEC-WS-015,2026-02-10T11:51:53.112Z,WiFi,00:1A:2B:55:56:F5,192.168.1.253,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
IT-WS-016,2026-02-16T04:21:28.306Z,Ethernet0,00:1A:2B:10:30:70,10.10.2.116,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
HR-WS-017,2026-02-16T17:05:22.194Z,Ethernet0,00:1A:2B:11:33:77,10.10.3.117,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
FIN-WS-018,2026-02-12T18:00:28.942Z,Ethernet0,00:1A:2B:12:36:7E,10.10.1.118,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
SLS-WS-019,2026-02-15T04:56:45.920Z,Ethernet0,00:1A:2B:13:39:85,10.10.2.119,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
ENG-WS-020,2026-02-11T10:01:43.793Z,Ethernet0,00:1A:2B:14:3C:8C,10.10.3.120,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
LEG-WS-021,2026-02-11T21:06:19.348Z,Ethernet0,00:1A:2B:15:3F:93,10.10.1.121,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
MKT-WS-022,2026-02-10T20:29:46.294Z,Ethernet0,00:1A:2B:16:42:9A,10.10.2.122,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
EXEC-WS-023,2026-02-11T19:04:20.910Z,Ethernet0,00:1A:2B:17:45:A1,10.10.3.123,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
IT-WS-024,2026-02-16T09:50:42.318Z,Ethernet0,00:1A:2B:18:48:A8,10.10.1.124,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
IT-WS-024,2026-02-14T22:30:35.434Z,WiFi,00:1A:2B:BF:1F:78,192.168.1.203,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
HR-WS-025,2026-02-13T09:55:26.913Z,Ethernet0,00:1A:2B:19:4B:AF,10.10.2.125,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
FIN-WS-026,2026-02-16T17:25:01.286Z,Ethernet0,00:1A:2B:1A:4E:B6,10.10.3.126,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
SLS-WS-027,2026-02-14T18:31:05.971Z,Ethernet0,00:1A:2B:1B:51:BD,10.10.1.127,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
ENG-WS-028,2026-02-20T14:27:15.813Z,Ethernet0,00:1A:2B:1C:54:C4,10.10.2.128,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
LEG-WS-029,2026-02-20T12:27:41.544Z,Ethernet0,00:1A:2B:1D:57:CB,10.10.3.129,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
LEG-WS-029,2026-02-16T09:22:52.989Z,WiFi,00:1A:2B:E0:FE:1A,192.168.1.111,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
MKT-WS-030,2026-02-15T04:55:56.987Z,Ethernet0,00:1A:2B:1E:5A:D2,10.10.1.130,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
EXEC-WS-031,2026-02-12T14:44:11.880Z,Ethernet0,00:1A:2B:1F:5D:D9,10.10.2.131,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
IT-WS-032,2026-02-15T05:33:06.487Z,Ethernet0,00:1A:2B:20:60:E0,10.10.3.132,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
HR-WS-033,2026-02-11T05:39:51.291Z,Ethernet0,00:1A:2B:21:63:E7,10.10.1.133,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
FIN-WS-034,2026-02-11T11:33:18.757Z,Ethernet0,00:1A:2B:22:66:EE,10.10.2.134,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
SLS-WS-035,2026-02-11T00:04:29.942Z,Ethernet0,00:1A:2B:23:69:F5,10.10.3.135,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
ENG-WS-036,2026-02-17T14:12:29.280Z,Ethernet0,00:1A:2B:24:6C:FC,10.10.1.136,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
LEG-WS-037,2026-02-14T08:36:09.053Z,Ethernet0,00:1A:2B:25:6F:03,10.10.2.137,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
MKT-WS-038,2026-02-17T03:32:51.019Z,Ethernet0,00:1A:2B:26:72:0A,10.10.3.138,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
EXEC-WS-039,2026-02-12T14:30:02.287Z,Ethernet0,00:1A:2B:27:75:11,10.10.1.139,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
IT-WS-040,2026-02-16T05:42:19.535Z,Ethernet0,00:1A:2B:28:78:18,10.10.2.140,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
HR-WS-041,2026-02-12T22:00:14.852Z,Ethernet0,00:1A:2B:29:7B:1F,10.10.3.141,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
FIN-WS-042,2026-02-14T08:06:34.719Z,Ethernet0,00:1A:2B:2A:7E:26,10.10.1.142,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
SLS-WS-043,2026-02-15T09:46:46.751Z,Ethernet0,00:1A:2B:2B:81:2D,10.10.2.143,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
ENG-WS-044,2026-02-13T22:35:19.386Z,Ethernet0,00:1A:2B:2C:84:34,10.10.3.144,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
LEG-WS-045,2026-02-16T14:36:44.741Z,Ethernet0,00:1A:2B:2D:87:3B,10.10.1.145,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
MKT-WS-046,2026-02-10T20:21:06.160Z,Ethernet0,00:1A:2B:2E:8A:42,10.10.2.146,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
EXEC-WS-047,2026-02-13T17:28:04.672Z,Ethernet0,00:1A:2B:2F:8D:49,10.10.3.147,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
EXEC-WS-047,2026-02-15T05:39:34.377Z,WiFi,00:1A:2B:56:C7:C5,192.168.1.173,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
IT-WS-048,2026-02-10T12:18:07.676Z,Ethernet0,00:1A:2B:30:90:50,10.10.1.148,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
IT-WS-048,2026-02-16T04:58:53.462Z,WiFi,00:1A:2B:0D:DC:8E,192.168.1.105,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
HR-WS-049,2026-02-15T16:58:23.088Z,Ethernet0,00:1A:2B:31:93:57,10.10.2.149,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
HR-WS-049,2026-02-11T10:08:21.611Z,WiFi,00:1A:2B:C0:BD:32,192.168.1.104,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
FIN-WS-050,2026-02-18T17:07:33.846Z,Ethernet0,00:1A:2B:32:96:5E,10.10.3.150,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
SLS-WS-051,2026-02-17T21:04:50.875Z,Ethernet0,00:1A:2B:33:99:65,10.10.1.151,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
ENG-WS-052,2026-02-12T08:55:29.512Z,Ethernet0,00:1A:2B:34:9C:6C,10.10.2.152,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
LEG-WS-053,2026-02-17T19:12:11.653Z,Ethernet0,00:1A:2B:35:9F:73,10.10.3.153,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
MKT-WS-054,2026-02-11T10:29:55.699Z,Ethernet0,00:1A:2B:36:A2:7A,10.10.1.154,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
EXEC-WS-055,2026-02-19T22:37:21.448Z,Ethernet0,00:1A:2B:37:A5:81,10.10.2.155,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
EXEC-WS-055,2026-02-15T04:05:15.992Z,WiFi,00:1A:2B:6F:0B:FB,192.168.1.234,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
IT-WS-056,2026-02-11T14:10:01.331Z,Ethernet0,00:1A:2B:38:A8:88,10.10.3.156,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
HR-WS-057,2026-02-19T21:26:13.167Z,Ethernet0,00:1A:2B:39:AB:8F,10.10.1.157,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
FIN-WS-058,2026-02-15T03:50:27.520Z,Ethernet0,00:1A:2B:3A:AE:96,10.10.2.158,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
SLS-WS-059,2026-02-15T13:42:58.594Z,Ethernet0,00:1A:2B:3B:B1:9D,10.10.3.159,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
ENG-WS-060,2026-02-14T20:41:02.310Z,Ethernet0,00:1A:2B:3C:B4:A4,10.10.1.160,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
LEG-WS-061,2026-02-13T22:18:10.231Z,Ethernet0,00:1A:2B:3D:B7:AB,10.10.2.161,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
LEG-WS-061,2026-02-13T20:39:08.097Z,WiFi,00:1A:2B:98:B3:9A,192.168.1.212,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
MKT-WS-062,2026-02-16T06:02:40.122Z,Ethernet0,00:1A:2B:3E:BA:B2,10.10.3.162,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
EXEC-WS-063,2026-02-19T05:26:24.391Z,Ethernet0,00:1A:2B:3F:BD:B9,10.10.1.163,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
IT-WS-064,2026-02-16T10:52:03.498Z,Ethernet0,00:1A:2B:40:C0:C0,10.10.2.164,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
HR-WS-065,2026-02-16T06:51:58.416Z,Ethernet0,00:1A:2B:41:C3:C7,10.10.3.165,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
FIN-WS-066,2026-02-12T21:32:27.236Z,Ethernet0,00:1A:2B:42:C6:CE,10.10.1.166,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
SLS-WS-067,2026-02-14T09:35:36.931Z,Ethernet0,00:1A:2B:43:C9:D5,10.10.2.167,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
ENG-WS-068,2026-02-17T01:03:57.807Z,Ethernet0,00:1A:2B:44:CC:DC,10.10.3.168,255.255.255.0,10.10.3.1,10.10.1.10,True,Up
|
||||
LEG-WS-069,2026-02-18T07:35:16.538Z,Ethernet0,00:1A:2B:45:CF:E3,10.10.1.169,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
MKT-WS-070,2026-02-13T22:47:29.628Z,Ethernet0,00:1A:2B:46:D2:EA,10.10.2.170,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
EXEC-WS-071,2026-02-10T22:35:50.568Z,Ethernet0,00:1A:2B:47:D5:F1,10.10.3.171,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
IT-WS-072,2026-02-10T18:39:18.543Z,Ethernet0,00:1A:2B:48:D8:F8,10.10.1.172,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
HR-WS-073,2026-02-15T02:52:48.715Z,Ethernet0,00:1A:2B:49:DB:FF,10.10.2.173,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
FIN-WS-074,2026-02-16T15:48:43.982Z,Ethernet0,00:1A:2B:4A:DE:06,10.10.3.174,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
FIN-WS-074,2026-02-15T03:16:41.128Z,WiFi,00:1A:2B:E5:58:D5,192.168.1.187,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
SLS-WS-075,2026-02-13T19:56:33.469Z,Ethernet0,00:1A:2B:4B:E1:0D,10.10.1.175,255.255.255.0,10.10.1.1,10.10.1.10,False,Up
|
||||
SLS-WS-075,2026-02-13T19:51:59.764Z,WiFi,00:1A:2B:85:45:E7,192.168.1.101,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
DC-01,2026-02-11T04:33:29.426Z,Ethernet0,00:1A:2B:AA:01:01,10.10.1.10,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
DC-02,2026-02-19T18:46:33.305Z,Ethernet0,00:1A:2B:AA:02:02,10.10.2.10,255.255.255.0,10.10.2.1,10.10.1.10,True,Up
|
||||
DC-02,2026-02-18T23:30:06.254Z,WiFi,00:1A:2B:CC:F1:9E,192.168.1.104,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
FILE-01,2026-02-18T11:12:43.756Z,Ethernet0,00:1A:2B:AA:03:03,10.10.1.11,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
FILE-01,2026-02-11T03:22:46.203Z,WiFi,00:1A:2B:CE:97:70,192.168.1.182,255.255.255.0,192.168.1.1,192.168.1.1,True,Up
|
||||
EXCH-01,2026-02-19T04:32:00.039Z,Ethernet0,00:1A:2B:AA:04:04,10.10.1.12,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
WEB-01,2026-02-18T10:09:44.001Z,Ethernet0,00:1A:2B:AA:05:05,10.10.3.10,255.255.255.0,10.10.3.1,10.10.1.10,False,Up
|
||||
SQL-01,2026-02-11T21:40:03.308Z,Ethernet0,00:1A:2B:AA:06:06,10.10.2.11,255.255.255.0,10.10.2.1,10.10.1.10,False,Up
|
||||
PROXY-01,2026-02-15T18:13:44.315Z,Ethernet0,00:1A:2B:AA:07:07,10.10.1.13,255.255.255.0,10.10.1.1,10.10.1.10,True,Up
|
||||
|
172
backend/tests/test_csvs/05_logged_in_users.csv
Normal file
@@ -0,0 +1,172 @@
|
||||
ComputerName,SourceIP,User,LogonType,LogonTime,LogoffTime,OS
|
||||
HR-WS-001,10.10.2.101,ACME\svc_backup,3,2026-02-10T19:47:11.019Z,,Windows 11 Enterprise
|
||||
FIN-WS-002,10.10.3.102,ACME\svc_sql,2,2026-02-20T13:32:12.532Z,,Windows Server 2022
|
||||
FIN-WS-002,10.10.3.102,ACME\jsmith,3,2026-02-11T07:54:47.111Z,2026-02-11T09:12:28.977Z,Windows Server 2022
|
||||
SLS-WS-003,10.10.1.103,ACME\svc_web,10,2026-02-19T16:28:42.907Z,2026-02-19T19:28:32.631Z,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,ACME\svc_sql,2,2026-02-18T21:50:34.213Z,2026-02-19T07:20:53.104Z,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,ACME\emartinez,2,2026-02-11T09:06:27.246Z,2026-02-11T11:25:00.125Z,Windows Server 2019
|
||||
ENG-WS-004,10.10.2.104,ACME\cjohnson,2,2026-02-13T08:35:29.495Z,2026-02-13T15:52:05.734Z,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,ACME\idavis,10,2026-02-19T17:23:04.396Z,2026-02-20T00:37:16.818Z,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,ACME\admin,10,2026-02-14T03:25:34.410Z,2026-02-14T10:55:35.412Z,Windows 10 Enterprise
|
||||
LEG-WS-005,10.10.3.105,ACME\svc_sql,3,2026-02-17T17:50:11.604Z,2026-02-17T22:14:26.990Z,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,ACME\svc_web,10,2026-02-11T09:48:56.652Z,,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,ACME\bwilson,10,2026-02-10T18:48:03.885Z,2026-02-11T01:51:20.678Z,Windows 11 Enterprise
|
||||
MKT-WS-006,10.10.1.106,ACME\svc_backup,10,2026-02-13T03:05:51.107Z,2026-02-13T06:52:13.336Z,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,ACME\svc_web,3,2026-02-18T18:32:46.430Z,,Windows Server 2022
|
||||
EXEC-WS-007,10.10.2.107,ACME\jsmith,3,2026-02-20T01:44:44.694Z,,Windows Server 2019
|
||||
IT-WS-008,10.10.3.108,ACME\bwilson,10,2026-02-12T17:44:34.450Z,,Windows 10 Enterprise
|
||||
HR-WS-009,10.10.1.109,ACME\dlee,3,2026-02-12T16:59:52.610Z,2026-02-12T19:19:40.030Z,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,ACME\svc_sql,10,2026-02-19T05:07:55.057Z,2026-02-19T13:20:14.389Z,Windows 11 Enterprise
|
||||
FIN-WS-010,10.10.2.110,ACME\idavis,3,2026-02-15T00:54:53.507Z,2026-02-15T02:02:32.810Z,Windows Server 2022
|
||||
FIN-WS-010,10.10.2.110,ACME\emartinez,2,2026-02-11T17:22:16.798Z,,Windows Server 2022
|
||||
FIN-WS-010,10.10.2.110,ACME\svc_sql,2,2026-02-19T03:09:04.371Z,2026-02-19T09:08:46.020Z,Windows Server 2022
|
||||
SLS-WS-011,10.10.3.111,ACME\jsmith,10,2026-02-14T17:25:05.510Z,2026-02-14T20:21:23.416Z,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,ACME\admin,2,2026-02-20T05:47:59.301Z,2026-02-20T07:11:14.685Z,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,ACME\svc_sql,3,2026-02-19T00:44:15.031Z,,Windows Server 2019
|
||||
ENG-WS-012,10.10.1.112,ACME\jsmith,2,2026-02-16T17:08:17.109Z,,Windows 10 Enterprise
|
||||
LEG-WS-013,10.10.2.113,ACME\svc_sql,2,2026-02-13T01:36:09.002Z,,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,ACME\emartinez,2,2026-02-12T06:29:05.626Z,2026-02-12T10:13:07.627Z,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,ACME\fthompson,2,2026-02-19T16:37:13.912Z,2026-02-19T23:51:58.182Z,Windows 11 Enterprise
|
||||
MKT-WS-014,10.10.3.114,ACME\jsmith,2,2026-02-12T10:03:32.551Z,2026-02-12T15:02:40.993Z,Windows Server 2022
|
||||
EXEC-WS-015,10.10.1.115,ACME\bwilson,2,2026-02-11T17:51:50.902Z,,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,ACME\idavis,2,2026-02-19T18:27:36.117Z,,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,ACME\emartinez,3,2026-02-18T13:18:33.046Z,2026-02-18T18:49:58.167Z,Windows Server 2019
|
||||
IT-WS-016,10.10.2.116,ACME\svc_web,10,2026-02-20T00:10:52.698Z,2026-02-20T04:34:07.792Z,Windows 10 Enterprise
|
||||
IT-WS-016,10.10.2.116,ACME\svc_backup,10,2026-02-17T17:42:51.219Z,2026-02-18T01:45:38.050Z,Windows 10 Enterprise
|
||||
IT-WS-016,10.10.2.116,ACME\emartinez,2,2026-02-13T13:58:01.715Z,2026-02-13T20:07:49.098Z,Windows 10 Enterprise
|
||||
HR-WS-017,10.10.3.117,ACME\hbrown,10,2026-02-16T05:42:16.836Z,2026-02-16T13:26:56.569Z,Windows 11 Enterprise
|
||||
FIN-WS-018,10.10.1.118,ACME\svc_web,10,2026-02-17T04:02:44.098Z,2026-02-17T06:47:30.956Z,Windows Server 2022
|
||||
SLS-WS-019,10.10.2.119,ACME\dlee,2,2026-02-16T09:29:29.147Z,2026-02-16T13:51:01.526Z,Windows Server 2019
|
||||
SLS-WS-019,10.10.2.119,ACME\svc_backup,10,2026-02-15T15:19:14.774Z,2026-02-16T00:49:54.396Z,Windows Server 2019
|
||||
ENG-WS-020,10.10.3.120,ACME\bwilson,3,2026-02-11T20:14:59.199Z,2026-02-11T23:16:55.830Z,Windows 10 Enterprise
|
||||
ENG-WS-020,10.10.3.120,ACME\dlee,10,2026-02-16T22:35:18.680Z,,Windows 10 Enterprise
|
||||
ENG-WS-020,10.10.3.120,ACME\svc_sql,10,2026-02-18T17:04:59.041Z,2026-02-18T22:34:22.842Z,Windows 10 Enterprise
|
||||
LEG-WS-021,10.10.1.121,ACME\fthompson,10,2026-02-18T10:34:32.151Z,2026-02-18T14:11:49.252Z,Windows 11 Enterprise
|
||||
LEG-WS-021,10.10.1.121,ACME\svc_backup,2,2026-02-13T01:37:08.345Z,2026-02-13T06:31:18.977Z,Windows 11 Enterprise
|
||||
LEG-WS-021,10.10.1.121,ACME\idavis,2,2026-02-18T01:44:04.261Z,,Windows 11 Enterprise
|
||||
MKT-WS-022,10.10.2.122,ACME\svc_backup,2,2026-02-10T21:51:50.126Z,,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,ACME\svc_sql,10,2026-02-16T08:50:03.777Z,,Windows Server 2022
|
||||
EXEC-WS-023,10.10.3.123,ACME\bwilson,10,2026-02-14T22:10:06.595Z,2026-02-14T23:45:48.592Z,Windows Server 2019
|
||||
EXEC-WS-023,10.10.3.123,ACME\svc_sql,2,2026-02-11T17:04:35.135Z,2026-02-11T20:05:01.609Z,Windows Server 2019
|
||||
EXEC-WS-023,10.10.3.123,ACME\svc_backup,3,2026-02-19T22:57:28.638Z,2026-02-20T06:20:22.890Z,Windows Server 2019
|
||||
IT-WS-024,10.10.1.124,ACME\svc_web,3,2026-02-12T11:59:59.061Z,2026-02-12T12:35:57.980Z,Windows 10 Enterprise
|
||||
HR-WS-025,10.10.2.125,ACME\svc_sql,3,2026-02-15T05:08:57.237Z,,Windows 11 Enterprise
|
||||
HR-WS-025,10.10.2.125,ACME\idavis,10,2026-02-20T08:38:06.286Z,2026-02-20T11:25:35.220Z,Windows 11 Enterprise
|
||||
FIN-WS-026,10.10.3.126,ACME\idavis,10,2026-02-17T21:12:27.358Z,2026-02-18T01:27:41.010Z,Windows Server 2022
|
||||
FIN-WS-026,10.10.3.126,ACME\jsmith,10,2026-02-17T18:36:19.617Z,2026-02-18T04:06:32.766Z,Windows Server 2022
|
||||
FIN-WS-026,10.10.3.126,ACME\agarcia,2,2026-02-18T20:11:00.202Z,2026-02-19T04:07:24.421Z,Windows Server 2022
|
||||
SLS-WS-027,10.10.1.127,ACME\cjohnson,3,2026-02-16T01:15:04.595Z,2026-02-16T08:14:59.728Z,Windows Server 2019
|
||||
SLS-WS-027,10.10.1.127,ACME\svc_web,3,2026-02-11T04:57:49.338Z,2026-02-11T12:20:08.092Z,Windows Server 2019
|
||||
SLS-WS-027,10.10.1.127,ACME\jsmith,2,2026-02-14T22:36:10.872Z,2026-02-15T04:09:32.820Z,Windows Server 2019
|
||||
ENG-WS-028,10.10.2.128,ACME\emartinez,2,2026-02-10T10:43:30.295Z,2026-02-10T12:21:18.954Z,Windows 10 Enterprise
|
||||
ENG-WS-028,10.10.2.128,ACME\agarcia,10,2026-02-17T11:10:18.102Z,2026-02-17T16:02:50.839Z,Windows 10 Enterprise
|
||||
LEG-WS-029,10.10.3.129,ACME\emartinez,10,2026-02-14T10:49:11.753Z,,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,ACME\gwhite,10,2026-02-20T01:41:09.184Z,2026-02-20T08:35:51.265Z,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,ACME\svc_backup,3,2026-02-12T04:17:16.386Z,2026-02-12T12:39:20.882Z,Windows 11 Enterprise
|
||||
MKT-WS-030,10.10.1.130,ACME\dlee,10,2026-02-19T09:56:10.855Z,2026-02-19T19:48:24.356Z,Windows Server 2022
|
||||
MKT-WS-030,10.10.1.130,ACME\agarcia,10,2026-02-17T17:21:55.036Z,2026-02-17T22:09:46.832Z,Windows Server 2022
|
||||
EXEC-WS-031,10.10.2.131,ACME\admin,10,2026-02-10T16:44:36.145Z,,Windows Server 2019
|
||||
IT-WS-032,10.10.3.132,ACME\cjohnson,10,2026-02-12T21:01:40.843Z,2026-02-13T00:13:30.847Z,Windows 10 Enterprise
|
||||
IT-WS-032,10.10.3.132,ACME\svc_sql,3,2026-02-10T08:49:32.659Z,2026-02-10T10:39:13.588Z,Windows 10 Enterprise
|
||||
IT-WS-032,10.10.3.132,ACME\idavis,2,2026-02-20T11:31:40.794Z,2026-02-20T16:48:16.067Z,Windows 10 Enterprise
|
||||
HR-WS-033,10.10.1.133,ACME\agarcia,3,2026-02-12T11:07:06.421Z,2026-02-12T20:44:06.116Z,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,ACME\idavis,2,2026-02-12T21:15:41.371Z,2026-02-13T03:37:44.480Z,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,ACME\cjohnson,2,2026-02-10T13:57:00.717Z,2026-02-10T20:39:38.037Z,Windows 11 Enterprise
|
||||
FIN-WS-034,10.10.2.134,ACME\bwilson,3,2026-02-17T18:03:46.436Z,2026-02-18T03:19:43.816Z,Windows Server 2022
|
||||
SLS-WS-035,10.10.3.135,ACME\svc_sql,10,2026-02-15T12:57:07.282Z,2026-02-15T19:04:37.869Z,Windows Server 2019
|
||||
ENG-WS-036,10.10.1.136,ACME\svc_web,10,2026-02-18T15:40:50.583Z,2026-02-18T18:05:35.800Z,Windows 10 Enterprise
|
||||
LEG-WS-037,10.10.2.137,ACME\bwilson,10,2026-02-16T02:10:26.988Z,2026-02-16T06:56:01.127Z,Windows 11 Enterprise
|
||||
LEG-WS-037,10.10.2.137,ACME\jsmith,2,2026-02-17T10:55:10.623Z,2026-02-17T17:06:13.738Z,Windows 11 Enterprise
|
||||
LEG-WS-037,10.10.2.137,ACME\admin,3,2026-02-15T08:12:50.391Z,2026-02-15T16:48:40.322Z,Windows 11 Enterprise
|
||||
MKT-WS-038,10.10.3.138,ACME\admin,10,2026-02-14T14:22:36.434Z,2026-02-14T20:33:17.463Z,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,ACME\idavis,2,2026-02-10T12:33:19.862Z,,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,ACME\cjohnson,3,2026-02-14T03:03:39.346Z,,Windows Server 2022
|
||||
EXEC-WS-039,10.10.1.139,ACME\emartinez,3,2026-02-12T15:26:12.740Z,2026-02-12T18:23:31.810Z,Windows Server 2019
|
||||
IT-WS-040,10.10.2.140,ACME\idavis,2,2026-02-12T15:39:47.637Z,2026-02-12T22:11:33.746Z,Windows 10 Enterprise
|
||||
IT-WS-040,10.10.2.140,ACME\fthompson,2,2026-02-13T10:36:38.122Z,,Windows 10 Enterprise
|
||||
HR-WS-041,10.10.3.141,ACME\dlee,3,2026-02-15T16:45:18.354Z,,Windows 11 Enterprise
|
||||
FIN-WS-042,10.10.1.142,ACME\svc_backup,10,2026-02-13T01:05:29.949Z,2026-02-13T08:45:38.547Z,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,ACME\gwhite,10,2026-02-12T05:08:08.376Z,2026-02-12T14:57:07.908Z,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,ACME\bwilson,2,2026-02-11T09:12:22.768Z,2026-02-11T18:03:32.327Z,Windows Server 2022
|
||||
SLS-WS-043,10.10.2.143,ACME\emartinez,10,2026-02-10T08:11:58.189Z,2026-02-10T14:21:34.307Z,Windows Server 2019
|
||||
SLS-WS-043,10.10.2.143,ACME\svc_sql,10,2026-02-14T03:50:49.336Z,2026-02-14T12:06:10.084Z,Windows Server 2019
|
||||
ENG-WS-044,10.10.3.144,ACME\fthompson,3,2026-02-10T21:26:43.757Z,,Windows 10 Enterprise
|
||||
ENG-WS-044,10.10.3.144,ACME\hbrown,2,2026-02-15T13:43:23.944Z,2026-02-15T23:21:05.115Z,Windows 10 Enterprise
|
||||
ENG-WS-044,10.10.3.144,ACME\idavis,2,2026-02-11T21:26:42.687Z,2026-02-12T03:15:22.121Z,Windows 10 Enterprise
|
||||
LEG-WS-045,10.10.1.145,ACME\svc_backup,10,2026-02-14T13:34:41.010Z,2026-02-14T19:05:53.025Z,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,ACME\agarcia,10,2026-02-10T15:47:07.953Z,2026-02-11T00:44:00.362Z,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,ACME\bwilson,10,2026-02-15T20:33:32.011Z,2026-02-16T04:02:03.695Z,Windows 11 Enterprise
|
||||
MKT-WS-046,10.10.2.146,ACME\emartinez,3,2026-02-12T03:40:13.508Z,2026-02-12T08:12:36.966Z,Windows Server 2022
|
||||
EXEC-WS-047,10.10.3.147,ACME\svc_web,2,2026-02-20T04:37:26.574Z,2026-02-20T12:36:14.841Z,Windows Server 2019
|
||||
EXEC-WS-047,10.10.3.147,ACME\cjohnson,2,2026-02-17T03:09:07.678Z,2026-02-17T05:11:23.506Z,Windows Server 2019
|
||||
IT-WS-048,10.10.1.148,ACME\svc_backup,2,2026-02-11T18:49:32.007Z,,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,ACME\admin,10,2026-02-19T13:38:11.426Z,,Windows 10 Enterprise
|
||||
HR-WS-049,10.10.2.149,ACME\idavis,2,2026-02-13T09:27:02.147Z,2026-02-13T14:09:17.279Z,Windows 11 Enterprise
|
||||
HR-WS-049,10.10.2.149,ACME\cjohnson,10,2026-02-16T14:31:31.829Z,2026-02-16T20:21:16.254Z,Windows 11 Enterprise
|
||||
HR-WS-049,10.10.2.149,ACME\svc_web,10,2026-02-11T04:49:41.763Z,2026-02-11T10:53:32.643Z,Windows 11 Enterprise
|
||||
FIN-WS-050,10.10.3.150,ACME\svc_sql,10,2026-02-16T13:23:33.444Z,2026-02-16T15:30:49.957Z,Windows Server 2022
|
||||
FIN-WS-050,10.10.3.150,ACME\svc_web,10,2026-02-13T09:58:42.075Z,2026-02-13T16:29:30.095Z,Windows Server 2022
|
||||
SLS-WS-051,10.10.1.151,ACME\agarcia,10,2026-02-18T07:55:41.992Z,2026-02-18T10:54:03.481Z,Windows Server 2019
|
||||
SLS-WS-051,10.10.1.151,ACME\admin,2,2026-02-15T16:03:29.360Z,2026-02-16T00:17:55.642Z,Windows Server 2019
|
||||
ENG-WS-052,10.10.2.152,ACME\svc_web,10,2026-02-18T12:28:09.927Z,2026-02-18T16:46:29.828Z,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,ACME\idavis,3,2026-02-18T23:31:19.526Z,2026-02-19T08:54:17.009Z,Windows 10 Enterprise
|
||||
LEG-WS-053,10.10.3.153,ACME\gwhite,2,2026-02-16T21:58:16.692Z,,Windows 11 Enterprise
|
||||
MKT-WS-054,10.10.1.154,ACME\fthompson,10,2026-02-16T02:40:07.132Z,2026-02-16T06:05:45.953Z,Windows Server 2022
|
||||
EXEC-WS-055,10.10.2.155,ACME\svc_web,3,2026-02-14T12:56:53.489Z,,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,ACME\svc_sql,2,2026-02-16T19:46:18.314Z,2026-02-17T03:37:45.216Z,Windows Server 2019
|
||||
IT-WS-056,10.10.3.156,ACME\agarcia,10,2026-02-13T12:11:31.170Z,2026-02-13T20:23:26.255Z,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,ACME\bwilson,2,2026-02-18T07:47:28.631Z,2026-02-18T12:43:49.215Z,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,ACME\fthompson,10,2026-02-15T10:34:59.328Z,,Windows 10 Enterprise
|
||||
HR-WS-057,10.10.1.157,ACME\emartinez,10,2026-02-12T20:30:10.337Z,,Windows 11 Enterprise
|
||||
HR-WS-057,10.10.1.157,ACME\fthompson,10,2026-02-17T23:44:46.543Z,2026-02-18T08:06:20.732Z,Windows 11 Enterprise
|
||||
FIN-WS-058,10.10.2.158,ACME\admin,10,2026-02-14T22:49:09.181Z,,Windows Server 2022
|
||||
SLS-WS-059,10.10.3.159,ACME\agarcia,2,2026-02-17T03:15:51.656Z,,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,ACME\fthompson,10,2026-02-18T17:16:46.768Z,2026-02-18T21:18:32.972Z,Windows Server 2019
|
||||
ENG-WS-060,10.10.1.160,ACME\dlee,10,2026-02-11T01:43:00.260Z,,Windows 10 Enterprise
|
||||
LEG-WS-061,10.10.2.161,ACME\gwhite,2,2026-02-13T11:12:03.130Z,,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,ACME\fthompson,10,2026-02-16T23:31:29.658Z,2026-02-17T01:57:47.139Z,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,ACME\emartinez,2,2026-02-11T05:57:03.436Z,2026-02-11T06:42:29.220Z,Windows 11 Enterprise
|
||||
MKT-WS-062,10.10.3.162,ACME\svc_sql,2,2026-02-20T01:38:41.003Z,,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,ACME\agarcia,2,2026-02-18T12:14:12.295Z,2026-02-18T13:22:44.877Z,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,ACME\jsmith,3,2026-02-11T05:12:34.424Z,,Windows Server 2022
|
||||
EXEC-WS-063,10.10.1.163,ACME\admin,10,2026-02-15T01:20:24.037Z,2026-02-15T08:29:33.232Z,Windows Server 2019
|
||||
IT-WS-064,10.10.2.164,ACME\emartinez,2,2026-02-17T11:38:09.952Z,2026-02-17T21:34:46.199Z,Windows 10 Enterprise
|
||||
HR-WS-065,10.10.3.165,ACME\bwilson,2,2026-02-12T07:12:16.922Z,,Windows 11 Enterprise
|
||||
HR-WS-065,10.10.3.165,ACME\svc_backup,10,2026-02-11T00:56:04.540Z,2026-02-11T08:01:21.095Z,Windows 11 Enterprise
|
||||
FIN-WS-066,10.10.1.166,ACME\dlee,10,2026-02-19T05:57:37.028Z,2026-02-19T11:17:42.241Z,Windows Server 2022
|
||||
SLS-WS-067,10.10.2.167,ACME\svc_backup,2,2026-02-15T12:30:46.626Z,,Windows Server 2019
|
||||
ENG-WS-068,10.10.3.168,ACME\admin,10,2026-02-18T12:37:02.663Z,,Windows 10 Enterprise
|
||||
ENG-WS-068,10.10.3.168,ACME\cjohnson,10,2026-02-14T14:25:49.436Z,,Windows 10 Enterprise
|
||||
ENG-WS-068,10.10.3.168,ACME\dlee,10,2026-02-12T04:36:57.506Z,2026-02-12T05:41:35.348Z,Windows 10 Enterprise
|
||||
LEG-WS-069,10.10.1.169,ACME\fthompson,2,2026-02-16T20:07:50.289Z,2026-02-17T04:27:27.454Z,Windows 11 Enterprise
|
||||
MKT-WS-070,10.10.2.170,ACME\dlee,10,2026-02-14T11:37:16.104Z,2026-02-14T13:22:16.645Z,Windows Server 2022
|
||||
MKT-WS-070,10.10.2.170,ACME\svc_web,3,2026-02-10T08:22:31.467Z,2026-02-10T10:17:31.541Z,Windows Server 2022
|
||||
MKT-WS-070,10.10.2.170,ACME\svc_sql,3,2026-02-18T04:28:19.510Z,2026-02-18T11:48:49.289Z,Windows Server 2022
|
||||
EXEC-WS-071,10.10.3.171,ACME\admin,2,2026-02-14T21:25:07.016Z,2026-02-15T01:48:57.362Z,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,ACME\svc_backup,10,2026-02-12T20:12:27.125Z,2026-02-12T23:20:08.269Z,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,ACME\cjohnson,10,2026-02-16T09:32:31.958Z,2026-02-16T10:29:12.449Z,Windows Server 2019
|
||||
IT-WS-072,10.10.1.172,ACME\idavis,2,2026-02-19T15:23:35.302Z,2026-02-20T00:57:36.100Z,Windows 10 Enterprise
|
||||
HR-WS-073,10.10.2.173,ACME\emartinez,2,2026-02-14T09:20:44.855Z,2026-02-14T18:55:52.875Z,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,ACME\hbrown,2,2026-02-13T11:47:22.510Z,2026-02-13T13:44:56.845Z,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,ACME\cjohnson,3,2026-02-14T16:58:28.455Z,2026-02-14T22:43:13.054Z,Windows 11 Enterprise
|
||||
FIN-WS-074,10.10.3.174,ACME\dlee,10,2026-02-16T18:29:13.839Z,,Windows Server 2022
|
||||
FIN-WS-074,10.10.3.174,ACME\admin,10,2026-02-19T12:25:04.093Z,2026-02-19T15:25:32.007Z,Windows Server 2022
|
||||
FIN-WS-074,10.10.3.174,ACME\gwhite,10,2026-02-13T17:34:03.583Z,,Windows Server 2022
|
||||
SLS-WS-075,10.10.1.175,ACME\admin,10,2026-02-16T15:23:53.945Z,2026-02-16T16:04:05.964Z,Windows Server 2019
|
||||
SLS-WS-075,10.10.1.175,ACME\svc_sql,2,2026-02-14T19:49:21.691Z,2026-02-15T04:31:54.404Z,Windows Server 2019
|
||||
DC-01,10.10.1.10,ACME\admin,2,2026-02-15T17:23:38.033Z,2026-02-15T18:02:54.817Z,Windows Server 2022
|
||||
DC-02,10.10.2.10,ACME\admin,10,2026-02-12T03:04:20.793Z,2026-02-12T07:34:27.380Z,Windows Server 2022
|
||||
DC-02,10.10.2.10,ACME\gwhite,2,2026-02-17T22:41:13.350Z,,Windows Server 2022
|
||||
DC-02,10.10.2.10,ACME\dlee,10,2026-02-14T17:29:00.267Z,,Windows Server 2022
|
||||
FILE-01,10.10.1.11,ACME\bwilson,3,2026-02-11T21:18:31.543Z,,Windows Server 2019
|
||||
FILE-01,10.10.1.11,ACME\hbrown,10,2026-02-16T00:53:59.083Z,2026-02-16T03:20:43.181Z,Windows Server 2019
|
||||
FILE-01,10.10.1.11,ACME\emartinez,3,2026-02-18T20:53:55.592Z,2026-02-19T05:59:50.555Z,Windows Server 2019
|
||||
EXCH-01,10.10.1.12,ACME\bwilson,10,2026-02-12T16:54:40.664Z,,Windows Server 2022
|
||||
EXCH-01,10.10.1.12,ACME\dlee,10,2026-02-13T00:45:40.170Z,,Windows Server 2022
|
||||
EXCH-01,10.10.1.12,ACME\svc_sql,2,2026-02-19T12:13:54.245Z,2026-02-19T18:00:56.147Z,Windows Server 2022
|
||||
WEB-01,10.10.3.10,ACME\admin,2,2026-02-11T04:22:10.529Z,2026-02-11T10:31:15.005Z,Windows Server 2022
|
||||
WEB-01,10.10.3.10,ACME\hbrown,3,2026-02-20T02:24:15.106Z,2026-02-20T04:27:46.695Z,Windows Server 2022
|
||||
WEB-01,10.10.3.10,ACME\cjohnson,2,2026-02-11T15:43:33.584Z,2026-02-11T19:05:11.076Z,Windows Server 2022
|
||||
SQL-01,10.10.2.11,ACME\agarcia,2,2026-02-16T14:25:46.694Z,2026-02-16T15:21:39.280Z,Windows Server 2019
|
||||
SQL-01,10.10.2.11,ACME\svc_backup,2,2026-02-11T07:27:04.230Z,2026-02-11T09:20:41.606Z,Windows Server 2019
|
||||
PROXY-01,10.10.1.13,ACME\cjohnson,10,2026-02-17T00:47:34.831Z,2026-02-17T07:52:30.535Z,Windows Server 2022
|
||||
|
411
backend/tests/test_csvs/06_scheduled_tasks.csv
Normal file
@@ -0,0 +1,411 @@
|
||||
Hostname,IP,TaskName,CommandLine,Enabled,LastRunTime,NextRunTime,Username
|
||||
HR-WS-001,10.10.2.101,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-11T04:10:18.272Z,2026-02-13T00:59:27.083Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-001,10.10.2.101,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-18T22:21:06.535Z,2026-02-13T00:20:19.721Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-001,10.10.2.101,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-11T22:34:59.678Z,2026-02-10T14:58:18.353Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-001,10.10.2.101,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-19T10:19:22.767Z,2026-02-20T15:37:57.928Z,ACME\svc_backup
|
||||
HR-WS-001,10.10.2.101,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-18T16:32:05.355Z,2026-02-18T09:04:04.511Z,ACME\svc_backup
|
||||
FIN-WS-002,10.10.3.102,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T19:08:35.756Z,2026-02-17T04:24:25.316Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-002,10.10.3.102,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-12T10:09:36.081Z,2026-02-12T14:35:40.437Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-002,10.10.3.102,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-15T16:46:39.201Z,2026-02-18T17:27:26.085Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-002,10.10.3.102,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T19:09:52.759Z,2026-02-18T11:49:21.126Z,ACME\svc_backup
|
||||
FIN-WS-002,10.10.3.102,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-19T01:57:38.377Z,2026-02-19T03:39:09.043Z,ACME\svc_backup
|
||||
SLS-WS-003,10.10.1.103,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T23:33:16.784Z,2026-02-18T03:56:24.145Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-003,10.10.1.103,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-14T12:02:21.885Z,2026-02-10T09:06:28.155Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-003,10.10.1.103,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-10T08:29:32.180Z,2026-02-20T05:43:38.079Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-003,10.10.1.103,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-12T20:47:27.743Z,2026-02-14T20:40:38.686Z,ACME\svc_backup
|
||||
SLS-WS-003,10.10.1.103,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-15T08:44:46.616Z,2026-02-14T02:51:26.262Z,ACME\svc_backup
|
||||
ENG-WS-004,10.10.2.104,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-16T09:23:23.347Z,2026-02-14T22:26:28.830Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-004,10.10.2.104,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-20T06:44:32.640Z,2026-02-17T21:47:53.810Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-004,10.10.2.104,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-15T04:57:06.522Z,2026-02-12T18:35:33.511Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-004,10.10.2.104,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T19:05:56.454Z,2026-02-12T15:35:44.582Z,ACME\svc_backup
|
||||
ENG-WS-004,10.10.2.104,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T12:42:15.302Z,2026-02-20T10:33:44.912Z,ACME\svc_backup
|
||||
LEG-WS-005,10.10.3.105,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T06:05:12.613Z,2026-02-15T21:32:29.512Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-005,10.10.3.105,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-18T11:19:55.513Z,2026-02-10T22:00:16.747Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-005,10.10.3.105,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-16T12:32:08.627Z,2026-02-13T08:21:40.415Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-005,10.10.3.105,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-14T06:57:14.139Z,2026-02-11T10:11:17.762Z,ACME\svc_backup
|
||||
LEG-WS-005,10.10.3.105,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T03:50:01.070Z,2026-02-18T00:35:18.228Z,ACME\svc_backup
|
||||
MKT-WS-006,10.10.1.106,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-11T22:20:50.111Z,2026-02-17T09:07:59.275Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-006,10.10.1.106,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T03:21:15.355Z,2026-02-16T21:59:46.067Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-006,10.10.1.106,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-13T05:57:57.374Z,2026-02-10T16:59:40.813Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-006,10.10.1.106,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-11T23:34:05.389Z,2026-02-13T01:32:43.835Z,ACME\svc_backup
|
||||
MKT-WS-006,10.10.1.106,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-18T04:35:33.516Z,2026-02-13T17:55:50.780Z,ACME\svc_backup
|
||||
EXEC-WS-007,10.10.2.107,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T09:31:08.738Z,2026-02-17T19:09:26.442Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-007,10.10.2.107,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-11T21:48:25.002Z,2026-02-11T03:43:03.357Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-007,10.10.2.107,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-10T23:54:37.709Z,2026-02-14T23:33:07.222Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-007,10.10.2.107,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T19:14:09.341Z,2026-02-14T09:51:49.598Z,ACME\svc_backup
|
||||
EXEC-WS-007,10.10.2.107,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-19T10:07:28.683Z,2026-02-15T07:58:45.104Z,ACME\svc_backup
|
||||
IT-WS-008,10.10.3.108,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-11T11:58:13.097Z,2026-02-19T14:28:46.034Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-008,10.10.3.108,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-17T18:52:05.470Z,2026-02-19T08:37:29.557Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-008,10.10.3.108,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T12:54:47.353Z,2026-02-18T13:22:38.075Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-008,10.10.3.108,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-18T12:22:20.956Z,2026-02-15T10:44:19.542Z,ACME\svc_backup
|
||||
IT-WS-008,10.10.3.108,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-17T15:14:52.999Z,2026-02-15T10:03:00.756Z,ACME\svc_backup
|
||||
HR-WS-009,10.10.1.109,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-15T17:50:29.509Z,2026-02-19T02:51:18.037Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-009,10.10.1.109,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T18:45:37.946Z,2026-02-18T18:31:24.942Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-009,10.10.1.109,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-20T14:02:14.569Z,2026-02-16T07:54:05.576Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-009,10.10.1.109,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-12T14:46:40.034Z,2026-02-17T09:05:45.968Z,ACME\svc_backup
|
||||
HR-WS-009,10.10.1.109,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-20T12:34:17.719Z,2026-02-10T10:25:36.528Z,ACME\svc_backup
|
||||
FIN-WS-010,10.10.2.110,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T16:05:20.284Z,2026-02-11T14:09:53.006Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-010,10.10.2.110,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-18T17:20:38.118Z,2026-02-11T14:06:07.250Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-010,10.10.2.110,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T16:42:52.183Z,2026-02-18T13:30:07.185Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-010,10.10.2.110,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T16:08:48.332Z,2026-02-12T08:59:35.678Z,ACME\svc_backup
|
||||
FIN-WS-010,10.10.2.110,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-10T21:41:35.587Z,2026-02-19T02:02:26.909Z,ACME\svc_backup
|
||||
SLS-WS-011,10.10.3.111,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T07:35:36.579Z,2026-02-14T04:17:39.468Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-011,10.10.3.111,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-11T10:21:27.809Z,2026-02-19T22:23:50.559Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-011,10.10.3.111,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-17T23:54:51.119Z,2026-02-11T11:19:59.085Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-011,10.10.3.111,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-18T14:59:56.507Z,2026-02-10T15:12:34.534Z,ACME\svc_backup
|
||||
SLS-WS-011,10.10.3.111,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T01:42:27.441Z,2026-02-15T16:42:53.961Z,ACME\svc_backup
|
||||
ENG-WS-012,10.10.1.112,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T08:47:19.597Z,2026-02-15T23:16:44.221Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-012,10.10.1.112,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-16T05:17:16.467Z,2026-02-20T15:14:36.400Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-012,10.10.1.112,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-12T17:04:07.300Z,2026-02-17T13:33:01.444Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-012,10.10.1.112,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-19T10:41:22.404Z,2026-02-15T08:14:38.725Z,ACME\svc_backup
|
||||
ENG-WS-012,10.10.1.112,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T19:29:02.351Z,2026-02-11T19:48:45.196Z,ACME\svc_backup
|
||||
LEG-WS-013,10.10.2.113,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T12:38:12.330Z,2026-02-17T15:08:18.153Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-013,10.10.2.113,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-12T12:39:57.944Z,2026-02-16T08:29:18.173Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-013,10.10.2.113,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T16:53:14.600Z,2026-02-10T15:48:25.569Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-013,10.10.2.113,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-18T23:37:15.766Z,2026-02-16T04:26:54.647Z,ACME\svc_backup
|
||||
LEG-WS-013,10.10.2.113,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-19T18:56:57.978Z,2026-02-18T08:05:41.707Z,ACME\svc_backup
|
||||
MKT-WS-014,10.10.3.114,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T04:54:01.657Z,2026-02-16T08:42:00.134Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-014,10.10.3.114,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-16T00:19:53.914Z,2026-02-15T07:55:08.380Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-014,10.10.3.114,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-17T02:15:29.456Z,2026-02-17T14:37:46.580Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-014,10.10.3.114,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-19T03:41:01.814Z,2026-02-18T20:49:02.270Z,ACME\svc_backup
|
||||
MKT-WS-014,10.10.3.114,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-18T22:20:31.364Z,2026-02-16T10:39:04.357Z,ACME\svc_backup
|
||||
EXEC-WS-015,10.10.1.115,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-16T00:25:37.310Z,2026-02-11T04:27:50.639Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-015,10.10.1.115,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T21:45:43.509Z,2026-02-17T07:13:31.528Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-015,10.10.1.115,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-18T10:33:00.550Z,2026-02-12T03:49:17.618Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-015,10.10.1.115,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-19T13:57:59.293Z,2026-02-12T17:08:19.461Z,ACME\svc_backup
|
||||
EXEC-WS-015,10.10.1.115,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-20T15:58:21.276Z,2026-02-18T02:43:23.938Z,ACME\svc_backup
|
||||
IT-WS-016,10.10.2.116,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-11T17:53:34.407Z,2026-02-15T13:47:25.202Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-016,10.10.2.116,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-19T05:34:08.201Z,2026-02-10T12:07:30.178Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-016,10.10.2.116,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T00:09:46.336Z,2026-02-15T06:50:54.924Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-016,10.10.2.116,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T09:11:55.347Z,2026-02-19T18:34:52.818Z,ACME\svc_backup
|
||||
IT-WS-016,10.10.2.116,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-11T23:33:28.562Z,2026-02-17T11:48:41.377Z,ACME\svc_backup
|
||||
HR-WS-017,10.10.3.117,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T09:47:17.619Z,2026-02-20T07:55:22.028Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-017,10.10.3.117,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T20:20:44.882Z,2026-02-19T08:19:13.236Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-017,10.10.3.117,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-12T15:24:05.664Z,2026-02-11T00:56:49.490Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-017,10.10.3.117,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-14T07:55:36.757Z,2026-02-19T07:54:44.258Z,ACME\svc_backup
|
||||
HR-WS-017,10.10.3.117,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T16:29:13.283Z,2026-02-14T21:46:54.021Z,ACME\svc_backup
|
||||
FIN-WS-018,10.10.1.118,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T14:39:21.986Z,2026-02-14T21:47:34.203Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-018,10.10.1.118,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-14T22:11:15.193Z,2026-02-11T11:23:59.541Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-018,10.10.1.118,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T13:50:52.916Z,2026-02-14T00:08:04.372Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-018,10.10.1.118,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T06:09:38.641Z,2026-02-19T21:38:06.213Z,ACME\svc_backup
|
||||
FIN-WS-018,10.10.1.118,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T23:36:15.163Z,2026-02-14T22:05:30.950Z,ACME\svc_backup
|
||||
SLS-WS-019,10.10.2.119,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-16T17:26:19.317Z,2026-02-18T14:05:54.158Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-019,10.10.2.119,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T00:53:27.139Z,2026-02-17T01:41:01.159Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-019,10.10.2.119,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-18T11:19:23.835Z,2026-02-16T12:51:56.274Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-019,10.10.2.119,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-11T07:27:43.891Z,2026-02-17T13:45:31.692Z,ACME\svc_backup
|
||||
SLS-WS-019,10.10.2.119,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-18T13:09:32.527Z,2026-02-16T17:33:43.569Z,ACME\svc_backup
|
||||
ENG-WS-020,10.10.3.120,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T06:49:14.065Z,2026-02-15T08:04:18.774Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-020,10.10.3.120,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-14T09:23:50.169Z,2026-02-10T12:51:38.083Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-020,10.10.3.120,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-18T05:01:59.602Z,2026-02-16T13:15:30.465Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-020,10.10.3.120,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-11T05:20:52.039Z,2026-02-10T11:12:43.499Z,ACME\svc_backup
|
||||
ENG-WS-020,10.10.3.120,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T08:32:00.457Z,2026-02-17T15:43:39.779Z,ACME\svc_backup
|
||||
LEG-WS-021,10.10.1.121,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-20T11:10:29.415Z,2026-02-11T13:13:41.644Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-021,10.10.1.121,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-17T23:06:43.206Z,2026-02-12T10:50:24.538Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-021,10.10.1.121,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-11T01:40:28.291Z,2026-02-15T22:59:54.785Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-021,10.10.1.121,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-12T11:56:38.797Z,2026-02-12T05:51:20.276Z,ACME\svc_backup
|
||||
LEG-WS-021,10.10.1.121,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-10T22:08:15.182Z,2026-02-15T16:48:06.361Z,ACME\svc_backup
|
||||
MKT-WS-022,10.10.2.122,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T17:43:14.699Z,2026-02-17T08:30:49.188Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-022,10.10.2.122,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T06:41:06.394Z,2026-02-19T10:32:28.292Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-022,10.10.2.122,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-19T10:24:37.330Z,2026-02-13T02:16:21.356Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-022,10.10.2.122,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-17T21:37:04.120Z,2026-02-13T06:53:53.583Z,ACME\svc_backup
|
||||
MKT-WS-022,10.10.2.122,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-19T20:02:44.067Z,2026-02-20T04:48:00.316Z,ACME\svc_backup
|
||||
EXEC-WS-023,10.10.3.123,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-15T17:40:24.940Z,2026-02-15T11:34:19.658Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-023,10.10.3.123,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T08:32:29.614Z,2026-02-18T15:15:55.737Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-023,10.10.3.123,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-19T22:32:49.533Z,2026-02-11T02:57:20.999Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-023,10.10.3.123,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-10T13:55:56.086Z,2026-02-19T23:21:07.294Z,ACME\svc_backup
|
||||
EXEC-WS-023,10.10.3.123,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-15T00:20:20.226Z,2026-02-18T08:40:38.412Z,ACME\svc_backup
|
||||
IT-WS-024,10.10.1.124,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-11T15:25:00.827Z,2026-02-10T09:26:07.179Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-024,10.10.1.124,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-11T14:09:06.733Z,2026-02-15T06:05:45.983Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-024,10.10.1.124,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-12T07:46:04.449Z,2026-02-14T05:57:31.991Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-024,10.10.1.124,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T15:17:14.219Z,2026-02-17T20:08:02.700Z,ACME\svc_backup
|
||||
IT-WS-024,10.10.1.124,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-19T19:59:37.242Z,2026-02-16T00:09:04.759Z,ACME\svc_backup
|
||||
HR-WS-025,10.10.2.125,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-16T04:55:50.389Z,2026-02-16T14:01:37.319Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-025,10.10.2.125,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T13:04:32.418Z,2026-02-11T03:10:31.981Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-025,10.10.2.125,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-20T03:38:36.810Z,2026-02-19T16:34:13.701Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-025,10.10.2.125,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T15:01:01.192Z,2026-02-19T20:36:40.015Z,ACME\svc_backup
|
||||
HR-WS-025,10.10.2.125,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T03:20:06.246Z,2026-02-15T07:33:50.496Z,ACME\svc_backup
|
||||
FIN-WS-026,10.10.3.126,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T03:14:45.670Z,2026-02-19T17:51:52.973Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-026,10.10.3.126,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T02:37:02.769Z,2026-02-13T17:09:29.023Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-026,10.10.3.126,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T15:57:32.073Z,2026-02-12T07:59:55.524Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-026,10.10.3.126,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-11T16:37:59.553Z,2026-02-20T09:54:18.072Z,ACME\svc_backup
|
||||
FIN-WS-026,10.10.3.126,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T08:39:07.969Z,2026-02-15T09:49:07.790Z,ACME\svc_backup
|
||||
SLS-WS-027,10.10.1.127,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T15:23:10.055Z,2026-02-12T05:53:57.700Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-027,10.10.1.127,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-10T15:00:56.181Z,2026-02-19T15:38:50.857Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-027,10.10.1.127,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T00:42:32.206Z,2026-02-17T14:17:13.806Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-027,10.10.1.127,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-10T18:46:56.374Z,2026-02-12T21:23:32.173Z,ACME\svc_backup
|
||||
SLS-WS-027,10.10.1.127,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T04:37:41.365Z,2026-02-11T01:23:31.251Z,ACME\svc_backup
|
||||
ENG-WS-028,10.10.2.128,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T13:04:58.474Z,2026-02-17T02:25:59.995Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-028,10.10.2.128,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T08:54:27.210Z,2026-02-19T20:33:58.101Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-028,10.10.2.128,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-17T04:37:46.619Z,2026-02-14T19:45:40.877Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-028,10.10.2.128,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-15T16:16:04.921Z,2026-02-10T14:30:04.621Z,ACME\svc_backup
|
||||
ENG-WS-028,10.10.2.128,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T10:32:54.762Z,2026-02-16T13:11:05.730Z,ACME\svc_backup
|
||||
LEG-WS-029,10.10.3.129,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-10T20:33:02.827Z,2026-02-16T16:21:00.588Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-029,10.10.3.129,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-13T23:34:43.565Z,2026-02-12T13:16:47.250Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-029,10.10.3.129,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-19T13:38:02.407Z,2026-02-17T10:33:17.759Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-029,10.10.3.129,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T20:36:41.924Z,2026-02-13T11:46:06.808Z,ACME\svc_backup
|
||||
LEG-WS-029,10.10.3.129,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-17T18:14:48.691Z,2026-02-18T00:04:12.553Z,ACME\svc_backup
|
||||
MKT-WS-030,10.10.1.130,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-16T04:50:25.242Z,2026-02-19T06:32:03.303Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-030,10.10.1.130,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-16T03:09:11.888Z,2026-02-18T19:17:17.312Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-030,10.10.1.130,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T19:54:43.760Z,2026-02-10T08:42:57.840Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-030,10.10.1.130,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T15:58:22.109Z,2026-02-18T03:19:40.560Z,ACME\svc_backup
|
||||
MKT-WS-030,10.10.1.130,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-12T22:26:04.406Z,2026-02-15T16:12:35.508Z,ACME\svc_backup
|
||||
EXEC-WS-031,10.10.2.131,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T18:39:03.864Z,2026-02-13T01:35:35.660Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-031,10.10.2.131,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-20T02:01:54.852Z,2026-02-15T22:53:31.476Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-031,10.10.2.131,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T22:27:22.763Z,2026-02-19T20:20:26.276Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-031,10.10.2.131,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-17T13:30:08.797Z,2026-02-10T21:37:36.455Z,ACME\svc_backup
|
||||
EXEC-WS-031,10.10.2.131,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-10T19:45:08.117Z,2026-02-15T03:26:58.890Z,ACME\svc_backup
|
||||
IT-WS-032,10.10.3.132,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-16T01:11:58.620Z,2026-02-17T16:57:29.329Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-032,10.10.3.132,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-12T01:08:52.343Z,2026-02-19T07:55:59.921Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-032,10.10.3.132,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-15T06:08:56.865Z,2026-02-17T22:09:43.378Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-032,10.10.3.132,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-10T19:49:42.684Z,2026-02-19T19:18:30.053Z,ACME\svc_backup
|
||||
IT-WS-032,10.10.3.132,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T18:14:57.150Z,2026-02-19T15:36:07.924Z,ACME\svc_backup
|
||||
HR-WS-033,10.10.1.133,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T05:31:20.367Z,2026-02-19T13:32:04.721Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-033,10.10.1.133,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-20T07:41:32.969Z,2026-02-12T02:11:07.863Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-033,10.10.1.133,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-19T12:03:45.202Z,2026-02-17T09:21:44.329Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-033,10.10.1.133,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T00:18:58.723Z,2026-02-16T20:17:56.784Z,ACME\svc_backup
|
||||
HR-WS-033,10.10.1.133,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-17T10:14:16.140Z,2026-02-15T01:13:59.702Z,ACME\svc_backup
|
||||
FIN-WS-034,10.10.2.134,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-16T00:27:55.308Z,2026-02-13T18:34:21.414Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-034,10.10.2.134,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-15T05:21:12.636Z,2026-02-14T22:04:37.191Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-034,10.10.2.134,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T18:41:00.839Z,2026-02-14T22:25:57.601Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-034,10.10.2.134,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-15T18:02:25.400Z,2026-02-16T04:53:02.671Z,ACME\svc_backup
|
||||
FIN-WS-034,10.10.2.134,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-13T15:50:06.949Z,2026-02-11T04:07:22.042Z,ACME\svc_backup
|
||||
SLS-WS-035,10.10.3.135,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T23:52:06.120Z,2026-02-14T12:09:55.914Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-035,10.10.3.135,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-14T13:05:06.701Z,2026-02-15T08:42:28.508Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-035,10.10.3.135,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-10T11:15:21.002Z,2026-02-17T13:37:59.232Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-035,10.10.3.135,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T02:44:47.045Z,2026-02-15T04:41:10.501Z,ACME\svc_backup
|
||||
SLS-WS-035,10.10.3.135,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-12T02:27:25.262Z,2026-02-20T08:42:40.871Z,ACME\svc_backup
|
||||
ENG-WS-036,10.10.1.136,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T12:53:00.176Z,2026-02-13T04:33:20.240Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-036,10.10.1.136,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T14:22:34.288Z,2026-02-11T01:13:46.648Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-036,10.10.1.136,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-13T11:55:36.117Z,2026-02-10T14:20:40.804Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-036,10.10.1.136,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-16T12:46:25.045Z,2026-02-19T20:12:56.821Z,ACME\svc_backup
|
||||
ENG-WS-036,10.10.1.136,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T07:34:56.922Z,2026-02-10T12:49:56.644Z,ACME\svc_backup
|
||||
LEG-WS-037,10.10.2.137,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T02:51:51.978Z,2026-02-13T06:11:38.941Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-037,10.10.2.137,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-11T01:44:07.216Z,2026-02-16T23:48:04.905Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-037,10.10.2.137,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-18T17:58:07.400Z,2026-02-15T22:26:08.683Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-037,10.10.2.137,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-17T18:16:48.846Z,2026-02-10T16:29:10.995Z,ACME\svc_backup
|
||||
LEG-WS-037,10.10.2.137,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-11T17:42:26.427Z,2026-02-16T12:20:33.670Z,ACME\svc_backup
|
||||
MKT-WS-038,10.10.3.138,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T17:39:41.163Z,2026-02-11T04:30:07.114Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-038,10.10.3.138,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-18T09:19:05.003Z,2026-02-19T11:32:12.157Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-038,10.10.3.138,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-12T07:20:45.346Z,2026-02-16T19:24:42.020Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-038,10.10.3.138,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T23:31:46.676Z,2026-02-12T23:17:47.391Z,ACME\svc_backup
|
||||
MKT-WS-038,10.10.3.138,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T06:38:36.143Z,2026-02-17T05:16:43.062Z,ACME\svc_backup
|
||||
EXEC-WS-039,10.10.1.139,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T17:12:53.766Z,2026-02-17T07:38:30.430Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-039,10.10.1.139,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T09:43:58.381Z,2026-02-16T15:33:28.709Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-039,10.10.1.139,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T21:23:01.168Z,2026-02-14T17:31:20.994Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-039,10.10.1.139,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-19T12:01:04.643Z,2026-02-10T16:20:29.794Z,ACME\svc_backup
|
||||
EXEC-WS-039,10.10.1.139,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T17:39:02.933Z,2026-02-12T10:14:43.649Z,ACME\svc_backup
|
||||
IT-WS-040,10.10.2.140,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T04:52:30.083Z,2026-02-12T22:44:13.582Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-040,10.10.2.140,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-11T09:59:49.613Z,2026-02-12T04:53:13.144Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-040,10.10.2.140,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-10T20:32:09.001Z,2026-02-15T21:51:24.060Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-040,10.10.2.140,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-14T02:02:25.314Z,2026-02-13T19:45:08.607Z,ACME\svc_backup
|
||||
IT-WS-040,10.10.2.140,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-12T08:37:46.983Z,2026-02-15T06:11:10.277Z,ACME\svc_backup
|
||||
HR-WS-041,10.10.3.141,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-20T16:34:18.148Z,2026-02-20T01:17:01.526Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-041,10.10.3.141,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-14T18:27:49.393Z,2026-02-14T16:48:45.838Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-041,10.10.3.141,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T19:58:20.162Z,2026-02-13T18:30:27.303Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-041,10.10.3.141,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-16T07:35:31.485Z,2026-02-20T07:24:39.759Z,ACME\svc_backup
|
||||
HR-WS-041,10.10.3.141,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-13T07:27:28.310Z,2026-02-13T11:41:10.906Z,ACME\svc_backup
|
||||
FIN-WS-042,10.10.1.142,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-12T08:32:06.811Z,2026-02-12T05:10:22.185Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-042,10.10.1.142,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-17T00:06:44.259Z,2026-02-20T16:31:44.712Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-042,10.10.1.142,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-13T09:20:35.899Z,2026-02-15T03:20:39.739Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-042,10.10.1.142,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-19T22:55:01.161Z,2026-02-15T03:32:13.303Z,ACME\svc_backup
|
||||
FIN-WS-042,10.10.1.142,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-10T17:54:52.225Z,2026-02-18T08:28:27.424Z,ACME\svc_backup
|
||||
SLS-WS-043,10.10.2.143,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T00:24:25.555Z,2026-02-16T02:01:43.780Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-043,10.10.2.143,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T14:00:55.806Z,2026-02-18T13:50:11.756Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-043,10.10.2.143,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-13T06:53:39.515Z,2026-02-20T04:01:26.269Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-043,10.10.2.143,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T00:12:31.793Z,2026-02-19T05:23:06.750Z,ACME\svc_backup
|
||||
SLS-WS-043,10.10.2.143,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T13:02:39.583Z,2026-02-20T11:31:42.030Z,ACME\svc_backup
|
||||
ENG-WS-044,10.10.3.144,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T21:30:20.789Z,2026-02-13T16:26:17.537Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-044,10.10.3.144,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-11T21:35:02.192Z,2026-02-15T01:29:08.612Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-044,10.10.3.144,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-20T00:35:55.846Z,2026-02-13T22:04:14.343Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-044,10.10.3.144,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-12T00:27:50.050Z,2026-02-14T12:14:17.634Z,ACME\svc_backup
|
||||
ENG-WS-044,10.10.3.144,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T22:57:28.555Z,2026-02-18T21:33:18.664Z,ACME\svc_backup
|
||||
LEG-WS-045,10.10.1.145,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T00:38:54.627Z,2026-02-18T17:56:33.277Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-045,10.10.1.145,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-10T14:37:41.313Z,2026-02-16T03:33:54.586Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-045,10.10.1.145,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T11:30:41.181Z,2026-02-18T20:06:24.052Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-045,10.10.1.145,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-17T19:45:45.532Z,2026-02-18T08:32:51.250Z,ACME\svc_backup
|
||||
LEG-WS-045,10.10.1.145,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-10T17:46:03.622Z,2026-02-20T11:51:45.421Z,ACME\svc_backup
|
||||
MKT-WS-046,10.10.2.146,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-16T22:38:49.613Z,2026-02-16T05:01:54.471Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-046,10.10.2.146,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-19T07:31:12.356Z,2026-02-12T19:41:18.613Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-046,10.10.2.146,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-13T18:14:10.252Z,2026-02-18T21:06:38.332Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-046,10.10.2.146,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T16:48:36.176Z,2026-02-12T10:24:42.652Z,ACME\svc_backup
|
||||
MKT-WS-046,10.10.2.146,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-12T16:41:02.198Z,2026-02-11T06:13:28.016Z,ACME\svc_backup
|
||||
EXEC-WS-047,10.10.3.147,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T02:42:54.883Z,2026-02-14T01:24:49.703Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-047,10.10.3.147,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T15:48:14.287Z,2026-02-20T00:45:42.458Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-047,10.10.3.147,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-15T13:25:29.139Z,2026-02-20T01:58:50.715Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-047,10.10.3.147,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-18T02:20:08.558Z,2026-02-20T03:12:22.453Z,ACME\svc_backup
|
||||
EXEC-WS-047,10.10.3.147,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-14T19:24:32.981Z,2026-02-13T16:28:04.308Z,ACME\svc_backup
|
||||
IT-WS-048,10.10.1.148,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T12:59:56.698Z,2026-02-15T00:06:53.085Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-048,10.10.1.148,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-10T16:44:28.196Z,2026-02-10T13:19:02.999Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-048,10.10.1.148,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-19T03:03:43.877Z,2026-02-20T01:11:41.019Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-048,10.10.1.148,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-10T17:48:45.262Z,2026-02-12T11:18:22.491Z,ACME\svc_backup
|
||||
IT-WS-048,10.10.1.148,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-18T02:52:35.295Z,2026-02-14T11:47:48.053Z,ACME\svc_backup
|
||||
HR-WS-049,10.10.2.149,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T04:37:24.806Z,2026-02-10T11:16:11.052Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-049,10.10.2.149,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-18T15:02:20.807Z,2026-02-13T13:06:36.614Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-049,10.10.2.149,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T09:52:11.777Z,2026-02-20T11:28:16.172Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-049,10.10.2.149,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-17T13:41:44.249Z,2026-02-18T10:32:22.995Z,ACME\svc_backup
|
||||
HR-WS-049,10.10.2.149,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-10T08:52:45.463Z,2026-02-19T07:54:06.248Z,ACME\svc_backup
|
||||
FIN-WS-050,10.10.3.150,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-20T01:27:24.175Z,2026-02-12T00:27:05.551Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-050,10.10.3.150,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-18T05:29:47.321Z,2026-02-11T22:31:56.904Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-050,10.10.3.150,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-14T02:23:17.798Z,2026-02-12T10:19:53.923Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-050,10.10.3.150,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T02:07:34.737Z,2026-02-20T14:41:09.357Z,ACME\svc_backup
|
||||
FIN-WS-050,10.10.3.150,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-20T12:03:05.917Z,2026-02-11T02:15:28.141Z,ACME\svc_backup
|
||||
SLS-WS-051,10.10.1.151,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-19T08:04:34.795Z,2026-02-18T02:19:39.654Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-051,10.10.1.151,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-14T05:59:26.864Z,2026-02-13T08:09:26.358Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-051,10.10.1.151,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-12T13:09:01.056Z,2026-02-10T14:13:12.308Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-051,10.10.1.151,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-18T06:57:38.570Z,2026-02-15T06:36:07.347Z,ACME\svc_backup
|
||||
SLS-WS-051,10.10.1.151,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-11T12:53:38.656Z,2026-02-15T03:14:29.799Z,ACME\svc_backup
|
||||
ENG-WS-052,10.10.2.152,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-13T13:07:37.297Z,2026-02-10T12:55:55.898Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-052,10.10.2.152,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T10:00:16.639Z,2026-02-16T03:46:49.138Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-052,10.10.2.152,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-13T02:19:57.856Z,2026-02-11T13:16:14.889Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-052,10.10.2.152,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-10T10:22:57.217Z,2026-02-16T00:54:01.239Z,ACME\svc_backup
|
||||
ENG-WS-052,10.10.2.152,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-11T07:47:26.259Z,2026-02-11T17:00:25.634Z,ACME\svc_backup
|
||||
LEG-WS-053,10.10.3.153,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T15:39:26.774Z,2026-02-19T06:11:54.575Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-053,10.10.3.153,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T04:10:39.301Z,2026-02-15T12:39:57.869Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-053,10.10.3.153,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-11T09:45:50.659Z,2026-02-15T10:30:21.655Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-053,10.10.3.153,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-16T23:36:22.191Z,2026-02-19T01:55:34.033Z,ACME\svc_backup
|
||||
LEG-WS-053,10.10.3.153,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-15T01:07:59.017Z,2026-02-11T10:08:48.989Z,ACME\svc_backup
|
||||
MKT-WS-054,10.10.1.154,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-14T06:56:32.106Z,2026-02-16T04:16:55.893Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-054,10.10.1.154,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-17T13:40:54.674Z,2026-02-15T20:03:49.632Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-054,10.10.1.154,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-19T21:42:16.371Z,2026-02-12T07:23:59.445Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-054,10.10.1.154,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-17T08:29:32.052Z,2026-02-19T11:31:12.681Z,ACME\svc_backup
|
||||
MKT-WS-054,10.10.1.154,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T23:18:47.119Z,2026-02-14T10:16:53.168Z,ACME\svc_backup
|
||||
EXEC-WS-055,10.10.2.155,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T19:03:18.814Z,2026-02-13T08:28:03.971Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-055,10.10.2.155,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-12T13:51:42.917Z,2026-02-14T01:57:38.173Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-055,10.10.2.155,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T03:52:50.143Z,2026-02-20T13:49:39.549Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-055,10.10.2.155,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-11T19:17:28.469Z,2026-02-17T04:25:41.385Z,ACME\svc_backup
|
||||
EXEC-WS-055,10.10.2.155,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T01:38:12.857Z,2026-02-17T09:11:12.721Z,ACME\svc_backup
|
||||
IT-WS-056,10.10.3.156,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-11T16:17:49.377Z,2026-02-12T07:49:48.497Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-056,10.10.3.156,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-16T08:10:22.467Z,2026-02-12T19:09:24.082Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-056,10.10.3.156,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-11T04:59:44.480Z,2026-02-16T07:05:23.990Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-056,10.10.3.156,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T03:53:39.130Z,2026-02-17T22:35:46.400Z,ACME\svc_backup
|
||||
IT-WS-056,10.10.3.156,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T20:11:02.708Z,2026-02-20T08:46:24.560Z,ACME\svc_backup
|
||||
HR-WS-057,10.10.1.157,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-20T10:39:54.324Z,2026-02-14T02:12:25.423Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-057,10.10.1.157,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T00:56:51.206Z,2026-02-11T20:57:13.260Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-057,10.10.1.157,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-10T09:07:51.265Z,2026-02-10T17:23:37.684Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-057,10.10.1.157,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T14:05:45.700Z,2026-02-17T06:33:21.076Z,ACME\svc_backup
|
||||
HR-WS-057,10.10.1.157,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T21:53:43.467Z,2026-02-11T02:12:27.885Z,ACME\svc_backup
|
||||
FIN-WS-058,10.10.2.158,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-10T13:32:35.479Z,2026-02-13T08:00:30.042Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-058,10.10.2.158,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-11T00:40:20.335Z,2026-02-20T07:03:07.506Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-058,10.10.2.158,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-18T17:07:22.065Z,2026-02-10T09:32:20.210Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-058,10.10.2.158,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-14T13:48:44.200Z,2026-02-15T09:17:02.922Z,ACME\svc_backup
|
||||
FIN-WS-058,10.10.2.158,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-14T00:25:04.919Z,2026-02-11T04:12:54.828Z,ACME\svc_backup
|
||||
SLS-WS-059,10.10.3.159,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T19:33:58.088Z,2026-02-16T01:48:28.014Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-059,10.10.3.159,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T10:53:44.931Z,2026-02-13T22:45:45.235Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-059,10.10.3.159,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T16:54:07.752Z,2026-02-20T03:15:07.125Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-059,10.10.3.159,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-18T19:11:20.798Z,2026-02-12T05:42:12.676Z,ACME\svc_backup
|
||||
SLS-WS-059,10.10.3.159,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-20T06:10:59.244Z,2026-02-17T00:50:19.553Z,ACME\svc_backup
|
||||
ENG-WS-060,10.10.1.160,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-13T06:48:10.401Z,2026-02-11T07:39:25.302Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-060,10.10.1.160,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-10T12:25:18.134Z,2026-02-20T08:51:20.161Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-060,10.10.1.160,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-15T12:28:09.879Z,2026-02-16T14:08:21.881Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-060,10.10.1.160,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-16T19:00:48.687Z,2026-02-17T14:29:52.896Z,ACME\svc_backup
|
||||
ENG-WS-060,10.10.1.160,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-18T04:30:53.688Z,2026-02-19T00:51:38.463Z,ACME\svc_backup
|
||||
LEG-WS-061,10.10.2.161,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-20T15:20:33.774Z,2026-02-19T00:18:06.091Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-061,10.10.2.161,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-17T09:53:01.370Z,2026-02-17T01:31:51.621Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-061,10.10.2.161,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T18:43:59.956Z,2026-02-13T19:36:16.733Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-061,10.10.2.161,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T04:44:49.801Z,2026-02-16T22:10:39.417Z,ACME\svc_backup
|
||||
LEG-WS-061,10.10.2.161,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-11T04:39:19.292Z,2026-02-19T11:07:42.571Z,ACME\svc_backup
|
||||
MKT-WS-062,10.10.3.162,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-10T21:35:44.844Z,2026-02-15T04:09:40.629Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-062,10.10.3.162,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-10T17:57:20.101Z,2026-02-15T18:15:52.409Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-062,10.10.3.162,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T11:46:28.196Z,2026-02-14T23:39:55.119Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-062,10.10.3.162,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-15T03:32:12.866Z,2026-02-12T22:09:54.245Z,ACME\svc_backup
|
||||
MKT-WS-062,10.10.3.162,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T02:29:09.217Z,2026-02-12T13:19:08.503Z,ACME\svc_backup
|
||||
EXEC-WS-063,10.10.1.163,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T03:00:42.244Z,2026-02-17T22:26:46.243Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-063,10.10.1.163,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-16T03:37:31.456Z,2026-02-11T22:59:47.615Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-063,10.10.1.163,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-18T06:38:56.200Z,2026-02-12T00:19:22.109Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-063,10.10.1.163,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T20:19:30.773Z,2026-02-14T18:24:24.789Z,ACME\svc_backup
|
||||
EXEC-WS-063,10.10.1.163,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T12:01:11.381Z,2026-02-12T12:43:38.922Z,ACME\svc_backup
|
||||
IT-WS-064,10.10.2.164,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T17:21:12.376Z,2026-02-17T10:02:16.368Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-064,10.10.2.164,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-19T15:50:42.011Z,2026-02-12T17:58:19.048Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-064,10.10.2.164,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-11T01:49:38.430Z,2026-02-19T15:56:40.756Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-064,10.10.2.164,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-13T00:09:09.866Z,2026-02-15T08:41:11.447Z,ACME\svc_backup
|
||||
IT-WS-064,10.10.2.164,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-12T22:14:29.236Z,2026-02-10T22:02:56.979Z,ACME\svc_backup
|
||||
HR-WS-065,10.10.3.165,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T00:54:39.768Z,2026-02-19T16:47:40.687Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-065,10.10.3.165,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T23:21:31.438Z,2026-02-17T13:46:56.603Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-065,10.10.3.165,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-12T13:01:23.505Z,2026-02-15T11:44:53.742Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-065,10.10.3.165,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-18T19:31:04.992Z,2026-02-11T14:13:49.636Z,ACME\svc_backup
|
||||
HR-WS-065,10.10.3.165,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-13T05:03:45.358Z,2026-02-13T03:36:04.167Z,ACME\svc_backup
|
||||
FIN-WS-066,10.10.1.166,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-10T16:57:48.753Z,2026-02-18T06:52:33.511Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-066,10.10.1.166,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-18T20:24:17.462Z,2026-02-16T10:51:51.126Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-066,10.10.1.166,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-18T20:59:12.352Z,2026-02-10T18:15:22.831Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-066,10.10.1.166,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-17T14:13:08.229Z,2026-02-10T21:42:39.121Z,ACME\svc_backup
|
||||
FIN-WS-066,10.10.1.166,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-19T11:17:12.330Z,2026-02-14T13:02:38.561Z,ACME\svc_backup
|
||||
SLS-WS-067,10.10.2.167,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T06:44:14.817Z,2026-02-14T10:04:24.351Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-067,10.10.2.167,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T18:21:16.370Z,2026-02-13T11:47:39.420Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-067,10.10.2.167,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-13T23:53:09.292Z,2026-02-19T04:22:51.709Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-067,10.10.2.167,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-14T09:32:14.262Z,2026-02-13T23:55:00.827Z,ACME\svc_backup
|
||||
SLS-WS-067,10.10.2.167,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T03:33:11.712Z,2026-02-14T20:40:52.915Z,ACME\svc_backup
|
||||
ENG-WS-068,10.10.3.168,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-13T13:55:46.181Z,2026-02-11T19:23:09.804Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-068,10.10.3.168,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T11:33:54.705Z,2026-02-13T21:19:09.081Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-068,10.10.3.168,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T14:45:02.469Z,2026-02-11T20:36:25.115Z,NT AUTHORITY\SYSTEM
|
||||
ENG-WS-068,10.10.3.168,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-11T16:37:23.228Z,2026-02-14T13:57:15.793Z,ACME\svc_backup
|
||||
ENG-WS-068,10.10.3.168,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-13T14:02:01.808Z,2026-02-13T05:49:47.398Z,ACME\svc_backup
|
||||
LEG-WS-069,10.10.1.169,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T06:45:29.345Z,2026-02-12T03:16:18.194Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-069,10.10.1.169,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-14T08:20:40.685Z,2026-02-16T17:16:42.022Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-069,10.10.1.169,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-12T03:40:20.934Z,2026-02-15T15:18:01.003Z,NT AUTHORITY\SYSTEM
|
||||
LEG-WS-069,10.10.1.169,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T11:52:48.196Z,2026-02-16T16:16:13.339Z,ACME\svc_backup
|
||||
LEG-WS-069,10.10.1.169,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-11T02:55:43.227Z,2026-02-13T07:56:11.915Z,ACME\svc_backup
|
||||
MKT-WS-070,10.10.2.170,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T22:47:31.958Z,2026-02-17T15:06:34.912Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-070,10.10.2.170,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T02:08:07.289Z,2026-02-15T12:41:06.092Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-070,10.10.2.170,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-11T14:03:06.009Z,2026-02-12T23:21:43.664Z,NT AUTHORITY\SYSTEM
|
||||
MKT-WS-070,10.10.2.170,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-16T11:23:49.725Z,2026-02-15T17:20:03.103Z,ACME\svc_backup
|
||||
MKT-WS-070,10.10.2.170,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T05:45:14.665Z,2026-02-18T04:36:05.218Z,ACME\svc_backup
|
||||
EXEC-WS-071,10.10.3.171,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-19T21:58:07.627Z,2026-02-13T20:29:02.208Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-071,10.10.3.171,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T11:01:14.918Z,2026-02-17T22:49:52.939Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-071,10.10.3.171,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T21:55:41.932Z,2026-02-19T20:13:09.894Z,NT AUTHORITY\SYSTEM
|
||||
EXEC-WS-071,10.10.3.171,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-17T02:32:40.833Z,2026-02-12T14:25:25.075Z,ACME\svc_backup
|
||||
EXEC-WS-071,10.10.3.171,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-13T16:04:12.865Z,2026-02-15T22:04:39.245Z,ACME\svc_backup
|
||||
IT-WS-072,10.10.1.172,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T01:30:34.938Z,2026-02-18T16:57:52.320Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-072,10.10.1.172,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T11:00:21.112Z,2026-02-18T18:39:21.915Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-072,10.10.1.172,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-19T06:06:35.027Z,2026-02-19T19:07:43.006Z,NT AUTHORITY\SYSTEM
|
||||
IT-WS-072,10.10.1.172,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-13T22:19:29.987Z,2026-02-16T23:11:40.491Z,ACME\svc_backup
|
||||
IT-WS-072,10.10.1.172,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-12T14:46:23.485Z,2026-02-10T15:07:59.419Z,ACME\svc_backup
|
||||
HR-WS-073,10.10.2.173,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-17T18:06:47.797Z,2026-02-18T04:18:01.534Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-073,10.10.2.173,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-20T17:51:58.761Z,2026-02-20T00:44:00.041Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-073,10.10.2.173,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-17T07:20:04.569Z,2026-02-17T09:06:03.744Z,NT AUTHORITY\SYSTEM
|
||||
HR-WS-073,10.10.2.173,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-16T23:23:12.970Z,2026-02-11T15:43:15.739Z,ACME\svc_backup
|
||||
HR-WS-073,10.10.2.173,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T14:03:31.039Z,2026-02-15T04:51:55.591Z,ACME\svc_backup
|
||||
FIN-WS-074,10.10.3.174,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-16T20:00:55.479Z,2026-02-15T02:59:46.886Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-074,10.10.3.174,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T15:12:39.650Z,2026-02-11T03:38:35.784Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-074,10.10.3.174,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-20T04:55:05.330Z,2026-02-14T00:15:48.376Z,NT AUTHORITY\SYSTEM
|
||||
FIN-WS-074,10.10.3.174,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-10T18:50:31.495Z,2026-02-20T00:42:34.849Z,ACME\svc_backup
|
||||
FIN-WS-074,10.10.3.174,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-16T04:36:02.677Z,2026-02-14T12:21:16.058Z,ACME\svc_backup
|
||||
SLS-WS-075,10.10.1.175,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-19T13:23:32.622Z,2026-02-11T05:35:54.700Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-075,10.10.1.175,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-11T00:02:00.028Z,2026-02-11T01:58:38.127Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-075,10.10.1.175,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-19T00:28:03.739Z,2026-02-16T12:10:17.803Z,NT AUTHORITY\SYSTEM
|
||||
SLS-WS-075,10.10.1.175,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-18T21:54:47.101Z,2026-02-17T22:18:28.826Z,ACME\svc_backup
|
||||
SLS-WS-075,10.10.1.175,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-15T14:45:11.098Z,2026-02-17T00:26:03.139Z,ACME\svc_backup
|
||||
DC-01,10.10.1.10,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-18T05:34:55.315Z,2026-02-12T02:10:11.753Z,NT AUTHORITY\SYSTEM
|
||||
DC-01,10.10.1.10,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T06:31:45.770Z,2026-02-14T09:35:12.337Z,NT AUTHORITY\SYSTEM
|
||||
DC-01,10.10.1.10,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T23:45:01.815Z,2026-02-20T12:20:04.033Z,NT AUTHORITY\SYSTEM
|
||||
DC-01,10.10.1.10,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-14T12:56:42.532Z,2026-02-18T08:03:44.533Z,ACME\svc_backup
|
||||
DC-01,10.10.1.10,\ACME\Inventory,C:\Tools\inventory.exe --scan,False,2026-02-14T16:21:55.199Z,2026-02-10T10:37:33.447Z,ACME\svc_backup
|
||||
DC-02,10.10.2.10,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-12T21:41:33.305Z,2026-02-18T06:57:25.169Z,NT AUTHORITY\SYSTEM
|
||||
DC-02,10.10.2.10,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-19T11:04:47.035Z,2026-02-18T12:48:33.666Z,NT AUTHORITY\SYSTEM
|
||||
DC-02,10.10.2.10,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-16T15:09:33.108Z,2026-02-19T13:21:17.011Z,NT AUTHORITY\SYSTEM
|
||||
DC-02,10.10.2.10,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-11T04:53:25.476Z,2026-02-17T23:33:55.502Z,ACME\svc_backup
|
||||
DC-02,10.10.2.10,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-17T06:32:00.675Z,2026-02-12T03:16:42.034Z,ACME\svc_backup
|
||||
FILE-01,10.10.1.11,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-10T16:02:30.187Z,2026-02-18T19:56:12.599Z,NT AUTHORITY\SYSTEM
|
||||
FILE-01,10.10.1.11,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,False,2026-02-18T11:25:10.160Z,2026-02-11T15:30:06.173Z,NT AUTHORITY\SYSTEM
|
||||
FILE-01,10.10.1.11,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-14T03:21:46.273Z,2026-02-18T23:56:47.836Z,NT AUTHORITY\SYSTEM
|
||||
FILE-01,10.10.1.11,\ACME\Backup,C:\Tools\backup.ps1,True,2026-02-20T02:34:57.371Z,2026-02-12T20:01:23.947Z,ACME\svc_backup
|
||||
FILE-01,10.10.1.11,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-18T17:43:39.979Z,2026-02-12T10:42:19.061Z,ACME\svc_backup
|
||||
EXCH-01,10.10.1.12,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-15T15:10:43.406Z,2026-02-16T04:55:20.434Z,NT AUTHORITY\SYSTEM
|
||||
EXCH-01,10.10.1.12,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-13T17:09:26.090Z,2026-02-17T18:12:23.822Z,NT AUTHORITY\SYSTEM
|
||||
EXCH-01,10.10.1.12,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-20T16:29:42.205Z,2026-02-19T10:49:11.222Z,NT AUTHORITY\SYSTEM
|
||||
EXCH-01,10.10.1.12,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-11T18:25:07.936Z,2026-02-20T04:19:26.786Z,ACME\svc_backup
|
||||
EXCH-01,10.10.1.12,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-17T01:30:28.460Z,2026-02-13T05:57:34.175Z,ACME\svc_backup
|
||||
WEB-01,10.10.3.10,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,False,2026-02-12T12:22:58.456Z,2026-02-18T19:15:11.541Z,NT AUTHORITY\SYSTEM
|
||||
WEB-01,10.10.3.10,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T19:15:16.733Z,2026-02-14T08:34:13.004Z,NT AUTHORITY\SYSTEM
|
||||
WEB-01,10.10.3.10,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,False,2026-02-19T09:53:12.372Z,2026-02-17T02:59:14.291Z,NT AUTHORITY\SYSTEM
|
||||
WEB-01,10.10.3.10,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-15T21:38:04.019Z,2026-02-19T04:43:09.645Z,ACME\svc_backup
|
||||
WEB-01,10.10.3.10,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-10T12:59:56.574Z,2026-02-13T15:51:55.193Z,ACME\svc_backup
|
||||
SQL-01,10.10.2.11,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-14T02:24:22.229Z,2026-02-12T07:23:21.046Z,NT AUTHORITY\SYSTEM
|
||||
SQL-01,10.10.2.11,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-12T15:35:11.480Z,2026-02-11T07:46:47.657Z,NT AUTHORITY\SYSTEM
|
||||
SQL-01,10.10.2.11,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-13T09:04:33.741Z,2026-02-14T22:48:34.137Z,NT AUTHORITY\SYSTEM
|
||||
SQL-01,10.10.2.11,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-12T08:16:22.617Z,2026-02-18T21:46:16.878Z,ACME\svc_backup
|
||||
SQL-01,10.10.2.11,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-14T01:28:09.989Z,2026-02-19T03:38:03.534Z,ACME\svc_backup
|
||||
PROXY-01,10.10.1.13,\Microsoft\Windows\UpdateOrchestrator\Schedule Scan,C:\Windows\System32\usoclient.exe StartScan,True,2026-02-20T01:06:00.970Z,2026-02-13T05:34:29.899Z,NT AUTHORITY\SYSTEM
|
||||
PROXY-01,10.10.1.13,\Microsoft\Windows\Defrag\ScheduledDefrag,C:\Windows\System32\defrag.exe -c -h -o,True,2026-02-15T00:23:40.261Z,2026-02-19T05:33:16.704Z,NT AUTHORITY\SYSTEM
|
||||
PROXY-01,10.10.1.13,\Microsoft\Windows\WindowsUpdate\Automatic App Update,C:\Windows\System32\UsoClient.exe,True,2026-02-18T03:48:10.799Z,2026-02-15T08:41:54.461Z,NT AUTHORITY\SYSTEM
|
||||
PROXY-01,10.10.1.13,\ACME\Backup,C:\Tools\backup.ps1,False,2026-02-17T12:57:02.701Z,2026-02-16T21:29:03.726Z,ACME\svc_backup
|
||||
PROXY-01,10.10.1.13,\ACME\Inventory,C:\Tools\inventory.exe --scan,True,2026-02-12T07:58:03.576Z,2026-02-16T01:04:54.799Z,ACME\svc_backup
|
||||
|
1587
backend/tests/test_csvs/07_browser_history.csv
Normal file
2518
backend/tests/test_csvs/08_sysmon_network.csv
Normal file
343
backend/tests/test_csvs/09_autoruns.csv
Normal file
@@ -0,0 +1,343 @@
|
||||
Hostname,IP,EntryName,EntryPath,Category,Enabled,Signer,MD5,Timestamp
|
||||
HR-WS-001,10.10.2.101,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f9476077f731904faca4ce51e8223280,2026-02-18T16:38:01.691Z
|
||||
HR-WS-001,10.10.2.101,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,22b9787216ab4bcc3e837fe83e56de08,2026-02-14T21:40:24.163Z
|
||||
HR-WS-001,10.10.2.101,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,e12c4d94832bcfc10ea719ab8c7a1e99,2026-02-10T10:35:43.190Z
|
||||
HR-WS-001,10.10.2.101,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,c9b8d71ffc7f6ba6815406168fe0e504,2026-02-19T20:45:28.448Z
|
||||
HR-WS-001,10.10.2.101,Spotify,C:\Users\gwhite\AppData\Roaming\Spotify\Spotify.exe /minimized,Logon,True,(Not Signed),4a4348e3d4e5abb139eb31a3b3ab4a37,2026-02-12T07:14:11.363Z
|
||||
FIN-WS-002,10.10.3.102,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,9784945d50c553a038ce9d31f138eeb9,2026-02-16T01:53:59.986Z
|
||||
FIN-WS-002,10.10.3.102,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,51c836ed0b3c7aac59187f2703ba3d95,2026-02-19T12:05:46.600Z
|
||||
FIN-WS-002,10.10.3.102,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,68f003e65e14be3f618ede037c9f3492,2026-02-10T13:54:22.852Z
|
||||
FIN-WS-002,10.10.3.102,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,3ee5bb60071f8c1235df6330912221a3,2026-02-13T13:21:36.604Z
|
||||
SLS-WS-003,10.10.1.103,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f43330733bd73bdc0e2abaca445c0a67,2026-02-12T17:21:14.744Z
|
||||
SLS-WS-003,10.10.1.103,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,4584172477b2fe74ddbd894fcdaa90f3,2026-02-16T01:51:57.062Z
|
||||
SLS-WS-003,10.10.1.103,OneDrive,C:\Users\jsmith\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,6db277775cf2f8a3e0ee4f3f7ea19fde,2026-02-14T11:36:04.076Z
|
||||
SLS-WS-003,10.10.1.103,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,b340a11b84628d2589af707554dc54bf,2026-02-14T14:31:28.902Z
|
||||
ENG-WS-004,10.10.2.104,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,ee50eb7054466553636e1747152ebde2,2026-02-18T21:02:30.826Z
|
||||
ENG-WS-004,10.10.2.104,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,3c3e4d576f1dcb57dda69452d46826e2,2026-02-15T16:11:07.434Z
|
||||
ENG-WS-004,10.10.2.104,OneDrive,C:\Users\hbrown\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,4199e0bd2a0aba2a5b54f21e0f733fc1,2026-02-15T00:36:10.306Z
|
||||
ENG-WS-004,10.10.2.104,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,17409f9956b4cc3de140c4eb9bcb8821,2026-02-10T19:23:50.518Z
|
||||
LEG-WS-005,10.10.3.105,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,a6965636c44cb10326a6726a5cc7ef7c,2026-02-14T06:39:11.997Z
|
||||
LEG-WS-005,10.10.3.105,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,3efd102a59a6bb9659854dbe62960882,2026-02-18T00:05:24.622Z
|
||||
LEG-WS-005,10.10.3.105,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,a892151ba200fd042e6d747af87a58be,2026-02-14T21:09:26.910Z
|
||||
LEG-WS-005,10.10.3.105,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,2382aea96e53966d77f67564e8bb9a0a,2026-02-20T14:37:35.078Z
|
||||
MKT-WS-006,10.10.1.106,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,64b4fba1f06924f821641be3be48af45,2026-02-19T15:12:25.834Z
|
||||
MKT-WS-006,10.10.1.106,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,009b84037a7d13ccd7129ff2dcac4145,2026-02-16T08:31:47.006Z
|
||||
MKT-WS-006,10.10.1.106,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,c6bcda73bfffe297bfa33ac91aceb351,2026-02-15T13:56:06.927Z
|
||||
MKT-WS-006,10.10.1.106,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,a02b5071684ff75768995fb47ce19b3b,2026-02-13T10:48:36.068Z
|
||||
EXEC-WS-007,10.10.2.107,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,3d52771382b18b1ba9f65b5dadf05410,2026-02-14T21:11:18.000Z
|
||||
EXEC-WS-007,10.10.2.107,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,3611b573b586e247bd6e2a507ca21683,2026-02-11T07:26:19.949Z
|
||||
EXEC-WS-007,10.10.2.107,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,d9903e5eded038b3dc3c91f427409805,2026-02-20T10:29:46.612Z
|
||||
EXEC-WS-007,10.10.2.107,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,5a667b764bf6c0b500b45c7b66be2022,2026-02-18T19:47:32.890Z
|
||||
IT-WS-008,10.10.3.108,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,d4856492b7a3b2f2a6df99c73467a7df,2026-02-11T00:15:04.254Z
|
||||
IT-WS-008,10.10.3.108,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,bba6882e5e4c691a6be948be2bdb49ba,2026-02-11T05:06:51.041Z
|
||||
IT-WS-008,10.10.3.108,OneDrive,C:\Users\svc_sql\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,e949a1d4c52013b98db0e82670c20b0f,2026-02-19T09:36:16.769Z
|
||||
IT-WS-008,10.10.3.108,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,19a1662d7a261f7c569de3dae84e3e46,2026-02-18T16:19:52.827Z
|
||||
HR-WS-009,10.10.1.109,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,035b97369bb068c1071c07fe79ed9db8,2026-02-13T18:48:23.256Z
|
||||
HR-WS-009,10.10.1.109,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,9192c986a04555c62ac2d111bae41dbf,2026-02-19T22:18:31.770Z
|
||||
HR-WS-009,10.10.1.109,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,07696f6a549fd643671fd3e7988b6c3a,2026-02-15T03:59:27.784Z
|
||||
HR-WS-009,10.10.1.109,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,61ad70133a4061a53f1bb15189f42acd,2026-02-17T17:53:33.602Z
|
||||
FIN-WS-010,10.10.2.110,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,7837891a3a1aa04f5b173d764d77c58c,2026-02-14T20:46:38.165Z
|
||||
FIN-WS-010,10.10.2.110,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,3d18d4bcf63d3d6616ba37dc0e04533e,2026-02-13T11:38:54.276Z
|
||||
FIN-WS-010,10.10.2.110,OneDrive,C:\Users\dlee\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,890d634cefc9a5575c6c916c2f4a9cd5,2026-02-11T03:52:37.756Z
|
||||
FIN-WS-010,10.10.2.110,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,184c45767f22c999e0c770716995e394,2026-02-17T17:38:37.540Z
|
||||
FIN-WS-010,10.10.2.110,uTorrent,C:\Users\dlee\AppData\Roaming\uTorrent\uTorrent.exe /minimized,Logon,True,(Not Signed),739ff4819dc765d9d1442fa9514ce5a3,2026-02-14T14:23:48.715Z
|
||||
SLS-WS-011,10.10.3.111,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,257302624c1d1b8ab6e41d64f386cd13,2026-02-14T14:18:31.992Z
|
||||
SLS-WS-011,10.10.3.111,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,2e7d979e1bbd4309ebfd30b771312520,2026-02-16T17:36:39.342Z
|
||||
SLS-WS-011,10.10.3.111,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,25ab312343a9b0f8dc132bcf529668f1,2026-02-17T18:55:31.648Z
|
||||
SLS-WS-011,10.10.3.111,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,907e662386549f04314d877f821e0f09,2026-02-16T07:37:15.438Z
|
||||
ENG-WS-012,10.10.1.112,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,a78f9f4d22b8ad4f1649b9451157b62a,2026-02-16T20:53:05.090Z
|
||||
ENG-WS-012,10.10.1.112,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,b7bcf70be51bffb5954b31802880fb7e,2026-02-16T00:23:19.427Z
|
||||
ENG-WS-012,10.10.1.112,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,33b121347f06281ed1f93461d317f81e,2026-02-20T10:49:15.804Z
|
||||
ENG-WS-012,10.10.1.112,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,3b03d2cb036d84e8e13b0cb450b4218b,2026-02-20T10:32:58.342Z
|
||||
LEG-WS-013,10.10.2.113,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f2d290a90081df0d39fb81c567d04776,2026-02-19T10:54:08.806Z
|
||||
LEG-WS-013,10.10.2.113,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,643804513444b466873e04fbb8c1e6b1,2026-02-13T09:02:59.760Z
|
||||
LEG-WS-013,10.10.2.113,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,7d019fd8a888bc50e95abdacdc9826d3,2026-02-16T03:01:45.895Z
|
||||
LEG-WS-013,10.10.2.113,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,08cae97b7813a4e56a551834a3acd593,2026-02-16T03:49:14.877Z
|
||||
LEG-WS-013,10.10.2.113,Discord,C:\Users\cjohnson\AppData\Local\Discord\Update.exe --processStart Discord.exe,Logon,True,(Not Signed),3f649a21c880cb1707c300cdf0cafc80,2026-02-18T00:57:58.524Z
|
||||
MKT-WS-014,10.10.3.114,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,13b2e547b818c31f445ef03f60c0f03f,2026-02-15T00:32:17.270Z
|
||||
MKT-WS-014,10.10.3.114,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,020100b29f3cef7e0d20805b2f6dcf6f,2026-02-16T04:50:18.379Z
|
||||
MKT-WS-014,10.10.3.114,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,104cbbea37b078846fb0ada324e071eb,2026-02-11T05:05:09.008Z
|
||||
MKT-WS-014,10.10.3.114,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,68be1255a2a881356089dca5b17f0235,2026-02-16T15:36:41.385Z
|
||||
MKT-WS-014,10.10.3.114,Steam,C:\Program Files (x86)\Steam\steam.exe -silent,Logon,True,(Not Signed),66cfdcdf2da7e2052a07ff85a398d38a,2026-02-14T22:28:49.877Z
|
||||
EXEC-WS-015,10.10.1.115,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,8a93b2a9ee834d7338c7190c5812b03d,2026-02-15T06:04:00.601Z
|
||||
EXEC-WS-015,10.10.1.115,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,5247e43ce576b5d8be2896d5352b3f2a,2026-02-16T13:10:55.685Z
|
||||
EXEC-WS-015,10.10.1.115,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,81f9a00e9e20223db4d10663bae6bed3,2026-02-17T08:00:37.595Z
|
||||
EXEC-WS-015,10.10.1.115,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,c735750ce48018ac577220efd90ae398,2026-02-11T12:26:22.399Z
|
||||
IT-WS-016,10.10.2.116,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,8deb9721364b822d96adf74e6b8a42d0,2026-02-19T01:58:36.892Z
|
||||
IT-WS-016,10.10.2.116,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,2365ad5ec8604ccf407870b6fc86f489,2026-02-11T15:03:21.222Z
|
||||
IT-WS-016,10.10.2.116,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,82b746aee8dc8483e0d14b477c0bf2e2,2026-02-18T20:43:58.890Z
|
||||
IT-WS-016,10.10.2.116,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,9709479b27d768d502a0f945c29b8f5e,2026-02-11T08:10:28.465Z
|
||||
HR-WS-017,10.10.3.117,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f11b2989e26d037388b3ae21ec50adb9,2026-02-11T00:31:37.655Z
|
||||
HR-WS-017,10.10.3.117,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,7ed41cd93f60a8667d4462fecef78b37,2026-02-12T22:22:11.343Z
|
||||
HR-WS-017,10.10.3.117,OneDrive,C:\Users\agarcia\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,19725dbc721037a8d3ec93c5809f695e,2026-02-19T08:33:19.727Z
|
||||
HR-WS-017,10.10.3.117,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,e883d2362914c58532c69935075e2bbe,2026-02-20T02:29:55.060Z
|
||||
FIN-WS-018,10.10.1.118,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,b524a6ee7c336102b59fa2c02a6c871b,2026-02-14T21:17:01.353Z
|
||||
FIN-WS-018,10.10.1.118,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,64c8ce48ce5252706848a4523fa92126,2026-02-11T10:03:09.619Z
|
||||
FIN-WS-018,10.10.1.118,OneDrive,C:\Users\hbrown\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,05cc51d03c884751bcc4a123b0e7ce7f,2026-02-15T06:12:23.669Z
|
||||
FIN-WS-018,10.10.1.118,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,356ef7d8cfc147e714b76c36c3fac9f5,2026-02-16T03:23:24.079Z
|
||||
SLS-WS-019,10.10.2.119,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,49286eb5be41056b8597acc51222df6a,2026-02-14T09:14:46.487Z
|
||||
SLS-WS-019,10.10.2.119,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,0872ebbf4f5530d12a54f7cdec48a438,2026-02-17T10:58:19.296Z
|
||||
SLS-WS-019,10.10.2.119,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,abd58a27994096e52591b6ffea3783f9,2026-02-14T23:34:37.781Z
|
||||
SLS-WS-019,10.10.2.119,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,4527ee4ad703e9ec0d5e82564b6ef158,2026-02-17T04:34:55.933Z
|
||||
ENG-WS-020,10.10.3.120,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,4dfaca00b0e6732e8b04b2eb21c15db3,2026-02-15T17:58:58.872Z
|
||||
ENG-WS-020,10.10.3.120,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,1f6e64f67e4ecdb0449fda62aa787352,2026-02-19T21:29:34.931Z
|
||||
ENG-WS-020,10.10.3.120,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,6bdc5c8a846721d4a76e895c67561df0,2026-02-18T19:36:32.427Z
|
||||
ENG-WS-020,10.10.3.120,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,1d20b62a8f705ab66986598430c174bc,2026-02-15T16:55:52.609Z
|
||||
LEG-WS-021,10.10.1.121,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,e4d41589137b6a96abc2f55a3791be8d,2026-02-13T15:30:33.773Z
|
||||
LEG-WS-021,10.10.1.121,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,147dc74ec0816e16bf7529e6ae7e5a53,2026-02-14T15:22:57.108Z
|
||||
LEG-WS-021,10.10.1.121,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,bd4587b48ec4020debbbddaf7f89ea7a,2026-02-11T01:41:27.194Z
|
||||
LEG-WS-021,10.10.1.121,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,46917a79cff1dce33a17344c2027cc9b,2026-02-16T17:33:36.432Z
|
||||
MKT-WS-022,10.10.2.122,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,def371c941305a046248395d2a30760d,2026-02-14T08:04:10.599Z
|
||||
MKT-WS-022,10.10.2.122,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,d1ab02535208f9bb6a16ffdf9b6728f0,2026-02-20T08:11:56.232Z
|
||||
MKT-WS-022,10.10.2.122,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,86abf68ccbc0b4c3108763e003389b6c,2026-02-10T12:34:22.389Z
|
||||
MKT-WS-022,10.10.2.122,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,1d5541e9a0ae2ad772e57e5c7e14f5e7,2026-02-12T13:16:19.289Z
|
||||
EXEC-WS-023,10.10.3.123,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,71d6f451ec0ec214188617f3bde4684e,2026-02-13T16:17:00.055Z
|
||||
EXEC-WS-023,10.10.3.123,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,2153c162cc3012bc15e9cb60abd2beb1,2026-02-13T06:32:25.842Z
|
||||
EXEC-WS-023,10.10.3.123,OneDrive,C:\Users\svc_sql\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,4b5aea314b7dcbf04e795bd961078683,2026-02-11T16:12:54.402Z
|
||||
EXEC-WS-023,10.10.3.123,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,e092c0fb7c52dce19b3d8dc3f33e3901,2026-02-14T16:32:21.336Z
|
||||
IT-WS-024,10.10.1.124,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,b6c7015c4dcbfacc3b0e1340c92d172a,2026-02-18T10:19:58.467Z
|
||||
IT-WS-024,10.10.1.124,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,00ac31fa29b3dff35072cafe76371b15,2026-02-19T15:14:39.012Z
|
||||
IT-WS-024,10.10.1.124,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,33b765b545fd45a83d19f0dc116303c8,2026-02-19T01:39:36.343Z
|
||||
IT-WS-024,10.10.1.124,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,2718bc3830483edb17e989e08f9c8fb5,2026-02-17T02:17:18.372Z
|
||||
HR-WS-025,10.10.2.125,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,56e53b7a62821a55258af76761ea1966,2026-02-14T18:40:44.549Z
|
||||
HR-WS-025,10.10.2.125,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,e4232abc36e81b10073bec33bb56d386,2026-02-15T23:11:54.823Z
|
||||
HR-WS-025,10.10.2.125,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,a402172a3ed4174b2fdafc567666b6da,2026-02-11T15:31:11.832Z
|
||||
HR-WS-025,10.10.2.125,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,c10c0af42f088f6b7357acbc09775fa8,2026-02-18T08:17:27.400Z
|
||||
FIN-WS-026,10.10.3.126,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,2fbbc6877d622feef68ffc65f1b497b8,2026-02-10T23:54:22.663Z
|
||||
FIN-WS-026,10.10.3.126,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,53d4cca3eda03f21675f3851158dc65b,2026-02-13T23:13:33.694Z
|
||||
FIN-WS-026,10.10.3.126,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,ac6bf1769af34c99f27c717ef5af0d40,2026-02-17T18:50:56.934Z
|
||||
FIN-WS-026,10.10.3.126,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,7c6a48b9eb64fba9537361bf03f48476,2026-02-19T13:08:39.543Z
|
||||
FIN-WS-026,10.10.3.126,uTorrent,C:\Users\bwilson\AppData\Roaming\uTorrent\uTorrent.exe /minimized,Logon,True,(Not Signed),d62e227614d0b1c5ece4ae2285bca535,2026-02-15T02:40:04.600Z
|
||||
SLS-WS-027,10.10.1.127,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,314a1fa1648cf1937bba36649ac71406,2026-02-16T16:59:40.073Z
|
||||
SLS-WS-027,10.10.1.127,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,71f4848f3579531726169015b8fe25d8,2026-02-11T17:18:52.433Z
|
||||
SLS-WS-027,10.10.1.127,OneDrive,C:\Users\jsmith\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,bcfd766327e1b751c75678cb49a947a5,2026-02-18T02:46:31.650Z
|
||||
SLS-WS-027,10.10.1.127,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,3e86a3eab1378f974b78dca84c9b8d61,2026-02-13T07:47:34.502Z
|
||||
ENG-WS-028,10.10.2.128,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,334d2603842c94b82319a2f73e628825,2026-02-14T20:37:13.140Z
|
||||
ENG-WS-028,10.10.2.128,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,ad5f1aa1f564391827db75bdc0470b91,2026-02-16T01:05:12.568Z
|
||||
ENG-WS-028,10.10.2.128,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,fed764abbda0a135fefbf3d0f4336932,2026-02-15T06:23:54.909Z
|
||||
ENG-WS-028,10.10.2.128,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,836b3596ccf5e97afb6ea50600828378,2026-02-19T19:21:30.616Z
|
||||
LEG-WS-029,10.10.3.129,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,29a32d4dbadce2590aa2ec00ecb9a3e8,2026-02-17T23:00:03.452Z
|
||||
LEG-WS-029,10.10.3.129,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,806e0d560c68c0d16c00bdeb38237f60,2026-02-20T04:19:14.893Z
|
||||
LEG-WS-029,10.10.3.129,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,b47af555bb8f26a514017faba2811633,2026-02-13T03:45:42.890Z
|
||||
LEG-WS-029,10.10.3.129,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,7c127e96cd46d111230479e3ad7ce00b,2026-02-13T07:14:54.166Z
|
||||
MKT-WS-030,10.10.1.130,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,1e72ddbb8a43a6338cd9292998369288,2026-02-18T01:53:46.382Z
|
||||
MKT-WS-030,10.10.1.130,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,b664ea6bdaae1a85803ff8384e1e22d3,2026-02-12T02:13:32.177Z
|
||||
MKT-WS-030,10.10.1.130,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,7a49d8ac591de6c1668c1a925cc87126,2026-02-11T09:16:29.394Z
|
||||
MKT-WS-030,10.10.1.130,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,425471ef3c40d97e8468dd87d12fb5fd,2026-02-16T06:17:51.666Z
|
||||
EXEC-WS-031,10.10.2.131,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,53802a9758578dd99ad67558e04a4d1e,2026-02-14T01:01:05.786Z
|
||||
EXEC-WS-031,10.10.2.131,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,c5809b61bf61d71f627cd3b9a8098bd9,2026-02-18T20:26:29.082Z
|
||||
EXEC-WS-031,10.10.2.131,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,e9f839f0536cddb42725b33b51edfb0d,2026-02-14T17:13:26.254Z
|
||||
EXEC-WS-031,10.10.2.131,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,be25d2578c527a3a93c3d6d5512d4d99,2026-02-14T07:14:54.730Z
|
||||
IT-WS-032,10.10.3.132,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,573fbf0d2247b7cf6d1616ace82eadf6,2026-02-14T19:06:58.657Z
|
||||
IT-WS-032,10.10.3.132,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,a185649a31ae55760a8940c28c44adcc,2026-02-19T19:34:20.023Z
|
||||
IT-WS-032,10.10.3.132,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,3a940ae061d746f6e7d0d053be9d80fb,2026-02-17T01:03:18.593Z
|
||||
IT-WS-032,10.10.3.132,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,e9372174a9ee2f3b313b929f7aa873b8,2026-02-12T12:36:51.081Z
|
||||
HR-WS-033,10.10.1.133,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,049542cdf146c7b80e285b1d9b89598e,2026-02-20T10:01:35.399Z
|
||||
HR-WS-033,10.10.1.133,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,c04ec3ba6b7e7d5a7e3fbbc1413191d4,2026-02-20T08:36:26.722Z
|
||||
HR-WS-033,10.10.1.133,OneDrive,C:\Users\svc_web\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,cfe1493ac15585198b1b5ec006b76b7b,2026-02-19T02:06:29.224Z
|
||||
HR-WS-033,10.10.1.133,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,a09c06d692c5b5720bfeaea892164f48,2026-02-12T01:08:50.432Z
|
||||
FIN-WS-034,10.10.2.134,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,413c77401d948cf3caefd479307d5f34,2026-02-13T03:38:39.557Z
|
||||
FIN-WS-034,10.10.2.134,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,15ddf4d2f0cf6627eb77ef4469a9cdc1,2026-02-12T05:07:11.172Z
|
||||
FIN-WS-034,10.10.2.134,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,d9cb45325fbbd6dd58b519a4b530dd29,2026-02-11T09:31:43.114Z
|
||||
FIN-WS-034,10.10.2.134,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,7f4260b664530f30a715023294e5ede6,2026-02-15T10:28:51.266Z
|
||||
SLS-WS-035,10.10.3.135,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,466275f3c677ad330f3405748012527c,2026-02-18T07:53:54.151Z
|
||||
SLS-WS-035,10.10.3.135,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,c7b8ec3f1db8662ed6ef334da37ed2b4,2026-02-10T12:38:49.511Z
|
||||
SLS-WS-035,10.10.3.135,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,99dd178ae3e335f9cf10abf48551cd83,2026-02-13T11:33:10.057Z
|
||||
SLS-WS-035,10.10.3.135,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,f3c98d1e56fb4ae5c5127b93910393cb,2026-02-14T14:42:54.688Z
|
||||
ENG-WS-036,10.10.1.136,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f335f5dee2b627ddd98ac77aef14493f,2026-02-19T14:14:30.311Z
|
||||
ENG-WS-036,10.10.1.136,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,e4e41f2c1e8c14009a5848d8d9325bd4,2026-02-19T15:52:22.084Z
|
||||
ENG-WS-036,10.10.1.136,OneDrive,C:\Users\svc_web\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,41df5e0402b7cf78ae7e9808827b18f0,2026-02-14T04:58:48.582Z
|
||||
ENG-WS-036,10.10.1.136,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,38ea68957c15ac8726ec5fd21a2ba7c1,2026-02-13T07:43:35.228Z
|
||||
LEG-WS-037,10.10.2.137,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,e7048b4e5ccce823b15a019ac56e231a,2026-02-17T15:19:52.037Z
|
||||
LEG-WS-037,10.10.2.137,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,368a1d6f2c21d1286a578fa8d0d8464d,2026-02-11T19:18:33.346Z
|
||||
LEG-WS-037,10.10.2.137,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,0ea7edfba739e329606025b79e278a5d,2026-02-11T14:38:55.720Z
|
||||
LEG-WS-037,10.10.2.137,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,3efd1a5f4bfe0d8e9c8f9386bd64ca9f,2026-02-14T10:06:13.090Z
|
||||
MKT-WS-038,10.10.3.138,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,cb8dcf4319fd39d7a85bcca0e7fbefb5,2026-02-16T10:16:07.373Z
|
||||
MKT-WS-038,10.10.3.138,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,982b2809524187b9ec74f543773e2764,2026-02-12T17:32:52.719Z
|
||||
MKT-WS-038,10.10.3.138,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,73819da1fee2c45962fe20f89c946299,2026-02-18T11:57:51.031Z
|
||||
MKT-WS-038,10.10.3.138,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,74dd81fe3a2d1ea0a482542378fb1048,2026-02-17T06:24:24.573Z
|
||||
MKT-WS-038,10.10.3.138,uTorrent,C:\Users\svc_backup\AppData\Roaming\uTorrent\uTorrent.exe /minimized,Logon,True,(Not Signed),e8920ccdcd86e6a50d2d7b62bdaa7a9c,2026-02-11T18:39:11.639Z
|
||||
EXEC-WS-039,10.10.1.139,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,ffc64af0d4ca8bd246ec913acaf3fb5e,2026-02-17T14:58:56.781Z
|
||||
EXEC-WS-039,10.10.1.139,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,7c3be0a36ee731522b38337911396c2c,2026-02-14T17:20:30.212Z
|
||||
EXEC-WS-039,10.10.1.139,OneDrive,C:\Users\svc_web\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,01a10918020665bcf838fbdfc4403437,2026-02-15T14:16:32.017Z
|
||||
EXEC-WS-039,10.10.1.139,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,250facb86fab9442299bc0e1abb659b5,2026-02-19T21:17:30.543Z
|
||||
IT-WS-040,10.10.2.140,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,6d209bc687694750e032c8db032fb3ff,2026-02-11T23:34:29.166Z
|
||||
IT-WS-040,10.10.2.140,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,8facdb1c57ff34148b820dab80f9e383,2026-02-10T23:23:33.944Z
|
||||
IT-WS-040,10.10.2.140,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,32f2c2bdb3ece1d25d9ff9b7dd75d281,2026-02-17T01:17:48.914Z
|
||||
IT-WS-040,10.10.2.140,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,c9e4d9ba6e9e9e5f89ebb925b2e748e4,2026-02-15T10:58:20.228Z
|
||||
HR-WS-041,10.10.3.141,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,32bf75f700df41e3d546e69a4b0cacfc,2026-02-17T21:08:02.897Z
|
||||
HR-WS-041,10.10.3.141,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,972852b0380734de6e92f320a28ff149,2026-02-20T16:59:40.093Z
|
||||
HR-WS-041,10.10.3.141,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,2d84ee72f12e76ffe4f1dbae553e4ce5,2026-02-12T08:49:33.876Z
|
||||
HR-WS-041,10.10.3.141,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,076bf4dbffd9edb921ed3678461babac,2026-02-13T23:15:44.185Z
|
||||
FIN-WS-042,10.10.1.142,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,006f3d453ef6527412ad58ca63aa6ff4,2026-02-10T12:17:12.913Z
|
||||
FIN-WS-042,10.10.1.142,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,7df4e3ee708a19f4629c9dbe517a2e88,2026-02-11T06:59:53.755Z
|
||||
FIN-WS-042,10.10.1.142,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,bb0060178f9a918c28213218121d2e26,2026-02-17T13:58:06.447Z
|
||||
FIN-WS-042,10.10.1.142,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,03845bd5e3d576b009a077818a0bf3f3,2026-02-19T08:29:17.982Z
|
||||
FIN-WS-042,10.10.1.142,Discord,C:\Users\fthompson\AppData\Local\Discord\Update.exe --processStart Discord.exe,Logon,True,(Not Signed),d3298e51be33515aeae92b10679c3276,2026-02-12T12:13:15.783Z
|
||||
SLS-WS-043,10.10.2.143,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,8f18a377b282e91cd1e173eee85f1169,2026-02-19T05:53:43.501Z
|
||||
SLS-WS-043,10.10.2.143,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,fe0a63d4d33b3d7b09f1a513cb3cf4bd,2026-02-12T07:09:06.629Z
|
||||
SLS-WS-043,10.10.2.143,OneDrive,C:\Users\dlee\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,7c3cbbb94c63c61b45d23df163cdacfa,2026-02-20T09:07:25.273Z
|
||||
SLS-WS-043,10.10.2.143,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,41a7e4e78ca47e38f2ae59fe95dbaa1f,2026-02-10T15:45:33.709Z
|
||||
ENG-WS-044,10.10.3.144,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,5c064bc2b3b12f1ba13adba76926666e,2026-02-19T14:53:50.194Z
|
||||
ENG-WS-044,10.10.3.144,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,e9c9d85d5fb0e0925e70aca2a5ebc14b,2026-02-15T00:21:57.235Z
|
||||
ENG-WS-044,10.10.3.144,OneDrive,C:\Users\admin\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,2e4da91d64ffbc3661df6c7732f27124,2026-02-13T11:34:37.418Z
|
||||
ENG-WS-044,10.10.3.144,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,a53afa45e3a5f0c5aa081a51a5c2cd9a,2026-02-11T12:55:15.217Z
|
||||
LEG-WS-045,10.10.1.145,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,daae08e18b96f0e17083b00dbbba43f3,2026-02-20T11:00:50.597Z
|
||||
LEG-WS-045,10.10.1.145,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,8000dfa77499f64504acd544c9ed2b44,2026-02-15T18:57:22.470Z
|
||||
LEG-WS-045,10.10.1.145,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,736de40e986b7001b9d51d9dfffc4393,2026-02-20T13:55:53.607Z
|
||||
LEG-WS-045,10.10.1.145,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,f5fe66325792c8ad14858cd514ef0c49,2026-02-18T16:15:00.214Z
|
||||
MKT-WS-046,10.10.2.146,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,98dd029b72034c677b77cbb4baba2f19,2026-02-16T23:41:33.531Z
|
||||
MKT-WS-046,10.10.2.146,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,51059e8db4e2ecaea0d9afec8ac888d6,2026-02-20T00:06:13.727Z
|
||||
MKT-WS-046,10.10.2.146,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,2464332ad026b59fcf540a2df3c7563b,2026-02-14T21:01:25.203Z
|
||||
MKT-WS-046,10.10.2.146,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,0e7912a0ac8e8400841afe8e1d26c752,2026-02-12T07:16:24.517Z
|
||||
EXEC-WS-047,10.10.3.147,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,c27973488fa8d6ccc35b45bbcbfcf1ec,2026-02-17T02:00:33.402Z
|
||||
EXEC-WS-047,10.10.3.147,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,2af2cfa5d01a2ce0d8dd99daf73a3208,2026-02-12T03:02:45.368Z
|
||||
EXEC-WS-047,10.10.3.147,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,9e74515aa29a7b3e5d8bd16092755e08,2026-02-18T13:30:31.571Z
|
||||
EXEC-WS-047,10.10.3.147,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,69b94d10458fe7f56e5a1c48a610e441,2026-02-10T11:18:38.294Z
|
||||
IT-WS-048,10.10.1.148,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,c16b145ffc51ff16c7100a9ce1ed7145,2026-02-18T17:24:24.371Z
|
||||
IT-WS-048,10.10.1.148,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,8115abbff0e6ed6a8f0502c5d66a164b,2026-02-15T14:36:56.923Z
|
||||
IT-WS-048,10.10.1.148,OneDrive,C:\Users\svc_web\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,80ea42f5e586d1396b59ca4a4379980c,2026-02-17T19:47:20.951Z
|
||||
IT-WS-048,10.10.1.148,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,978aa10480ffe0c32e7928077dddee59,2026-02-12T15:47:32.226Z
|
||||
HR-WS-049,10.10.2.149,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f18d6a9ca22157827344faad6edc3f43,2026-02-16T21:28:27.865Z
|
||||
HR-WS-049,10.10.2.149,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,ccc68355f8a4824c1f55304c7b9c1821,2026-02-18T17:48:34.613Z
|
||||
HR-WS-049,10.10.2.149,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,7aec94942f186eb470473d02acca2c41,2026-02-10T10:53:46.013Z
|
||||
HR-WS-049,10.10.2.149,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,7f0644cea5e9d5e9074c241b5d305777,2026-02-15T16:24:04.628Z
|
||||
FIN-WS-050,10.10.3.150,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,e770d168aad26f9aa80dfccf1456b33e,2026-02-11T03:25:16.210Z
|
||||
FIN-WS-050,10.10.3.150,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,572f3c3ecbcd7e348201d540d845035a,2026-02-12T14:56:55.158Z
|
||||
FIN-WS-050,10.10.3.150,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,9c5adbcd1738c3a86edebbf0973dfb5f,2026-02-17T11:15:30.182Z
|
||||
FIN-WS-050,10.10.3.150,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,0b272489c6c0815115c8bd238398a884,2026-02-16T01:22:35.399Z
|
||||
FIN-WS-050,10.10.3.150,Discord,C:\Users\gwhite\AppData\Local\Discord\Update.exe --processStart Discord.exe,Logon,True,(Not Signed),aa339164d7d31497cc642c6902451906,2026-02-16T07:27:26.567Z
|
||||
SLS-WS-051,10.10.1.151,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,e32cd3849f82c4860435fafa7dc815c2,2026-02-11T23:42:50.015Z
|
||||
SLS-WS-051,10.10.1.151,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,c87049028ccc42c31ceb5ce9453eb07d,2026-02-17T08:44:11.345Z
|
||||
SLS-WS-051,10.10.1.151,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,0d0a3a879f3a49cfb08f26177867d9e9,2026-02-15T11:26:48.618Z
|
||||
SLS-WS-051,10.10.1.151,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,05bb54baf4ce2358d13fcc4848aa33b6,2026-02-16T19:58:21.694Z
|
||||
ENG-WS-052,10.10.2.152,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,2f9ef5c9bcdffb9ef50fadb3fb7b3071,2026-02-13T05:29:22.387Z
|
||||
ENG-WS-052,10.10.2.152,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,8af8f892378d437d4d1c8a4a51be47d3,2026-02-10T12:34:37.519Z
|
||||
ENG-WS-052,10.10.2.152,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,23f56f0691a732c5d268a503d0661c73,2026-02-14T17:20:11.430Z
|
||||
ENG-WS-052,10.10.2.152,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,488635a51a8db16a96ab5b2486b3e99b,2026-02-16T03:58:31.008Z
|
||||
LEG-WS-053,10.10.3.153,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,277f4ae42f54b415f5674f0399d48851,2026-02-19T01:02:29.576Z
|
||||
LEG-WS-053,10.10.3.153,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,7b6df8a41be6c0d22c967cc940696e09,2026-02-14T02:21:58.061Z
|
||||
LEG-WS-053,10.10.3.153,OneDrive,C:\Users\dlee\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,094b1cefcf394930bc9193a446c9c6d9,2026-02-12T23:39:58.180Z
|
||||
LEG-WS-053,10.10.3.153,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,6ee42997d46f4dc9d4266b1c8f182927,2026-02-20T06:43:48.545Z
|
||||
MKT-WS-054,10.10.1.154,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,987bbb22c8cdb20ab790c330bc961b58,2026-02-13T18:56:44.135Z
|
||||
MKT-WS-054,10.10.1.154,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,121570d56e4b522c211a5b0369ba8b38,2026-02-20T04:26:12.326Z
|
||||
MKT-WS-054,10.10.1.154,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,91537e600e667e639b1ec1ad951a5fd3,2026-02-18T09:14:35.557Z
|
||||
MKT-WS-054,10.10.1.154,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,92c9384f5a03393796327699fec5f20a,2026-02-17T00:58:48.519Z
|
||||
EXEC-WS-055,10.10.2.155,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,df44882927d4f15391536dfc26a8c0bd,2026-02-10T09:59:08.913Z
|
||||
EXEC-WS-055,10.10.2.155,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,e1081eb13daef772716fc648bc238c07,2026-02-19T21:26:09.314Z
|
||||
EXEC-WS-055,10.10.2.155,OneDrive,C:\Users\fthompson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,1467e33b648b9abf5adf4a2ada8e387b,2026-02-15T16:27:13.551Z
|
||||
EXEC-WS-055,10.10.2.155,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,c18d8b93bd24e6cf73d50e22d9d78bb7,2026-02-10T20:40:35.229Z
|
||||
IT-WS-056,10.10.3.156,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,ca730f1d3a62ab91e55fd3dd4a600fb1,2026-02-18T03:17:40.085Z
|
||||
IT-WS-056,10.10.3.156,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,9eca3a6acefa22b5a0ee9f8d1d12b2b1,2026-02-18T04:32:03.354Z
|
||||
IT-WS-056,10.10.3.156,OneDrive,C:\Users\dlee\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,a3cbfa1a00b8118d5fe27a5794d8b5ee,2026-02-19T05:02:49.038Z
|
||||
IT-WS-056,10.10.3.156,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,6ea8ee29b08a081ebba72ec11b598d87,2026-02-10T17:29:22.882Z
|
||||
HR-WS-057,10.10.1.157,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,58ecaffac946cc370d322cba869edfe4,2026-02-13T11:35:42.464Z
|
||||
HR-WS-057,10.10.1.157,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,f56143391ddff8edfa9ccdfe51f476b2,2026-02-10T21:38:04.419Z
|
||||
HR-WS-057,10.10.1.157,OneDrive,C:\Users\svc_sql\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,d41b4fae62fe7a33a43582fff6d926c9,2026-02-14T18:22:05.932Z
|
||||
HR-WS-057,10.10.1.157,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,383a872eeb9de9cdc065257155cb4200,2026-02-14T06:09:45.704Z
|
||||
FIN-WS-058,10.10.2.158,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,a4a6a20b403b4fc19102305a6e4fd94e,2026-02-10T08:42:35.702Z
|
||||
FIN-WS-058,10.10.2.158,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,48143f42f576ae575ebfece272765c11,2026-02-19T04:41:14.049Z
|
||||
FIN-WS-058,10.10.2.158,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,d9bfc51ac72f1e6b21fa53b2bd2a02b0,2026-02-11T04:32:01.045Z
|
||||
FIN-WS-058,10.10.2.158,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,8d5a44fb24c3c11874008e77b825743f,2026-02-14T20:19:16.665Z
|
||||
FIN-WS-058,10.10.2.158,Discord,C:\Users\cjohnson\AppData\Local\Discord\Update.exe --processStart Discord.exe,Logon,True,(Not Signed),cf58eaed9e013bc38819bd998b969616,2026-02-10T12:16:52.144Z
|
||||
SLS-WS-059,10.10.3.159,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,e3c74c9e8c3492cae2b9a71a1e427585,2026-02-19T22:17:09.337Z
|
||||
SLS-WS-059,10.10.3.159,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,fe9249491447329754fa3ae5cc2050ac,2026-02-18T05:39:20.950Z
|
||||
SLS-WS-059,10.10.3.159,OneDrive,C:\Users\admin\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,b50cf05fc070879415afdff534325531,2026-02-13T15:09:06.895Z
|
||||
SLS-WS-059,10.10.3.159,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,a117e1a617bab8831ce6dd4e4fba6d67,2026-02-15T14:14:24.306Z
|
||||
SLS-WS-059,10.10.3.159,Discord,C:\Users\admin\AppData\Local\Discord\Update.exe --processStart Discord.exe,Logon,True,(Not Signed),f91869f509fdbce7a300cd698e9d3de8,2026-02-17T16:51:11.312Z
|
||||
ENG-WS-060,10.10.1.160,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,5e3fb72ba3dc555cc44077769be8ce5d,2026-02-10T15:01:52.042Z
|
||||
ENG-WS-060,10.10.1.160,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,25abfcfe6042e882f58b36d0ae662e30,2026-02-13T17:28:08.992Z
|
||||
ENG-WS-060,10.10.1.160,OneDrive,C:\Users\agarcia\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,ed45996c6ab270febf0e20503e0901f2,2026-02-14T14:08:35.251Z
|
||||
ENG-WS-060,10.10.1.160,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,25360d7653284e5ce57d014c5f5fa344,2026-02-19T19:59:32.408Z
|
||||
LEG-WS-061,10.10.2.161,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,5e9fabf85430c116dbe504e81ac3f79b,2026-02-17T10:28:34.683Z
|
||||
LEG-WS-061,10.10.2.161,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,a8be97cc47061d65abe719d11b4f0c85,2026-02-17T06:25:12.347Z
|
||||
LEG-WS-061,10.10.2.161,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,74a40f54e3fe117be1c980697c2c37a3,2026-02-19T00:01:37.208Z
|
||||
LEG-WS-061,10.10.2.161,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,1dfe7db59f003be66178f993295bb56a,2026-02-12T03:43:03.285Z
|
||||
LEG-WS-061,10.10.2.161,Discord,C:\Users\bwilson\AppData\Local\Discord\Update.exe --processStart Discord.exe,Logon,True,(Not Signed),d1b0abd2bb61d4cd50f917b77257834f,2026-02-16T20:47:33.918Z
|
||||
MKT-WS-062,10.10.3.162,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,3138daa62a744df11adbc154d5643e7c,2026-02-19T22:15:20.157Z
|
||||
MKT-WS-062,10.10.3.162,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,2fa02cc9d22691833f64f4149a28e9a5,2026-02-14T09:26:09.982Z
|
||||
MKT-WS-062,10.10.3.162,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,6eb0f457f1f4c1308b994c881de7eaf8,2026-02-16T17:09:20.065Z
|
||||
MKT-WS-062,10.10.3.162,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,67609be7d4e999c1d5dfc3aa489274ec,2026-02-20T15:40:16.621Z
|
||||
EXEC-WS-063,10.10.1.163,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,ba697671060136ab33909c410f80e42c,2026-02-18T19:02:36.065Z
|
||||
EXEC-WS-063,10.10.1.163,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,a13556cef9f10cc12550f7c2620a5202,2026-02-11T05:27:32.442Z
|
||||
EXEC-WS-063,10.10.1.163,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,f677999b0375185656d9ef8ce716a578,2026-02-13T01:51:56.607Z
|
||||
EXEC-WS-063,10.10.1.163,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,ad61a6340315e733677af5c5780f5316,2026-02-17T14:44:49.088Z
|
||||
IT-WS-064,10.10.2.164,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,56c40e3fbf9bf2370cd8a93138fe9128,2026-02-17T06:05:14.098Z
|
||||
IT-WS-064,10.10.2.164,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,ee030cf6ceb536c1fbca61f2fa340b4f,2026-02-11T10:37:43.803Z
|
||||
IT-WS-064,10.10.2.164,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,38ed7f369c3d7f3d5c89d4cae1e8f56c,2026-02-15T07:49:13.007Z
|
||||
IT-WS-064,10.10.2.164,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,736cb1859c8110049369075c36cd11f7,2026-02-13T08:14:54.300Z
|
||||
HR-WS-065,10.10.3.165,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,795538f956cdef5781af357031304dbd,2026-02-11T14:15:29.330Z
|
||||
HR-WS-065,10.10.3.165,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,dc07973ee58d79940996e8e4458dd89a,2026-02-12T08:02:58.202Z
|
||||
HR-WS-065,10.10.3.165,OneDrive,C:\Users\svc_backup\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,2bbf153fa9968949c640d202021d8e3f,2026-02-13T20:51:22.220Z
|
||||
HR-WS-065,10.10.3.165,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,66df52a3fe1471696f506a4a2690b357,2026-02-10T13:21:44.634Z
|
||||
FIN-WS-066,10.10.1.166,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,69a94cf819533dd10e7ec76b0b77032a,2026-02-16T02:04:37.083Z
|
||||
FIN-WS-066,10.10.1.166,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,7d2e14e4796e51be1206a7abfd70c202,2026-02-17T07:51:27.575Z
|
||||
FIN-WS-066,10.10.1.166,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,e9dedc92ce86477003bbad2c26f2dc1d,2026-02-10T10:38:27.985Z
|
||||
FIN-WS-066,10.10.1.166,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,e68e585e83f868742c5f51b5feb72b72,2026-02-11T05:26:00.039Z
|
||||
FIN-WS-066,10.10.1.166,Steam,C:\Program Files (x86)\Steam\steam.exe -silent,Logon,True,(Not Signed),dacabfae9c7421ad3011a0eadf3e0443,2026-02-13T09:11:29.353Z
|
||||
SLS-WS-067,10.10.2.167,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,5f51d7d0e8888e07990da248c12efc6a,2026-02-17T15:19:00.925Z
|
||||
SLS-WS-067,10.10.2.167,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,b2b52fd93f2a11da12ccbae1e3f01fdf,2026-02-18T17:22:50.656Z
|
||||
SLS-WS-067,10.10.2.167,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,eaeb983924bde23dc828cffacc2fa365,2026-02-14T17:13:54.310Z
|
||||
SLS-WS-067,10.10.2.167,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,cb6d47768131e6cb2e6b789c01badb1d,2026-02-13T04:54:44.965Z
|
||||
ENG-WS-068,10.10.3.168,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,1d78072736aaf3b08dbfda72b303bcd1,2026-02-12T10:17:52.651Z
|
||||
ENG-WS-068,10.10.3.168,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,e475c94081df0af0bb76745351b2d40c,2026-02-15T19:49:17.801Z
|
||||
ENG-WS-068,10.10.3.168,OneDrive,C:\Users\jsmith\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,e5992ba9a161353da4d300ca1ce2a1f3,2026-02-17T08:26:41.519Z
|
||||
ENG-WS-068,10.10.3.168,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,2d4cab9657ebeb3c2177531acbf581e3,2026-02-10T18:16:25.140Z
|
||||
LEG-WS-069,10.10.1.169,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,b1483fd46b2b62ebadd883c80938d34d,2026-02-13T07:53:52.108Z
|
||||
LEG-WS-069,10.10.1.169,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,8ce9fcda50019e404015bde12c5a98ca,2026-02-13T04:05:14.999Z
|
||||
LEG-WS-069,10.10.1.169,OneDrive,C:\Users\admin\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,4d24770fc83650ed89854b3cb253a557,2026-02-16T04:01:07.901Z
|
||||
LEG-WS-069,10.10.1.169,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,32db33f8e2614cb7d3c30cfa77573ab7,2026-02-12T10:06:06.030Z
|
||||
MKT-WS-070,10.10.2.170,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,3690e9435e87a3b2da8153837763d6fa,2026-02-15T12:51:45.643Z
|
||||
MKT-WS-070,10.10.2.170,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,611f1fb8cca866914714c3f77737e0bf,2026-02-10T10:52:50.728Z
|
||||
MKT-WS-070,10.10.2.170,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,282318771d4e5d5320c9ae9114ba79ef,2026-02-20T15:37:35.086Z
|
||||
MKT-WS-070,10.10.2.170,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,411fe474a0dc3c2098dba83da371b82b,2026-02-19T23:07:28.521Z
|
||||
MKT-WS-070,10.10.2.170,uTorrent,C:\Users\cjohnson\AppData\Roaming\uTorrent\uTorrent.exe /minimized,Logon,True,(Not Signed),0cae3b0d47ed5cbde4e68f091e66d1f9,2026-02-12T17:48:03.256Z
|
||||
EXEC-WS-071,10.10.3.171,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,1ca5480bb6110b7c97a13cd76d5358cd,2026-02-12T15:28:43.280Z
|
||||
EXEC-WS-071,10.10.3.171,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,92c685da727c44a3ce3fa492acfd8a0c,2026-02-18T16:34:25.734Z
|
||||
EXEC-WS-071,10.10.3.171,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,32386481300f754aa2a29feb02931755,2026-02-14T11:21:06.851Z
|
||||
EXEC-WS-071,10.10.3.171,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,9c20b89abe39bf0cc2ac97dab5a996c9,2026-02-14T10:44:52.198Z
|
||||
EXEC-WS-071,10.10.3.171,Steam,C:\Program Files (x86)\Steam\steam.exe -silent,Logon,True,(Not Signed),d06125e2c8424b10e2923b09db7c8d92,2026-02-18T21:27:54.682Z
|
||||
IT-WS-072,10.10.1.172,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,cb7b573fe9140d654dfd153059cec23e,2026-02-20T03:09:22.548Z
|
||||
IT-WS-072,10.10.1.172,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,be04f87ba1c2b005f1c4e6e31f2a65bb,2026-02-20T09:31:17.420Z
|
||||
IT-WS-072,10.10.1.172,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,f99ce66416492b49700e0de7ab8f49e2,2026-02-12T01:20:28.360Z
|
||||
IT-WS-072,10.10.1.172,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,68b05ec7686f503f5acd476d1470e9a8,2026-02-16T11:06:58.383Z
|
||||
HR-WS-073,10.10.2.173,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,0eea9d8033027713fe579dce9fcfc907,2026-02-10T21:27:35.847Z
|
||||
HR-WS-073,10.10.2.173,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,139da91d72859c78c9dfc840ff53fad2,2026-02-11T19:44:37.217Z
|
||||
HR-WS-073,10.10.2.173,OneDrive,C:\Users\gwhite\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,9cd4908a44e2690b00006b62bd71145e,2026-02-17T06:27:11.201Z
|
||||
HR-WS-073,10.10.2.173,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,19b467e51a124b4945dc148fe8410601,2026-02-15T07:02:48.879Z
|
||||
FIN-WS-074,10.10.3.174,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,d0fe1e4b1f64132f0916608a47cd113b,2026-02-14T07:06:20.159Z
|
||||
FIN-WS-074,10.10.3.174,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,75dd67220a772027d6ccb4a16908557f,2026-02-16T04:58:14.026Z
|
||||
FIN-WS-074,10.10.3.174,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,f31dc9eccc4f5f1019a61ab51df1bcf3,2026-02-18T21:54:55.899Z
|
||||
FIN-WS-074,10.10.3.174,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,0834614d2ccd6226f3dfd91f6870e205,2026-02-19T22:41:19.265Z
|
||||
SLS-WS-075,10.10.1.175,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,58a3f3e8a3efd3e794f9d70381478749,2026-02-10T22:13:17.055Z
|
||||
SLS-WS-075,10.10.1.175,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,f16ed7d63800f6e6118dcc03465d70f1,2026-02-14T09:12:21.535Z
|
||||
SLS-WS-075,10.10.1.175,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,5a90e6680bf14a9826bdfbd01e652e11,2026-02-11T11:25:24.778Z
|
||||
SLS-WS-075,10.10.1.175,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,e12e3431344420d6a9c0c0c52599fe07,2026-02-15T08:44:30.948Z
|
||||
DC-01,10.10.1.10,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,0917bf6a53fc4de5ded1e219738e4383,2026-02-19T21:49:06.115Z
|
||||
DC-01,10.10.1.10,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,a05c61ef8d7d6aee1ba89f5a6ac96f68,2026-02-11T01:52:42.165Z
|
||||
DC-01,10.10.1.10,OneDrive,C:\Users\bwilson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,e0697fde4e2e8b3d3d08aedfd7154653,2026-02-11T01:06:10.097Z
|
||||
DC-01,10.10.1.10,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,a8ecdd8717e08108a0bc716c66d69d82,2026-02-19T06:25:33.306Z
|
||||
DC-02,10.10.2.10,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,8e46f4877112f92437b541ba7a887e07,2026-02-17T04:48:51.329Z
|
||||
DC-02,10.10.2.10,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,979189e8b0c249d75a1ccc4967cb0988,2026-02-19T06:21:28.740Z
|
||||
DC-02,10.10.2.10,OneDrive,C:\Users\idavis\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,63e689679efbd68b9abb28137a85f930,2026-02-16T06:02:32.762Z
|
||||
DC-02,10.10.2.10,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,e51f19b33cb1292474ddb3722a0b1eff,2026-02-11T12:29:21.456Z
|
||||
FILE-01,10.10.1.11,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,3cfbacc574bcdbe88697a22ac0ad8638,2026-02-15T00:42:20.076Z
|
||||
FILE-01,10.10.1.11,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,a45b31e4fd69a54f2c4d062b4c4eaa99,2026-02-13T13:22:11.398Z
|
||||
FILE-01,10.10.1.11,OneDrive,C:\Users\admin\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,83fef4984c8e4973fd684d6e6d0ddfdc,2026-02-15T08:23:32.263Z
|
||||
FILE-01,10.10.1.11,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,64d9a6110e88e9e3413066f1924cba07,2026-02-14T21:27:06.097Z
|
||||
EXCH-01,10.10.1.12,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,63cec7c6e35e0026c1720450ceec7934,2026-02-12T18:06:31.325Z
|
||||
EXCH-01,10.10.1.12,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,311bd1adc40a2e9072d76f9d8d33cd7d,2026-02-13T07:12:35.966Z
|
||||
EXCH-01,10.10.1.12,OneDrive,C:\Users\cjohnson\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,2a08fbfdc68efc14c4c2d3897475acd9,2026-02-18T22:57:33.185Z
|
||||
EXCH-01,10.10.1.12,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,d9e8ef9701fcd7dee505470e302b8472,2026-02-19T19:15:19.950Z
|
||||
WEB-01,10.10.3.10,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,f299839d55a02ccba36daeab018698a8,2026-02-12T00:42:42.409Z
|
||||
WEB-01,10.10.3.10,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,87554861f53ebdb14204b0319d543532,2026-02-20T08:53:42.632Z
|
||||
WEB-01,10.10.3.10,OneDrive,C:\Users\jsmith\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,052ad7b9cb17552f54425fbb307afb2f,2026-02-16T15:54:45.442Z
|
||||
WEB-01,10.10.3.10,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,4922ca93a61f0e6e6f7ffaf832301ab9,2026-02-12T18:43:55.289Z
|
||||
SQL-01,10.10.2.11,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,33289dc101881ca32b2549eea1f33fbb,2026-02-20T15:57:36.229Z
|
||||
SQL-01,10.10.2.11,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,bd96f2f5fce3cbcef65388abff27bcef,2026-02-12T16:12:09.448Z
|
||||
SQL-01,10.10.2.11,OneDrive,C:\Users\emartinez\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,b12baa76eaeb4ee8eb515c354662a053,2026-02-13T09:38:17.385Z
|
||||
SQL-01,10.10.2.11,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,f1413bff4520ec6759b1b3679af3fc53,2026-02-12T16:05:43.210Z
|
||||
PROXY-01,10.10.1.13,MicrosoftEdgeAutoLaunch,C:\Program Files\Microsoft\Edge\msedge.exe --no-startup-window,Logon,True,Microsoft Corporation,d10cdc47203a981f69ccd23b24077711,2026-02-15T15:14:35.089Z
|
||||
PROXY-01,10.10.1.13,SecurityHealth,C:\Windows\System32\SecurityHealthSystray.exe,Logon,True,Microsoft Corporation,f3f137e8da7eb37b0ae66fb45a71e4d3,2026-02-10T15:57:58.411Z
|
||||
PROXY-01,10.10.1.13,OneDrive,C:\Users\svc_web\AppData\Local\Microsoft\OneDrive\OneDrive.exe /background,Logon,True,Microsoft Corporation,99800716da8de3befa044253f65bf65d,2026-02-17T21:59:13.166Z
|
||||
PROXY-01,10.10.1.13,WindowsDefender,C:\ProgramData\Microsoft\Windows Defender\MsMpEng.exe,Logon,True,Microsoft Corporation,55e54fe6641547957cfae514eeb50c12,2026-02-16T12:46:38.263Z
|
||||
|
1605
backend/tests/test_csvs/10_logon_events.csv
Normal file
2606
backend/tests/test_csvs/11_proxy_logs.csv
Normal file
422
backend/tests/test_csvs/12_file_listing.csv
Normal file
@@ -0,0 +1,422 @@
|
||||
Hostname,SourceIP,FullPath,FileName,Size,MD5,SHA256,MTime,CTime,Username,OS
|
||||
HR-WS-001,10.10.2.101,C:\Users\jsmith\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,d9398a9cb083e44ca369d0e0a23f3dc821e10a187d83b67ddd039c8ba3a9116d,2026-02-16T13:19:03.752Z,2026-02-20T05:28:53.611Z,ACME\jsmith,Windows 11 Enterprise
|
||||
HR-WS-001,10.10.2.101,C:\Users\jsmith\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,93ff71317e2633263f047c0b9c8e432e31a4d326f622a0e0c0991c0865d85678,2026-02-17T16:01:54.830Z,2026-02-19T13:46:11.397Z,ACME\jsmith,Windows 11 Enterprise
|
||||
HR-WS-001,10.10.2.101,C:\Users\jsmith\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,1529047f20932e75ce4ebf63126bf597c5e8091f4e9ba80da6548e6e67711ad3,2026-02-19T11:04:21.129Z,2026-02-20T02:43:48.243Z,ACME\jsmith,Windows 11 Enterprise
|
||||
HR-WS-001,10.10.2.101,C:\Users\jsmith\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,c0fa69ab4b4182e331fc46c2eaf8905d0224f2aa7547912483e56206e38f8a8f,2026-02-15T19:03:15.373Z,2026-02-19T17:40:13.726Z,ACME\jsmith,Windows 11 Enterprise
|
||||
HR-WS-001,10.10.2.101,C:\Users\jsmith\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,15eccb4149dc36b19e68074454add5769d366db6d05b8076fd05676e636b91ff,2026-02-17T23:01:25.329Z,2026-02-20T12:53:18.490Z,ACME\jsmith,Windows 11 Enterprise
|
||||
FIN-WS-002,10.10.3.102,C:\Users\svc_web\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,bebcb21ef97ab1644782d4f482a7f23d0838f55341f6ddd099088d44ac85b586,2026-02-15T19:22:47.320Z,2026-02-10T12:09:59.146Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-002,10.10.3.102,C:\Users\svc_web\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,1cb580e4318e25781be96f6d04e7b93fadccd48ee889480e0037fd217f9e2bec,2026-02-20T04:48:06.534Z,2026-02-10T21:45:29.326Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-002,10.10.3.102,C:\Users\svc_web\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,16eeb42c151ac92527b2e82ea3a7d59dcb390534c4421569cc93e01ea3b29226,2026-02-18T16:11:04.929Z,2026-02-19T05:11:09.976Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-002,10.10.3.102,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,e4d0929c0204f8af6a811845327056a96de7936903409f666f5000021605c468,2026-02-15T02:38:32.076Z,2026-02-15T05:24:05.057Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-002,10.10.3.102,C:\Users\svc_web\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,d4e62404cadeadd5aba511ceb1407ac2671c65534ea97d34a0babf3b1b520b40,2026-02-10T14:35:15.617Z,2026-02-13T22:49:02.985Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-002,10.10.3.102,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,884679c5c3376b620bbec01042eda5274e7baa3aabfd9acac7a4ec8fa2e71b70,2026-02-15T21:10:18.128Z,2026-02-18T20:16:10.218Z,ACME\svc_web,Windows Server 2022
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,dd940feda7697e0881885f7b832ddd42e4142ca98c6b04f62b337c277e9814d0,2026-02-18T22:56:18.542Z,2026-02-15T02:02:55.852Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,f4c291cc71e45efa2b658d1e870a37e646ecebf7d68ed668b1e5bb675b95b22f,2026-02-11T00:14:51.087Z,2026-02-14T04:19:44.082Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,cc0be4f1276f32dfabbc486d677da37eb0d1568e14bc7ef60a9362f30bb51e3f,2026-02-20T01:23:37.345Z,2026-02-18T02:44:48.884Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,43bc35995ce8bff1ed5600d037d2721f09a2875b35aded4e55184d17c8c34d7f,2026-02-16T23:01:08.694Z,2026-02-16T08:00:13.194Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,c46ac5b57260052d81b8fba68ac79452520042b72863ff2321380a184a0b9e01,2026-02-18T08:11:25.572Z,2026-02-14T21:14:50.154Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,6fd0ae828a07d440cb51faf7d224bf13ea6113e40714d17c42ee805d510700bd,2026-02-15T23:30:09.553Z,2026-02-16T06:58:21.648Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,e77c3ace2e9a5d4733cba737ecd56d481b871a76393082978b2cecab4b55cbde,2026-02-14T15:33:26.433Z,2026-02-18T23:16:32.653Z,ACME\gwhite,Windows Server 2019
|
||||
SLS-WS-003,10.10.1.103,C:\Users\gwhite\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,74701666b6b5491558f3b1ec3d933142bb859b338325835bca90c7822df99be2,2026-02-10T10:07:37.210Z,2026-02-11T05:06:28.050Z,ACME\gwhite,Windows Server 2019
|
||||
ENG-WS-004,10.10.2.104,C:\Users\hbrown\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,9ef2181c95134083db5c5d501df6811caf1025dc59bacb3ae22667483c021aa6,2026-02-15T05:26:07.808Z,2026-02-15T01:49:03.289Z,ACME\hbrown,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,C:\Users\hbrown\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,d806c1b1839c662f7094e4564f82f30466dbceadbb0160db19c8077df6e04abc,2026-02-10T12:08:24.672Z,2026-02-13T03:55:55.103Z,ACME\hbrown,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,C:\Users\hbrown\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,42dcbd93293a04150360bd533da1014753b5cc62fd92445137790df25b69fe17,2026-02-12T01:52:41.612Z,2026-02-12T04:59:20.851Z,ACME\hbrown,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,C:\Users\hbrown\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,cdbcf85c85dff5e23e4661495c409b365d7ee5c44d487adbd08d1dba7af45514,2026-02-19T04:46:17.158Z,2026-02-16T13:32:29.383Z,ACME\hbrown,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,C:\Users\hbrown\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,0458dabcf18d2301bcd001a9b14b113c3d86a053493f74447d993915e26ed954,2026-02-20T13:03:52.822Z,2026-02-17T03:20:36.883Z,ACME\hbrown,Windows 10 Enterprise
|
||||
ENG-WS-004,10.10.2.104,C:\Users\hbrown\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,52c722b3b67931d0e175cf13179adb22129c8b446913edf88dd9a9c90dc2ea27,2026-02-14T14:50:13.644Z,2026-02-13T17:25:28.793Z,ACME\hbrown,Windows 10 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,1e48d33e125f502928291ef60cfcef2ead5c7886e9cb02abab5712fdf5f423eb,2026-02-16T14:46:45.919Z,2026-02-10T18:25:21.559Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,26c7966d8aff5454de2850109926194eb175e5a7d731b121a9ff5e5145c2aa17,2026-02-15T11:24:54.902Z,2026-02-13T07:42:13.662Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,fcd8414e6848cf69774dd0d8f6dca14ceccb0a4e7b5691fa2bda5160a886e2fc,2026-02-15T08:14:11.397Z,2026-02-17T02:52:01.305Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,fd843f31fee5420f2ae676614c37327ea068ba3f78d725fa784e44816286e6dd,2026-02-15T16:52:41.161Z,2026-02-19T16:51:39.978Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,388e3ed505d1337e763301158d926ddefc8e724e418d96ee1e5c4e127f56918d,2026-02-18T04:38:19.565Z,2026-02-20T16:51:30.839Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,19f09f4df24fb4cf46992967253a3493261227f34356a4e7c599f338dd4224c0,2026-02-10T16:22:34.591Z,2026-02-12T11:54:32.586Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-005,10.10.3.105,C:\Users\jsmith\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,3c6a7b99a7d093ad3b8ed1d2a08ef2b2ba27c1a8fdeee940d35d1a0f713cbefd,2026-02-15T02:13:29.674Z,2026-02-19T14:57:22.742Z,ACME\jsmith,Windows 11 Enterprise
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,986dcba7adec12e5a478bdc20b77147ad715d4ede257b54243ac156ad0c9db28,2026-02-12T01:14:44.806Z,2026-02-14T19:58:03.091Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,80cb0154c923d605dfdac0131396d194ede1de502ccf46c71e664b80f34aa15b,2026-02-20T07:37:05.802Z,2026-02-18T06:48:03.060Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,5177f9eada05e28d62a7c6e6f7d9ff4d3008372c3bd4a9dc621399daf6d9e3a2,2026-02-17T23:10:16.463Z,2026-02-12T01:17:30.446Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,40589fde7236a2453e709da139c22595b1c9b5b9cf5985e18ebb3eead721ceb5,2026-02-11T18:41:14.257Z,2026-02-16T18:53:17.297Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,7ee1322239b7e4885a25274c69ab7cc21b7e9c85f436285b3df7f9f4c1a01087,2026-02-16T20:37:26.647Z,2026-02-12T19:44:45.379Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,c05b6c3bfdf7d91b5ead63d64ee45fb56b2c94b7a1ca931d27c8d2e0a04cc880,2026-02-10T18:18:33.303Z,2026-02-10T20:49:20.549Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,56ac289d6d1a68ace8ecccd3415e29e0628c03b4280c278eb36b4a1d57eb17e3,2026-02-13T07:59:05.135Z,2026-02-15T13:01:37.121Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-006,10.10.1.106,C:\Users\dlee\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,bfd3229229542781f26bdc4ad5d0fb7004df6f2650fb562c81162a9c935f11ca,2026-02-13T07:28:41.744Z,2026-02-19T12:13:19.445Z,ACME\dlee,Windows Server 2022
|
||||
EXEC-WS-007,10.10.2.107,C:\Users\idavis\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,02cdbff42616de0a0e88a574ec17a88d6d7510cb849c72768efe244b2c123297,2026-02-17T06:37:23.040Z,2026-02-14T19:33:31.793Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-007,10.10.2.107,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,e89cf3db33cdae1593007dae1051159b26c012fd035f3f12b92120fef736c36c,2026-02-20T00:43:45.544Z,2026-02-20T12:11:49.036Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-007,10.10.2.107,C:\Users\idavis\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,609a626819620750e20c363938331a8f7ae997f0ea6f08e45c8c65f241b3e123,2026-02-15T06:22:26.513Z,2026-02-10T08:32:05.554Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-007,10.10.2.107,C:\Users\idavis\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,520dff08006cc179d470d0c97f8bcfba81407acc58f6d1d3fc999582b73743e2,2026-02-12T23:56:30.834Z,2026-02-14T17:03:59.974Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-007,10.10.2.107,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,c4448dd90342e73bc926a48506f5ee668d85595e6a49a6dc837277ee21360020,2026-02-20T05:12:27.254Z,2026-02-10T09:30:26.085Z,ACME\idavis,Windows Server 2019
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,18dab2e9c0603c43780cd3739a62d78c35117dcb142ccedb671d0f5a93d63c97,2026-02-18T19:06:55.581Z,2026-02-17T09:11:51.013Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,8722cc701d5d5b63a2455fe48bbd40232dc307c94f737f32af738a86623bb95e,2026-02-11T11:21:02.231Z,2026-02-13T05:33:28.227Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,de2467de37daa07688b87f7f4b95306c8192ae93cc7cafc4f63938643d7c04f6,2026-02-16T22:59:40.937Z,2026-02-14T20:26:31.932Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,d9c25b2943eb2ba8452caa4549e343c24971da7915bf37ab6f74942a1208fc7e,2026-02-20T09:24:06.357Z,2026-02-19T00:37:53.480Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,ea16f8b57122c132454f3effa62cdb557bbe17dccc6d935a94332192c71141ad,2026-02-15T14:41:13.159Z,2026-02-11T17:25:02.171Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,ac7a7b3f0039772af6f43af475c1e3cb2008a6c8124d71691c9858380414bab1,2026-02-16T17:11:23.572Z,2026-02-13T11:42:50.802Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,e267e5ae9ed0a5276d042caf6c853d030a4c800714f680e36e3b5ec675224e1c,2026-02-12T22:58:14.806Z,2026-02-17T13:25:04.377Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-008,10.10.3.108,C:\Users\svc_backup\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,455ab448ad895b4378ac15382f798e0fd7e7ae912469e0b27238553ca6dfc87f,2026-02-11T17:25:46.935Z,2026-02-14T12:21:04.223Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,061958c628f2e622ccb045e884b77d591bd30419625046fdf1fe14676662c9d6,2026-02-10T10:33:57.592Z,2026-02-17T11:18:51.294Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,f52ba5baa778eb33238a1925f87e21b10e02a9a4d2eace637b580e9e9dd4b369,2026-02-15T04:32:42.457Z,2026-02-12T03:37:52.011Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,070032429ec132bfcc7182ad092eeacffbea2dd860a34e225068dbafdb835660,2026-02-17T12:31:06.335Z,2026-02-18T21:27:57.266Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,815d52dbb92cb2ef396795439e7edf5c84a7a6e9402e9b591f75a21b9352587a,2026-02-17T18:26:17.913Z,2026-02-16T01:04:11.210Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,36648d95785e3eedcc18c831c5d031c5ed7e7d849cfb98ec407b04a523cf20fe,2026-02-14T14:15:27.996Z,2026-02-20T10:14:17.722Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,6bfa2d1461989af77eaa98ddac4d93c521ade590aac87d472fac7ef2f973c78e,2026-02-17T10:44:17.885Z,2026-02-19T06:03:41.243Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-009,10.10.1.109,C:\Users\agarcia\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,d8765dcdaadf62ff78aa9b37f9d0d02bc91cf15cd9ddca7e7b24a38af0caed9b,2026-02-16T15:12:28.419Z,2026-02-19T16:20:41.048Z,ACME\agarcia,Windows 11 Enterprise
|
||||
FIN-WS-010,10.10.2.110,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,04ffd30571c246f40d0bd9ae1a2b2665054c5200a12b40ff4d5a3dc449d7e8c5,2026-02-10T10:33:10.480Z,2026-02-18T10:35:46.614Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-010,10.10.2.110,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,ec79d0ab01b25d4ea08918a99a87d63e0a762d89789423a77184da8812510712,2026-02-19T02:16:02.384Z,2026-02-20T13:50:16.951Z,ACME\svc_web,Windows Server 2022
|
||||
FIN-WS-010,10.10.2.110,C:\Users\svc_web\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,e14aa1f99c48aa55c7822fb37ec895b1c180be75f0948b7e88f73e9c0ca2b3fe,2026-02-15T22:11:35.355Z,2026-02-18T15:20:51.429Z,ACME\svc_web,Windows Server 2022
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,8195effa5cdcdae87f8ca8917163c052707f7aa2b926870f01b8ebf48bb7f9a8,2026-02-13T09:24:13.934Z,2026-02-16T22:41:56.031Z,ACME\svc_web,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,cb87187d93f7c0e32e953887d1eb9143378a33b2c38c47839cce86fdc7acc58e,2026-02-19T03:12:06.522Z,2026-02-10T17:42:02.709Z,ACME\svc_web,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,34602ccb8330dd6ed07c24bbe0f726ba21803a968bcf55e2eef6eee26880ea62,2026-02-18T12:27:49.007Z,2026-02-20T14:56:32.952Z,ACME\svc_web,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,43e44615245aab40de0f1a59fb09718e7b868f48142861e6da4e223c5abb1d82,2026-02-15T21:32:48.486Z,2026-02-15T14:08:38.686Z,ACME\svc_web,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,1391176bea5719b42bc1d06a5044544cd155702a91cbd6c4337f5a599159b273,2026-02-19T18:57:02.493Z,2026-02-11T02:47:23.745Z,ACME\svc_web,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,ee21410df3ff7c166c43f6c4f9281e386d8027d45ff44565e4d4f726a091c424,2026-02-15T19:50:53.847Z,2026-02-15T09:52:49.331Z,ACME\svc_web,Windows Server 2019
|
||||
SLS-WS-011,10.10.3.111,C:\Users\svc_web\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,647138b0cbc9d7b1054ec2bcf7d1f2fcdfd1a1cbd2d972d69b6b09db807cba2e,2026-02-15T16:13:18.192Z,2026-02-10T18:48:46.536Z,ACME\svc_web,Windows Server 2019
|
||||
ENG-WS-012,10.10.1.112,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,c00d6df589e08a33ae27df4ad008bd744fec1c0552047f74885943206c812397,2026-02-15T02:31:02.788Z,2026-02-12T06:35:40.669Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-012,10.10.1.112,C:\Users\bwilson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,48a8ccf902ca738fa29eaa647fa2f52f2ad279b69089e8277ddd8a1165544969,2026-02-16T18:03:48.839Z,2026-02-19T09:57:23.609Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-012,10.10.1.112,C:\Users\bwilson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,487392abecedb53dfe46f5bbd0b693b83d58e0462ac5389cfd86410065c0ade1,2026-02-19T06:16:25.477Z,2026-02-11T01:25:17.563Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-012,10.10.1.112,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,5d2e30c6a3ab68be3f1ea1c087bd318fa8aa1e64bf6890e53d1d7f4418225cb0,2026-02-13T08:33:12.589Z,2026-02-10T14:48:42.180Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-012,10.10.1.112,C:\Users\bwilson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,97be611a5089d11f738583bc60c36921106472017ac4c286da2f0ff76042d0ab,2026-02-12T14:49:00.469Z,2026-02-14T06:38:20.153Z,ACME\bwilson,Windows 10 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,82d54b3f9594e4745b373a71247dd9b42eb1f47d8a688dcee015c09735bef60c,2026-02-12T12:21:39.914Z,2026-02-14T13:08:38.645Z,ACME\gwhite,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,b60223b2fc35fbc64a9583f44b3c8939fd663c8596356fcd33851788923baf95,2026-02-18T05:07:56.774Z,2026-02-18T05:56:24.313Z,ACME\gwhite,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,d78bf349ffadec141bf780a26fb70b9a301bf82b2c7a059f0d66a3ea5316767c,2026-02-15T07:15:33.441Z,2026-02-12T10:37:09.919Z,ACME\gwhite,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,96a5721699b403903ee22cc951cc3c7b1726367075ebaa98539581b64a3d8274,2026-02-19T01:08:41.486Z,2026-02-15T14:07:00.136Z,ACME\gwhite,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,690417f4e18c2bf10e7bfff893f1496a25fe12061ba3d808fd0185abad519f3b,2026-02-10T11:01:38.737Z,2026-02-11T22:58:53.797Z,ACME\gwhite,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,49d4e93de9156a545e37ab553171d153bd7bec26e701ac3c09ba054ce2a49ab3,2026-02-13T21:48:55.078Z,2026-02-11T16:00:09.468Z,ACME\gwhite,Windows 11 Enterprise
|
||||
LEG-WS-013,10.10.2.113,C:\Users\gwhite\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,3796cfcc33c9df93a14ba9d2c08c443c536ecf1f70c937dab65bc29333334e4d,2026-02-12T20:29:08.822Z,2026-02-12T08:07:00.147Z,ACME\gwhite,Windows 11 Enterprise
|
||||
MKT-WS-014,10.10.3.114,C:\Users\bwilson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,81f824e4da5b29fe9ef7b8ef50aa3368449316ab2fea4e6ea4db76d464f2bee2,2026-02-16T23:50:25.975Z,2026-02-19T08:13:50.505Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-014,10.10.3.114,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,13422dc5abcdb2b5475145accfb6023f12360b77bbc88411765f464d2387a00d,2026-02-19T22:04:58.021Z,2026-02-12T22:35:28.171Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-014,10.10.3.114,C:\Users\bwilson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,819ba3781b023669628466931615fa2bf171dd6e5b474492b1d41d63e8803474,2026-02-14T23:39:24.269Z,2026-02-18T12:46:34.176Z,ACME\bwilson,Windows Server 2022
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,c95605c10d5098140ebc519d4c40265c640c894506d05c24d1ea7b5d82a9b17d,2026-02-17T12:10:11.109Z,2026-02-16T04:59:20.442Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,111b1f4c86c497ad934c8d278f68cb5a092eef4f16ee514336126745032e6a86,2026-02-10T10:25:47.678Z,2026-02-12T15:37:59.982Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,7051d68d067d298f4f05f7e048145415fa1e8c3428cd5658edab8b14a9d93435,2026-02-19T11:13:01.382Z,2026-02-15T15:03:42.696Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,aa8f97a0196b70692db7209df4cc79b5879261e37f8011b64d91120b4aa57154,2026-02-15T12:26:06.906Z,2026-02-12T17:21:35.303Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,2f8ec86d8fd39d4ba65342d74697fcecb2c15fb52c05900006b55e5b2425fb19,2026-02-11T14:46:35.748Z,2026-02-11T10:16:08.063Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,f1728a253b09625f61a30c9cd276a99090b9f17744bbd1f8dd73558e07633c8b,2026-02-16T03:50:29.471Z,2026-02-11T07:24:01.535Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,50969908f5327f9ea9ac63a8161caa60b336720697ad077fca912fd210f60b14,2026-02-20T02:11:25.387Z,2026-02-13T11:39:21.339Z,ACME\svc_web,Windows Server 2019
|
||||
EXEC-WS-015,10.10.1.115,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,cf98a147338f6fa2f32cd9e1a099b3d171763ad04f2d2c2b4d1839da3b9bdfbf,2026-02-20T07:41:51.468Z,2026-02-18T17:19:58.026Z,ACME\svc_web,Windows Server 2019
|
||||
IT-WS-016,10.10.2.116,C:\Users\idavis\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,771fbdb1c45229119f7ef681f28f47b0c5c4b61c45f1266e765b5267357a4cea,2026-02-15T20:04:14.053Z,2026-02-15T11:34:58.944Z,ACME\idavis,Windows 10 Enterprise
|
||||
IT-WS-016,10.10.2.116,C:\Users\idavis\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,5da4871367e1a461135a9ccaf4779c3de3f6719ec3fcd758fc93f1dfb6d2b775,2026-02-17T21:39:32.219Z,2026-02-14T22:01:00.640Z,ACME\idavis,Windows 10 Enterprise
|
||||
IT-WS-016,10.10.2.116,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,38d95d609715bfa54de3d7fbf1d288ad1fb3169a171bcdc6a71859fd3ad78bf5,2026-02-13T10:44:00.956Z,2026-02-15T03:19:31.176Z,ACME\idavis,Windows 10 Enterprise
|
||||
HR-WS-017,10.10.3.117,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,85882d5c26ecae94ae887fda18c5bb0f640eceb2cb757505c113dba15e37af8d,2026-02-15T21:02:39.675Z,2026-02-11T09:10:03.794Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-017,10.10.3.117,C:\Users\idavis\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,e0a93ce4b28ebaaf4027d4bb3fad19eecfdc6db4b19f50757fc6cda5357fb7a3,2026-02-16T04:07:18.864Z,2026-02-11T02:26:40.634Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-017,10.10.3.117,C:\Users\idavis\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,2d5280f8cdb4604ab2a770ce1af09fb96666cfca699a7823ea5aa52ed75b2c4e,2026-02-14T05:03:38.615Z,2026-02-14T06:41:36.091Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-017,10.10.3.117,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,0e01b23720cc521a339dd5a7e436404a509e7490b5b97a1a5bd67f35a622b411,2026-02-11T00:33:48.157Z,2026-02-16T10:08:10.633Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-017,10.10.3.117,C:\Users\idavis\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,843dbd5842d8790565dee1fc5ad9e39c5751bf72427cd2bb3f8fa1bafd07e14f,2026-02-15T06:41:00.888Z,2026-02-14T21:56:27.923Z,ACME\idavis,Windows 11 Enterprise
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,ac21d2a43b1839b133da3a2c2fbc425c6515e634744458e6b0dd6af0226286cd,2026-02-16T13:10:24.302Z,2026-02-13T22:58:29.701Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,c6fdcb03c2f2ca8188ca3d82f1aec2aa9ba3362b8a32901c9f3bfb4a8799a47c,2026-02-12T15:58:59.584Z,2026-02-11T18:53:25.718Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,99f2ed6d3fca6526621caf0a25113596c173901f71bdffa0785510070939d8c8,2026-02-12T19:30:31.054Z,2026-02-18T21:37:17.785Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,318efe09cd2102e6f78a3584aef6ada02ddba772891c5f1fb1c76e2258bf3df2,2026-02-16T22:21:23.145Z,2026-02-19T19:20:53.180Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,f677a56375e6dde90cbac73c3b083b257d77a3a34a205740d996764650705f90,2026-02-11T22:28:23.820Z,2026-02-17T21:39:36.140Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,88f63a1476f73d0f72d31ba8522b9ddc907c02852b3878a02b66f0124a9a1a1e,2026-02-19T15:25:15.826Z,2026-02-16T16:29:51.946Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-018,10.10.1.118,C:\Users\cjohnson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,d20b92c5e27382941c3617b95f24392422cc8c31c4c58f1d471a836af5c9b78f,2026-02-18T17:51:42.704Z,2026-02-11T04:56:47.216Z,ACME\cjohnson,Windows Server 2022
|
||||
SLS-WS-019,10.10.2.119,C:\Users\jsmith\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,e9b9e19b802d99147764ebf9d98fa56c77f1105bb0ede548a1421a5d357a31df,2026-02-18T13:46:01.003Z,2026-02-15T15:46:24.285Z,ACME\jsmith,Windows Server 2019
|
||||
SLS-WS-019,10.10.2.119,C:\Users\jsmith\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,2b04c91127a1428994a3a0a24d00dffc08eaf08afdc9dca3040f80224d99044d,2026-02-14T10:55:22.796Z,2026-02-15T00:29:25.508Z,ACME\jsmith,Windows Server 2019
|
||||
SLS-WS-019,10.10.2.119,C:\Users\jsmith\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,e22e5b81bbef8a44cb5d3b669dfbc67c5e0260092a4fab21c4bad91e16ade656,2026-02-14T00:05:23.209Z,2026-02-11T13:37:55.023Z,ACME\jsmith,Windows Server 2019
|
||||
SLS-WS-019,10.10.2.119,C:\Users\jsmith\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,8ec2632970b87bf60cdbcdebe6142c071bd3b40e5f30fc19eb0c040f7b4ad70a,2026-02-19T06:52:33.651Z,2026-02-20T16:10:33.204Z,ACME\jsmith,Windows Server 2019
|
||||
SLS-WS-019,10.10.2.119,C:\Users\jsmith\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,6fb8e562463b6a1899b5aac434e83f89e59ba3f8258152c86a7b68f3074a1822,2026-02-18T16:18:14.258Z,2026-02-16T19:22:23.529Z,ACME\jsmith,Windows Server 2019
|
||||
ENG-WS-020,10.10.3.120,C:\Users\emartinez\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,6395891ca629a4ebf2436a9241c7159906f310e49e67b132859cee23ec56f591,2026-02-15T11:39:45.307Z,2026-02-14T18:51:35.356Z,ACME\emartinez,Windows 10 Enterprise
|
||||
ENG-WS-020,10.10.3.120,C:\Users\emartinez\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,6ca1fc4c6cfbf1c8059773b5ff3145e8751bbd3031992a0cfc99839878b9bed2,2026-02-12T05:37:54.651Z,2026-02-12T02:40:38.109Z,ACME\emartinez,Windows 10 Enterprise
|
||||
ENG-WS-020,10.10.3.120,C:\Users\emartinez\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,dbc7ddae01120381d222d016fcf7e00f1d82c1757bfbd98dc5386225ed020803,2026-02-12T06:35:08.956Z,2026-02-10T21:01:28.305Z,ACME\emartinez,Windows 10 Enterprise
|
||||
ENG-WS-020,10.10.3.120,C:\Users\emartinez\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,bad743bc78682e6f4cd975900a5d681510c8df07ecbf7a1cf4f2de8e67bc315c,2026-02-14T16:33:22.394Z,2026-02-13T18:55:44.710Z,ACME\emartinez,Windows 10 Enterprise
|
||||
LEG-WS-021,10.10.1.121,C:\Users\dlee\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,af7b6e167afbb3e68674fad243433f1b1e5aaf93bb5a8bbbd523bd20bcfc8429,2026-02-13T07:54:51.526Z,2026-02-12T11:19:10.407Z,ACME\dlee,Windows 11 Enterprise
|
||||
LEG-WS-021,10.10.1.121,C:\Users\dlee\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,f919410ed899e84eb617986a2d6f00c6bda557033bfa1ccb991f45ce6d84226c,2026-02-20T07:09:54.353Z,2026-02-19T08:21:23.376Z,ACME\dlee,Windows 11 Enterprise
|
||||
LEG-WS-021,10.10.1.121,C:\Users\dlee\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,032aefc466cecd1bc8a4193efd0a40610f898f0c7d753f5d878493984301daa3,2026-02-16T12:50:38.668Z,2026-02-17T00:30:17.495Z,ACME\dlee,Windows 11 Enterprise
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,78e97fbb938b97a5a3c1631966d3eb4c7f39f020e28becb5d942f8403af256f6,2026-02-19T15:27:21.801Z,2026-02-18T06:21:16.450Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,cfc244fb6d959c3b1fc9b7f709cb7780e421e306b9e601dadaef1e9a5702eb4a,2026-02-16T14:40:27.993Z,2026-02-17T04:36:11.910Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,0fc975a17dc3189dc70773778c4045869d2494aebab4385c989521fe9a125485,2026-02-12T00:13:03.842Z,2026-02-18T06:23:04.573Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,0e5973748294d2375ee04c55e9f30bee797b5d424011434f061f06f04a1cbc59,2026-02-13T10:15:20.331Z,2026-02-18T17:21:50.586Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,cdafaa8dc66649af20a3b6a9513e89a249c4af8998082753b8379e154d71356a,2026-02-14T12:25:44.863Z,2026-02-13T01:18:19.585Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,a020898201e929b8b0d7ffa4a84471b1c66711dbd226748ba11520fe79874b00,2026-02-19T18:13:54.754Z,2026-02-15T04:39:12.277Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,09b027d9be23b8460d796fba35e4c5f5b93f3982c541a39e0568a69ce3d1709f,2026-02-18T21:22:28.097Z,2026-02-15T14:15:27.038Z,ACME\svc_web,Windows Server 2022
|
||||
MKT-WS-022,10.10.2.122,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,b8b1e706ae8c75e1576cb44a4776443f455b562eae4ea6d79c1744de443301e8,2026-02-11T21:04:14.555Z,2026-02-13T05:41:53.727Z,ACME\svc_web,Windows Server 2022
|
||||
EXEC-WS-023,10.10.3.123,C:\Users\dlee\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,9214af24edab339b3fbeb0b0a2fe06e0db0c588ef59882f934f81abb21d1cdc6,2026-02-11T14:50:24.265Z,2026-02-18T11:08:52.872Z,ACME\dlee,Windows Server 2019
|
||||
EXEC-WS-023,10.10.3.123,C:\Users\dlee\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,1299b1cf1ca8dee8a986afbfae7b50893a658a2d475eca546e1b5e284e72aa52,2026-02-11T08:41:14.303Z,2026-02-19T10:22:56.199Z,ACME\dlee,Windows Server 2019
|
||||
EXEC-WS-023,10.10.3.123,C:\Users\dlee\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,30e2f20a28a550f4223352ce32e0c8ed4395889fb139490d7866e935304868c4,2026-02-20T09:25:00.268Z,2026-02-16T00:44:46.451Z,ACME\dlee,Windows Server 2019
|
||||
EXEC-WS-023,10.10.3.123,C:\Users\dlee\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,1df7c0b9e0cf99a529942fa373a90d444643f48222dde21ac4f6ba157f15b8c6,2026-02-20T12:57:48.564Z,2026-02-18T13:30:26.472Z,ACME\dlee,Windows Server 2019
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,c83aa77f8401a9abe73863022e9aeb69c1cf1db58f20f5d2b7f2f20ee69f0fe1,2026-02-15T16:53:02.632Z,2026-02-15T06:18:37.743Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,5dec0d840e6e3ec6d1b3fc0f2ab23207f7376d5c286480624d84cfa934a46d89,2026-02-16T09:59:04.958Z,2026-02-19T15:33:15.955Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,b36194f85f946b0a61eb4d457e6f582ec223173d74cd4f85217412f468bfbbd1,2026-02-10T13:49:12.100Z,2026-02-12T05:50:06.543Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,25c075acf71a02449f9cada327e0524807f8e314f42324fd03ea90b93a26c882,2026-02-11T22:29:32.029Z,2026-02-11T10:55:22.798Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,2acc5f10d551e72b4566e0045756a13cabfdd5adeef17071c03bd66ad36599a3,2026-02-17T18:53:46.682Z,2026-02-19T15:13:11.957Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,878aefdbabf42578d87499066ea315b103b6c75751bd644883dacffc19221546,2026-02-11T19:02:51.117Z,2026-02-10T20:46:07.104Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,a41cfa659a48a3ce63356be6ac6b90b2ebc0174f77273f14d5ff2315358853d8,2026-02-18T10:19:54.685Z,2026-02-14T23:15:46.596Z,ACME\fthompson,Windows 10 Enterprise
|
||||
IT-WS-024,10.10.1.124,C:\Users\fthompson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,bf68174459da914b86d509e8fe3a5917102371805cd5a2e860685eaf83223a7d,2026-02-17T01:02:32.778Z,2026-02-17T17:05:56.527Z,ACME\fthompson,Windows 10 Enterprise
|
||||
HR-WS-025,10.10.2.125,C:\Users\agarcia\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,f3400b367a0a4e89f590d942dd963b08c0db304b657147098618d54097531dbd,2026-02-13T12:09:19.195Z,2026-02-16T04:28:04.747Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-025,10.10.2.125,C:\Users\agarcia\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,6c6adf8336a9ee411cbe60a8c9ffba92008bcf9467b2e1dbf3e4eca05fee5081,2026-02-19T13:04:28.343Z,2026-02-10T16:45:40.445Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-025,10.10.2.125,C:\Users\agarcia\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,dffc21f55f13de152263d858fc05428768996e461db305aee8eb583c602f5df3,2026-02-12T02:04:02.215Z,2026-02-18T06:25:19.318Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-025,10.10.2.125,C:\Users\agarcia\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,112e513c68ee2553dcd59684a7ac9076dd53431c8d8f9d58e468f8804f166c3b,2026-02-16T11:03:05.757Z,2026-02-10T19:47:40.837Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-025,10.10.2.125,C:\Users\agarcia\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,012bd3195f3e0b3b1e388e96e27696f21017eea267d07328e25c56a80f89a9cf,2026-02-17T09:33:59.446Z,2026-02-13T21:04:33.547Z,ACME\agarcia,Windows 11 Enterprise
|
||||
HR-WS-025,10.10.2.125,C:\Users\agarcia\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,b91717b8431f8621847d64b7e096d25462a9fde54e65d58bde68edaa82731999,2026-02-16T02:50:39.284Z,2026-02-11T11:20:41.040Z,ACME\agarcia,Windows 11 Enterprise
|
||||
FIN-WS-026,10.10.3.126,C:\Users\svc_sql\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,f57784da71565fd96d8fb3f6b3163196b745ceb56253569ee54e337259bc45fa,2026-02-18T10:17:41.924Z,2026-02-15T19:11:10.397Z,ACME\svc_sql,Windows Server 2022
|
||||
FIN-WS-026,10.10.3.126,C:\Users\svc_sql\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,8205edb20d13c17f71d807552b722bdbdc37b844e65ae7ad27f759f07e348e6f,2026-02-11T20:08:53.008Z,2026-02-10T18:58:26.359Z,ACME\svc_sql,Windows Server 2022
|
||||
FIN-WS-026,10.10.3.126,C:\Users\svc_sql\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,c6abca91ea20bdc4bd2a206de6e3ae4736e689f599c08884b64d553240bf48ee,2026-02-19T04:58:57.663Z,2026-02-12T10:15:48.479Z,ACME\svc_sql,Windows Server 2022
|
||||
SLS-WS-027,10.10.1.127,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,91c544dd5daa1ed9ad2c1f8ae703da89cbdf0f1e2a5f574f78cec724afe65a34,2026-02-13T02:11:26.259Z,2026-02-13T07:25:54.504Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-027,10.10.1.127,C:\Users\bwilson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,9664a3afa2b3ba0c82291a65e2f9234accb735a7afb3b4a2aa5f7b1e69c53867,2026-02-11T23:17:29.928Z,2026-02-18T23:08:53.569Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-027,10.10.1.127,C:\Users\bwilson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,3eaad4fb9ce1eba5d4f4a8b345b549633347c2f1fa4556b899067a5ce205f419,2026-02-17T16:14:32.811Z,2026-02-10T12:21:57.016Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-027,10.10.1.127,C:\Users\bwilson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,3d1d9fa885481be6b98bde44e4a7d5d0ebba8ef4342785a6f07b0e4d17047d61,2026-02-12T07:27:23.560Z,2026-02-14T01:19:39.909Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-027,10.10.1.127,C:\Users\bwilson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,8262ded2edfded9c4cda58bb529a46be3be60ad648ef7637832cd3e572316190,2026-02-14T10:12:51.874Z,2026-02-11T19:56:26.161Z,ACME\bwilson,Windows Server 2019
|
||||
ENG-WS-028,10.10.2.128,C:\Users\fthompson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,e45eb54de65ca04a9e9499047f9e2f55b430f6d9177a1ed5d578d52da80b4976,2026-02-17T22:57:21.030Z,2026-02-16T14:01:19.812Z,ACME\fthompson,Windows 10 Enterprise
|
||||
ENG-WS-028,10.10.2.128,C:\Users\fthompson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,23cc51d16870ff841109482b189e56c2cbb25eb027208afb7d94c12c07c180be,2026-02-18T11:06:31.245Z,2026-02-15T07:35:13.379Z,ACME\fthompson,Windows 10 Enterprise
|
||||
ENG-WS-028,10.10.2.128,C:\Users\fthompson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,7560bf2e3fd88995bf2717503b9fbe5d37b643fba61a74068482cf0bf5679eb9,2026-02-20T06:16:46.087Z,2026-02-10T20:43:24.291Z,ACME\fthompson,Windows 10 Enterprise
|
||||
ENG-WS-028,10.10.2.128,C:\Users\fthompson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,3bc3fe100e5b9e597e26ac426042708d8cc91d8a703c67983ead7f349b4f13f8,2026-02-17T09:34:19.950Z,2026-02-14T13:48:57.701Z,ACME\fthompson,Windows 10 Enterprise
|
||||
LEG-WS-029,10.10.3.129,C:\Users\emartinez\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,5e6ec662788b1b19c736c148b1942ac5755d2e7cab9ac87a88920fda5a7752e2,2026-02-13T02:28:34.800Z,2026-02-14T08:40:24.282Z,ACME\emartinez,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,C:\Users\emartinez\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,db799da2f9c0d2a67d8c51e63acde705329573eec13e2c745f85154a14800bb9,2026-02-14T04:56:48.894Z,2026-02-13T13:52:53.766Z,ACME\emartinez,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,C:\Users\emartinez\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,cfe69a439620a18581c26f82e19f3b1094fb68a651aaa9fc96667afdbe7be0d6,2026-02-16T14:50:37.334Z,2026-02-19T20:23:16.204Z,ACME\emartinez,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,C:\Users\emartinez\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,dfe3f478183fc7b9b6afaa60232eaae71743b477004910dc3cec605e992b9e9b,2026-02-14T00:44:44.842Z,2026-02-11T18:00:20.836Z,ACME\emartinez,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,C:\Users\emartinez\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,b82143dbc3b4c4552e2a8a79ea3b881ecf0fdd83063610d61d1b9f662b7f000c,2026-02-20T06:38:09.130Z,2026-02-13T06:12:01.506Z,ACME\emartinez,Windows 11 Enterprise
|
||||
LEG-WS-029,10.10.3.129,C:\Users\emartinez\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,5165edc673331f8e91ea782af66d022646b0fe711e3cccf30f3b8c1522d89140,2026-02-19T04:48:35.722Z,2026-02-19T18:53:50.138Z,ACME\emartinez,Windows 11 Enterprise
|
||||
MKT-WS-030,10.10.1.130,C:\Users\fthompson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,bc188605ea225c34812fbba2ae8564d2fd361dc3aef73e97c7f6efade6e78cef,2026-02-19T13:59:02.088Z,2026-02-12T07:07:14.562Z,ACME\fthompson,Windows Server 2022
|
||||
MKT-WS-030,10.10.1.130,C:\Users\fthompson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,84a489daa18c2664969f49490c2a334b52246a13e2c4d446b1eb330f302baa55,2026-02-11T13:09:23.409Z,2026-02-10T20:53:38.200Z,ACME\fthompson,Windows Server 2022
|
||||
MKT-WS-030,10.10.1.130,C:\Users\fthompson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,308c8971a8caf7957721f50f4fbfd214f2907cb67f204003e19e097ad7fb6aa1,2026-02-10T18:07:36.618Z,2026-02-20T06:58:27.961Z,ACME\fthompson,Windows Server 2022
|
||||
MKT-WS-030,10.10.1.130,C:\Users\fthompson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,1b85d1263bd84ee7e75b2fd53f8a2c375b544da20111d20c49ba416ce602d3f6,2026-02-13T07:35:20.553Z,2026-02-14T11:29:51.396Z,ACME\fthompson,Windows Server 2022
|
||||
MKT-WS-030,10.10.1.130,C:\Users\fthompson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,e97de97233d9a1e36c0bd8a0700390b930f9b3ba2f87d37219329c8febe5d1e0,2026-02-20T01:03:11.510Z,2026-02-19T17:00:18.211Z,ACME\fthompson,Windows Server 2022
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,e5e6c9b1f9fdf05b2fb49904e2fad7f5dcb0025246bc90b0921c2ec6e4548904,2026-02-18T18:29:25.712Z,2026-02-11T20:44:07.683Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,558e2eefc862b79c26a4795e8764e66fc472a0f0e475924a5506fe19c8302dab,2026-02-13T06:11:23.071Z,2026-02-16T21:37:18.960Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,b1065393ce7921c3df0473685fe041c1b912bfb003ed75856cd181f6ea2fde12,2026-02-14T05:38:11.047Z,2026-02-18T05:27:02.185Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,4bdd583a7b8185bb2b900b41b4933ea4714cb8e9c8e4efd480e51b766952c871,2026-02-14T00:09:51.009Z,2026-02-20T12:03:51.081Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,939db503a0b5f00327ae47fa12c8766c16368e37dbecbd63d385d77e6aa8260e,2026-02-16T19:05:15.953Z,2026-02-13T10:05:22.004Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,4dbbe7f18c26a4cb8adbcb60501860305d9377dbfd7d1ccaf2a4871f2b8888b6,2026-02-10T10:41:53.628Z,2026-02-16T15:44:47.603Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,4abe4f1f36f913fdd83e5e05d04228adb884ba09bdcc79fc0e9cd25a65d8ea4c,2026-02-15T17:00:14.420Z,2026-02-15T14:52:35.294Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-031,10.10.2.131,C:\Users\idavis\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,054944316c8fe1e77b599a92ceb5d3b5cc30880f1570dd1ce983eb38cc7b3e63,2026-02-15T09:50:20.336Z,2026-02-17T06:38:25.460Z,ACME\idavis,Windows Server 2019
|
||||
IT-WS-032,10.10.3.132,C:\Users\svc_backup\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,ace45a6b76fe1479d364204ed11e16e81779456632adb0acdb98c371177cdeb2,2026-02-19T06:52:17.245Z,2026-02-11T06:25:29.532Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-032,10.10.3.132,C:\Users\svc_backup\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,61a0341ddfefb026471b2839190a7fe695e8d04628286bc73b2ecc0965e4dc9d,2026-02-17T01:45:36.259Z,2026-02-11T18:35:57.603Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-032,10.10.3.132,C:\Users\svc_backup\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,a8e5df5fcf2f588bbdcba1c25ce0523d5781691802e7bccb3d28773effe506bd,2026-02-15T15:23:41.465Z,2026-02-19T04:04:38.862Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
IT-WS-032,10.10.3.132,C:\Users\svc_backup\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,54e776fed7cf065c183b41c73aedc29de4d78d5e9ddf21b0165fb2656b85a04e,2026-02-12T05:37:36.274Z,2026-02-13T01:42:39.770Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
HR-WS-033,10.10.1.133,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,7dfcb22b3fe5fcf7a1068ad409fd197562bd993dcb5ef8d5fee9ae1c5a9a0bda,2026-02-18T13:56:57.432Z,2026-02-13T04:52:19.892Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,C:\Users\idavis\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,dc8e9f13c063ebf28785f93c9539f48a6fbdf646425195e1b92bc986169d05b8,2026-02-12T17:44:13.063Z,2026-02-14T05:04:12.879Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,C:\Users\idavis\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,83c6d6cb9494782556bdf79dd1993b27df4aec5ed76e7681f6ac71978b67fec0,2026-02-17T00:25:15.116Z,2026-02-20T14:20:54.809Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,C:\Users\idavis\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,d94a432c86532c08934172cad9a5f0cd077e0b6e6a25d5f037d9a97c2148b314,2026-02-19T05:44:42.767Z,2026-02-11T06:51:26.667Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,3df492e0121556726d79dbf0fa4dfe3d7945e9163042696d317fadb88f605111,2026-02-15T06:08:00.698Z,2026-02-11T13:01:25.568Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-033,10.10.1.133,C:\Users\idavis\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,1eec1df9f1bb478acd7b96e42d204523c2699ae13b63114384f1aa0205719f33,2026-02-12T11:16:46.815Z,2026-02-11T04:15:43.242Z,ACME\idavis,Windows 11 Enterprise
|
||||
FIN-WS-034,10.10.2.134,C:\Users\jsmith\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,5ea5cc6f716f93998f5e8ae7e96e11dde920bb69e43ecf84f20bb75a46213822,2026-02-17T06:10:17.311Z,2026-02-13T18:05:07.728Z,ACME\jsmith,Windows Server 2022
|
||||
FIN-WS-034,10.10.2.134,C:\Users\jsmith\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,a711c5cc83d5427dd34c75fc9ba7921ab91618aa5a61f8f7fe8dada92bb559cd,2026-02-12T13:31:38.992Z,2026-02-15T01:10:21.926Z,ACME\jsmith,Windows Server 2022
|
||||
FIN-WS-034,10.10.2.134,C:\Users\jsmith\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,a8c207730c61bb964f99e6887ad46adc6343993eede4b20852de5574a567643d,2026-02-11T21:30:04.245Z,2026-02-19T13:17:55.999Z,ACME\jsmith,Windows Server 2022
|
||||
FIN-WS-034,10.10.2.134,C:\Users\jsmith\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,ed2f9de53e806c07c8d5f389ebfc04d0d0b6be3019266fd2307a1361a74130d8,2026-02-15T01:28:25.600Z,2026-02-18T14:35:15.979Z,ACME\jsmith,Windows Server 2022
|
||||
FIN-WS-034,10.10.2.134,C:\Users\jsmith\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,e3a3977a17f3baaca4e97a0a37535b34fbdf51e81a267c825739a341a47ccb6d,2026-02-11T14:53:18.458Z,2026-02-19T17:41:57.822Z,ACME\jsmith,Windows Server 2022
|
||||
FIN-WS-034,10.10.2.134,C:\Users\jsmith\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,67f8521ede4cf808716104a5d755bdd71fc0d8ca21b1e189e78e423a6417df3e,2026-02-11T11:41:52.692Z,2026-02-13T08:58:56.276Z,ACME\jsmith,Windows Server 2022
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,c8c97a3fe47aad6212c12959572a31fb3e4b69241d32a9b2961f230d89071128,2026-02-12T12:51:21.276Z,2026-02-10T23:32:58.010Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,e779ad8864325055b8c1d8a112f1f84dce970ccc54d9e908ff289d7c5bd896ce,2026-02-11T14:19:46.425Z,2026-02-11T18:54:50.469Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,9a096ee99dffeace622915c7a8dabe5e3549862be4f27edbe3a275310496e8bc,2026-02-16T06:46:54.792Z,2026-02-12T02:50:04.974Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,52dc2715a00ea5ed0f688b1849d592daa25d0238865ac3a564a9c1819c0855ac,2026-02-20T05:09:27.577Z,2026-02-13T01:12:34.235Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,9a44afb82aed4e6be66a79275a22e5aabd39e3d2c902c14c5208f14508d24a1e,2026-02-19T02:51:24.872Z,2026-02-17T10:28:35.146Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,ee6710f2a83d86a77d9f1c5997fe4bcd44da0cfc94d7f6a9afb1bee66ccd6434,2026-02-19T18:42:44.310Z,2026-02-13T21:12:09.751Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,809a881510d282551e8c40a05d5d44f32005dce65c7718cad210b38900db41e3,2026-02-18T05:38:36.021Z,2026-02-17T10:52:29.239Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-035,10.10.3.135,C:\Users\hbrown\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,9113a902ea2d6046ff9e2217a0f6526def8eb1390920c752f7bcadebc536e8f0,2026-02-10T10:41:39.118Z,2026-02-15T02:26:59.814Z,ACME\hbrown,Windows Server 2019
|
||||
ENG-WS-036,10.10.1.136,C:\Users\admin\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,1bd13af9bdbdc08fb38d5204c6b6ef474151989a6a8636364ee42f869ca840be,2026-02-16T15:14:21.074Z,2026-02-15T12:25:14.086Z,ACME\admin,Windows 10 Enterprise
|
||||
ENG-WS-036,10.10.1.136,C:\Users\admin\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,ba3801328dd87b02d93af8fac334848cceaa87d94c93cb6e4cdaa8683ae32562,2026-02-14T01:00:06.403Z,2026-02-20T09:09:35.306Z,ACME\admin,Windows 10 Enterprise
|
||||
ENG-WS-036,10.10.1.136,C:\Users\admin\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,fb9723037b217327edcb6f61c85840cd472c0c4e3a53deabb783df2f8bbd9d51,2026-02-10T14:53:12.870Z,2026-02-16T20:12:46.600Z,ACME\admin,Windows 10 Enterprise
|
||||
ENG-WS-036,10.10.1.136,C:\Users\admin\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,05f0f3504ae99d31286df3032f3c169c8d7d18cb286487e8adc2719809f45f10,2026-02-11T08:17:02.173Z,2026-02-13T02:49:48.080Z,ACME\admin,Windows 10 Enterprise
|
||||
ENG-WS-036,10.10.1.136,C:\Users\admin\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,c6204f53c0b2a92aac50c49874bd3c3396ce84a4e7245eeaf431357cd6066a09,2026-02-19T08:27:29.801Z,2026-02-12T03:12:57.119Z,ACME\admin,Windows 10 Enterprise
|
||||
LEG-WS-037,10.10.2.137,C:\Users\jsmith\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,11d84aba9b6fb92692c6c3877d7ed3f6d8e9e6bfe02176f1c90b429972bead4b,2026-02-16T16:43:28.875Z,2026-02-11T08:21:13.326Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-037,10.10.2.137,C:\Users\jsmith\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,4bb17fbc2388d976a74dde32e999e1938bf05b96ccacd344536ee83fcf9f54fe,2026-02-20T01:03:42.410Z,2026-02-11T10:16:41.667Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-037,10.10.2.137,C:\Users\jsmith\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,4fd19b36fb2125c9b61bb2e6996dba2e2b07b81eaf40e0e69d09c52a7079a14f,2026-02-18T17:59:20.902Z,2026-02-12T09:24:23.517Z,ACME\jsmith,Windows 11 Enterprise
|
||||
LEG-WS-037,10.10.2.137,C:\Users\jsmith\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,e86344507d08dec44296bdf977d4c8d0a670875071bf56e23b5fa292ffb50649,2026-02-17T06:33:11.969Z,2026-02-14T15:11:35.189Z,ACME\jsmith,Windows 11 Enterprise
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,8739271a457daf8f41f30037e1c1ced0d54c95fe59389991c117a0f8453ce868,2026-02-15T12:13:23.608Z,2026-02-12T08:42:46.245Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,d1342ced6690422872780de686c8e7494954935308ba8f7ae7ddb9711a3396b0,2026-02-19T07:48:51.901Z,2026-02-15T08:36:20.479Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,6539e9b3c1abaca85a289d05ca4586f23fac9d93d2323931e53a619b52bba9b0,2026-02-17T15:39:34.395Z,2026-02-20T05:29:11.517Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,3fb656a44717a35a7c0be9a901365af909e24b7c425d927552b096fc7167ec4c,2026-02-11T12:46:09.509Z,2026-02-18T05:47:14.564Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,9063b069d8db3fd36bb61c02fcd4341632897245d9b0749108e7d37dff081bed,2026-02-15T14:02:37.289Z,2026-02-18T16:56:33.936Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,0d09a89c8c062c535072505ab85c53c406b90ada55f2bc1779169081d7f2588c,2026-02-19T04:47:12.878Z,2026-02-18T00:54:24.218Z,ACME\dlee,Windows Server 2022
|
||||
MKT-WS-038,10.10.3.138,C:\Users\dlee\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,11203efe9474b9ba0ef4a03b9c28759d0160e3d9d93588a06d478c36f874c65c,2026-02-20T02:29:49.078Z,2026-02-19T22:21:47.535Z,ACME\dlee,Windows Server 2022
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,826823ebcf6461b669d6dfcf4d4e495d5627d79d6dc032dda8fe3c2c48fcc50a,2026-02-20T00:00:12.630Z,2026-02-15T11:27:53.682Z,ACME\svc_sql,Windows Server 2019
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,97b7f6c064e5b62490dc562001d0e2f95b7d8b6e7a11f78e5921e000c5e5f9e4,2026-02-17T18:26:33.219Z,2026-02-15T18:44:39.015Z,ACME\svc_sql,Windows Server 2019
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,34c482eab608dffe2f6758f46a41d9d937edd67dce700f017558626b13cd85fc,2026-02-19T12:05:29.960Z,2026-02-12T11:47:29.812Z,ACME\svc_sql,Windows Server 2019
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,237048f3f8186d58b7bca02c01da3f5a90fca244fcd60e46111863b29c4cd555,2026-02-14T18:36:48.164Z,2026-02-15T21:38:26.531Z,ACME\svc_sql,Windows Server 2019
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,a5ec502981598ffdab22820409a2e4fd7017cfb9d9a01bfc69f7a946f880d021,2026-02-17T06:22:57.044Z,2026-02-16T07:39:30.819Z,ACME\svc_sql,Windows Server 2019
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,1dae3108a52487125d94622cf48258726b235c252999bdfed3ccaa91bace0215,2026-02-18T23:16:27.662Z,2026-02-14T16:25:39.893Z,ACME\svc_sql,Windows Server 2019
|
||||
EXEC-WS-039,10.10.1.139,C:\Users\svc_sql\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,cb5657ca38b76d25085837f60deccd56569b22c7754e9a929cae3ac4b56c643e,2026-02-13T11:50:27.341Z,2026-02-16T00:45:44.228Z,ACME\svc_sql,Windows Server 2019
|
||||
IT-WS-040,10.10.2.140,C:\Users\svc_web\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,62bbebecfcdb6b0e658c0503f21acbf4522930206e7e804e4df1bb50d529c025,2026-02-12T05:52:16.406Z,2026-02-20T01:56:44.308Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-040,10.10.2.140,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,559a434bad4fd1e7ac1d2219a5e796f6a528b8d0ae9a3bcf3a4c2710389a57bb,2026-02-15T12:26:38.075Z,2026-02-13T19:50:04.532Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-040,10.10.2.140,C:\Users\svc_web\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,e7aa387015616f0568607781a009fefbc244025c7673c862fe73abbbbf2f250c,2026-02-10T10:15:46.451Z,2026-02-14T14:07:36.291Z,ACME\svc_web,Windows 10 Enterprise
|
||||
HR-WS-041,10.10.3.141,C:\Users\emartinez\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,64c9e9427ec80e30136f0faa959a1daad692bb35973496202ccd6084b6d57e7d,2026-02-14T17:38:15.014Z,2026-02-12T23:50:02.998Z,ACME\emartinez,Windows 11 Enterprise
|
||||
HR-WS-041,10.10.3.141,C:\Users\emartinez\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,b0ca056803ad17117b280211794722eb5de45ddfa57b35e09bb4ffd2110cc0db,2026-02-13T03:12:59.189Z,2026-02-10T09:56:05.071Z,ACME\emartinez,Windows 11 Enterprise
|
||||
HR-WS-041,10.10.3.141,C:\Users\emartinez\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,6bcd09fa9f6e067c94dd7157e87730e622b18af8b4ecfb0f7e783af146738e53,2026-02-11T16:32:02.524Z,2026-02-18T03:07:54.319Z,ACME\emartinez,Windows 11 Enterprise
|
||||
HR-WS-041,10.10.3.141,C:\Users\emartinez\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,2c65e1bc636fcb720f680b6319ed9c4b41ef473ba6ff493ecacfdb04741e4ffd,2026-02-14T15:44:58.668Z,2026-02-17T22:42:21.245Z,ACME\emartinez,Windows 11 Enterprise
|
||||
HR-WS-041,10.10.3.141,C:\Users\emartinez\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,5c5f013d7cc755997654fb94b39516106c14f9ec40eebe465751db3231dfa7cd,2026-02-12T11:37:53.268Z,2026-02-19T10:25:06.657Z,ACME\emartinez,Windows 11 Enterprise
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,a4da7990e3d3a19d0198a2b92d8be7e9083031fe8ee5648c73fb46441c1aa43e,2026-02-15T07:47:32.254Z,2026-02-16T00:08:09.602Z,ACME\idavis,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,69cd9b817a3e8b48a586b2b2660be5551d926a12a6f552403579aab03bd362d2,2026-02-11T10:27:22.817Z,2026-02-17T22:43:10.085Z,ACME\idavis,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,7c4de6d7b583d8f253040ea6415d825c86bc2e6e6eed53b2ec83cffac3f6c95c,2026-02-18T07:06:06.113Z,2026-02-16T07:02:42.593Z,ACME\idavis,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,a40c352351e3f1959edbea4ca25bf1032d0b86026d35cc461da1eaf6930acde6,2026-02-17T04:24:32.715Z,2026-02-20T09:33:18.546Z,ACME\idavis,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,3f37dc9176e2a2f2aee12b69f1536b7c2e69773cd606f7b29385f1e8ae57b926,2026-02-17T12:50:15.529Z,2026-02-15T01:43:18.692Z,ACME\idavis,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,5dde7ae84a3807d2864a1de6fb74d3a7cb97901109d1ed6bd3830a5eccc8f751,2026-02-14T00:16:23.810Z,2026-02-18T14:21:01.599Z,ACME\idavis,Windows Server 2022
|
||||
FIN-WS-042,10.10.1.142,C:\Users\idavis\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,e1a764ecd9ecc4dea8f35392cedc76364bb8e8c7f631e4fe56471059f466d86f,2026-02-15T08:12:07.452Z,2026-02-12T12:23:53.101Z,ACME\idavis,Windows Server 2022
|
||||
SLS-WS-043,10.10.2.143,C:\Users\hbrown\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,caf49344f41486a2f7ca444363309f97de2794f0e28519077b2c8e97a09d5747,2026-02-17T12:01:21.401Z,2026-02-10T14:03:05.915Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-043,10.10.2.143,C:\Users\hbrown\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,4b0cb099ef0e43921668ffeb7c10413ef57c89f221045d423009a2f17b930aad,2026-02-17T09:10:05.692Z,2026-02-19T00:56:23.555Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-043,10.10.2.143,C:\Users\hbrown\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,de9530ae9a96239a45653fb2c4e8c39c1422ef63d320258f1e8ec933c502fb1a,2026-02-13T03:10:39.548Z,2026-02-19T22:53:31.688Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-043,10.10.2.143,C:\Users\hbrown\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,ab3cc4f79938850d79f27c8fd131e523d1fb512299209a97855b30dfe29f0b26,2026-02-12T04:51:39.336Z,2026-02-14T22:30:22.757Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-043,10.10.2.143,C:\Users\hbrown\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,725be315101693ae4da65d9178e6c0b18e9fa2dc03c8856303030adaf6fbc12d,2026-02-14T11:23:51.453Z,2026-02-16T18:42:41.012Z,ACME\hbrown,Windows Server 2019
|
||||
SLS-WS-043,10.10.2.143,C:\Users\hbrown\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,90864179c571f1895d0ac072247d70dae30b4625b33aa39a5a75e2b2e48a9934,2026-02-16T06:59:13.911Z,2026-02-13T15:54:31.046Z,ACME\hbrown,Windows Server 2019
|
||||
ENG-WS-044,10.10.3.144,C:\Users\svc_backup\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,b7f35cf93d7ccbda6c74c571e4a54b568d749525b9f9d580b4e0472c7672ba21,2026-02-12T00:40:35.817Z,2026-02-20T14:47:37.629Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
ENG-WS-044,10.10.3.144,C:\Users\svc_backup\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,3fd6431876135c6847283dc252799c757ef7f2fb8195c22cd90386c56f814102,2026-02-19T03:43:18.491Z,2026-02-17T13:06:36.485Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
ENG-WS-044,10.10.3.144,C:\Users\svc_backup\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,0d8d2f9d3da475161c770f781e8374b86ba629751a7bfd28d053762e14a9fa0a,2026-02-15T07:45:47.271Z,2026-02-20T15:37:22.446Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
ENG-WS-044,10.10.3.144,C:\Users\svc_backup\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,f6a7a23dfd0ff30f220ea3ef043b452d767b662d972e11f542befa15a489fd20,2026-02-18T11:44:50.893Z,2026-02-18T23:28:09.534Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
ENG-WS-044,10.10.3.144,C:\Users\svc_backup\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,7f3cc0419d9f1bab44faa1a49ab31e292d5a604eac29c1e15f94c844d23a354e,2026-02-16T05:30:17.569Z,2026-02-10T21:40:58.118Z,ACME\svc_backup,Windows 10 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,8e82c9bd05ccd7a81a3b560c737042cbd9336ae03ed701845613abe0d59a299f,2026-02-13T05:29:24.014Z,2026-02-20T00:29:15.903Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,5e73d77eca2387c2afa168cf8056f28fb2dec916546b428ab50ac811f4cefa21,2026-02-11T01:10:37.466Z,2026-02-16T19:58:39.822Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,e87e043453e02569e0bba0cdef4b336d8d8a69a60d52dad3132e3990b57cedce,2026-02-16T08:46:28.014Z,2026-02-17T21:58:04.518Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,1009e17d12990f95d7fc5bb91fd9c8f0e475feb06265e6766a0e0f39b9531456,2026-02-10T15:29:09.939Z,2026-02-20T17:53:35.951Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,cdb7380059530e0c6d76a5b780034081b1513aa95de751d82e5a945d225a982f,2026-02-14T13:11:37.245Z,2026-02-19T01:32:57.232Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,406b1b9785ca1dad5468288172e147aeeadfd279d90aa8a81e94ac04ce12f63d,2026-02-11T15:39:29.933Z,2026-02-12T12:06:15.932Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,7260f94b893d531df361dd6f5c1f282642bbda812e21e3ee0375164274638b54,2026-02-15T23:15:39.620Z,2026-02-14T04:49:55.937Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
LEG-WS-045,10.10.1.145,C:\Users\cjohnson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,4eeea26c4105f7c956646ef3ec9045a88d82ae941daafd7170df268626d1c283,2026-02-10T20:28:13.454Z,2026-02-16T05:36:55.749Z,ACME\cjohnson,Windows 11 Enterprise
|
||||
MKT-WS-046,10.10.2.146,C:\Users\gwhite\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,15c8638a45bdd673d3ff7c95171a0eaf422a4cca8ded7e91f6e9dbf2d2295aba,2026-02-15T08:09:33.058Z,2026-02-17T22:57:07.760Z,ACME\gwhite,Windows Server 2022
|
||||
MKT-WS-046,10.10.2.146,C:\Users\gwhite\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,3a33ce0e2967b1d2d9905d03f55b632d97e802931bf8593e9a45d69225d57a77,2026-02-20T07:25:37.501Z,2026-02-13T04:43:40.976Z,ACME\gwhite,Windows Server 2022
|
||||
MKT-WS-046,10.10.2.146,C:\Users\gwhite\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,c21407bc7559f5ce75bcc050c946c4c4d266a4ac9d147e84a66bf174cd19645b,2026-02-18T08:52:58.045Z,2026-02-13T13:13:19.531Z,ACME\gwhite,Windows Server 2022
|
||||
EXEC-WS-047,10.10.3.147,C:\Users\svc_backup\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,8ca46a0fd1f8749083aa7f989b5fc735df042fbd8a07656dcf7e66af935b3274,2026-02-13T16:41:39.060Z,2026-02-18T17:15:07.175Z,ACME\svc_backup,Windows Server 2019
|
||||
EXEC-WS-047,10.10.3.147,C:\Users\svc_backup\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,91d608b1d94a04b68d9e9668f23fce348a333baa9e1831ea526475bf381ac83e,2026-02-12T07:19:38.065Z,2026-02-19T15:24:54.241Z,ACME\svc_backup,Windows Server 2019
|
||||
EXEC-WS-047,10.10.3.147,C:\Users\svc_backup\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,7b243e08bcb74f6345e753c91bf0f66e96fc194f1b4ec87b00c491cc16b3325f,2026-02-16T05:52:30.816Z,2026-02-12T03:50:42.145Z,ACME\svc_backup,Windows Server 2019
|
||||
EXEC-WS-047,10.10.3.147,C:\Users\svc_backup\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,22d31ee74d58df4963f98cda8110c4c8209e26433e3940f1db0a59d6e3260638,2026-02-12T23:31:17.286Z,2026-02-15T22:26:40.326Z,ACME\svc_backup,Windows Server 2019
|
||||
EXEC-WS-047,10.10.3.147,C:\Users\svc_backup\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,4bdaf635205a763f2327146524cc008105891b14a3c8311fe8e46fbbc66443e0,2026-02-16T12:09:02.109Z,2026-02-15T00:20:32.421Z,ACME\svc_backup,Windows Server 2019
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,15d008e3f556e820f7f18bdf44d4bd68c8c6ebc7aa234acce6c2e9db13bbaa48,2026-02-17T15:29:47.826Z,2026-02-19T03:16:38.437Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,4854aa44d8e59d34e4011dee8a4f6b826566c8f09ad2b3517b41fcaabe53ba44,2026-02-13T22:28:53.872Z,2026-02-14T19:43:15.171Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,0561c5e25e2a4e394a00c4d04ace0e58fcb82370f757818acdc29a97f1315230,2026-02-10T22:58:17.984Z,2026-02-13T04:40:38.646Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,ad56dc8bd5f4e3a7244942969d14a5ed007564afd8234bc41b749a9d6ef94625,2026-02-14T01:28:03.196Z,2026-02-11T05:01:23.410Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,f45a8ddb81dcea19037d3e99421bade1397085f15afda9d8fd3d79e2509625c0,2026-02-13T22:23:36.095Z,2026-02-16T19:37:27.832Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,b11beeeb39c2bbede4e6a3fc15062c3883c71a7b4ebc880f8e860e2565bec245,2026-02-17T23:56:29.235Z,2026-02-16T15:53:44.965Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,51f6861479b42e6e1f286a6b360e65d6ab295b3a9d38b17440edfcfc4ef75bbf,2026-02-15T15:04:02.380Z,2026-02-20T08:26:44.056Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-048,10.10.1.148,C:\Users\svc_web\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,7210e00d9ff6868307df0eeb3788de0b2ae9ff946f111368b46d42da243514d1,2026-02-10T20:28:38.011Z,2026-02-16T05:11:46.785Z,ACME\svc_web,Windows 10 Enterprise
|
||||
HR-WS-049,10.10.2.149,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,591763af5cfc6bec51fdf942dcb5bba11a1881f7f4d8e09ec57b95b8868c8ad7,2026-02-19T12:07:32.972Z,2026-02-11T17:02:36.653Z,ACME\svc_web,Windows 11 Enterprise
|
||||
HR-WS-049,10.10.2.149,C:\Users\svc_web\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,e94f7dfbc335b2f4bffd2a908afba84b2e8ff31ca1f556da6239a01ddd487f19,2026-02-15T07:04:52.369Z,2026-02-20T01:34:51.495Z,ACME\svc_web,Windows 11 Enterprise
|
||||
HR-WS-049,10.10.2.149,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,41f1e49f2b8afbb450447b5a11ce7c322467db266383e67382050465390585e7,2026-02-17T10:22:35.991Z,2026-02-15T16:58:14.855Z,ACME\svc_web,Windows 11 Enterprise
|
||||
HR-WS-049,10.10.2.149,C:\Users\svc_web\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,da4382304451637b3a9eea68afadcfa74d975af4db4a9bfddb477e38d4246d3f,2026-02-13T17:53:29.182Z,2026-02-14T15:11:33.519Z,ACME\svc_web,Windows 11 Enterprise
|
||||
HR-WS-049,10.10.2.149,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,93fea86c2009fb57c3b01dbe58e63b5cdfd43470a41a160fe36b02921027be54,2026-02-20T16:27:37.178Z,2026-02-16T16:57:25.343Z,ACME\svc_web,Windows 11 Enterprise
|
||||
FIN-WS-050,10.10.3.150,C:\Users\hbrown\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,a24d40cd3df2bf6f7bbc3aa5cab22829a3ac957dc5a1454ca338dbdf2def6bf5,2026-02-17T07:03:33.301Z,2026-02-10T14:26:13.260Z,ACME\hbrown,Windows Server 2022
|
||||
FIN-WS-050,10.10.3.150,C:\Users\hbrown\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,8b6213801b49ffee0f1e75f3dbb6669c40a61fa39db745127f72082e80e047ad,2026-02-14T10:59:49.346Z,2026-02-15T16:23:02.229Z,ACME\hbrown,Windows Server 2022
|
||||
FIN-WS-050,10.10.3.150,C:\Users\hbrown\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,3d909be6cfcd977f62b7e5e27e340c068be43e11e6ea02998e789d8483bf1699,2026-02-10T15:00:35.773Z,2026-02-17T16:43:42.684Z,ACME\hbrown,Windows Server 2022
|
||||
SLS-WS-051,10.10.1.151,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,dfbd05289c931ca09ece378fc352ca7cef2e942f0bc272243cb82ec81cdfcb3c,2026-02-14T16:36:33.359Z,2026-02-16T08:13:53.158Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-051,10.10.1.151,C:\Users\bwilson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,5b11223971d332a81eab4a5d19388086710d772a6df25ce155a5ed027d0b84ad,2026-02-14T02:42:22.927Z,2026-02-12T01:38:45.881Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-051,10.10.1.151,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,28f4b935f30de5efca6703ccd92960182250cb7bf4a5c748d99b9a42895b53a0,2026-02-13T11:50:44.278Z,2026-02-13T15:17:01.703Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-051,10.10.1.151,C:\Users\bwilson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,5724811fa9ef2397a6048874f621bc1e75ff9050e4901271175fd87ba83cc306,2026-02-19T03:11:32.086Z,2026-02-10T22:27:32.373Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-051,10.10.1.151,C:\Users\bwilson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,3ee51ef26fad03eb6cc0492b70726fca488209929a82018a87896782187d1588,2026-02-12T01:16:52.307Z,2026-02-14T04:23:02.713Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-051,10.10.1.151,C:\Users\bwilson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,cdb40d12da5dd8ccda9c391aeca91ce3b7a62015d9e0de8e8b244ed5a22c0814,2026-02-13T21:31:26.399Z,2026-02-12T12:21:12.074Z,ACME\bwilson,Windows Server 2019
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,20f04fa1378f3605b1f05eb21a5b39947107f5bc0451fd8fd14b1261eded33d7,2026-02-16T05:59:03.718Z,2026-02-11T01:10:17.575Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,bc41d47c0e020f38f171c4e7bb05d6a36f82cce5795dbefb9d889f1ab8a98092,2026-02-11T18:58:26.405Z,2026-02-19T03:59:09.719Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,f3a9bb726eb6764a7d6979caab7cc1f976d2294c14dc58ab65a77b624885517f,2026-02-15T15:33:14.983Z,2026-02-15T21:28:39.909Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,8dfcfd541f02f8743a7fba9a21ab9c11017843174e9fe31557561e19154084d5,2026-02-17T13:03:01.811Z,2026-02-11T04:11:46.132Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,b07700109e195c26035923a978783dffc552bd564d5441c05c431d11862f268c,2026-02-12T00:47:41.334Z,2026-02-18T05:36:57.669Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,4fb0ef847ff8dc1b3853694111af672c1e1666b68dad3e2b2aa0aeca16a169bd,2026-02-17T10:26:40.881Z,2026-02-18T23:08:43.836Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,4274dd6afe5faa8353b2a849de4bc43d17e394d8724db0fef46e692977ab3db5,2026-02-18T09:06:30.024Z,2026-02-11T00:09:19.656Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-052,10.10.2.152,C:\Users\bwilson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,2f0e1a60ae27dec2f23616d01400e717f93911c9eb34e6db99459ce4281944dd,2026-02-18T14:50:37.907Z,2026-02-17T11:14:29.469Z,ACME\bwilson,Windows 10 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,5350bd0171d8593cf11cf439d51d9ec22db8f4959b995fbfc80c5d410e454286,2026-02-16T08:45:23.209Z,2026-02-13T04:40:16.458Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,e2dbd309766a659ae2850e73eaf59694cc7992ee70860a03a1bdb6bb6830f188,2026-02-17T20:13:47.658Z,2026-02-19T03:12:54.071Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,5cd2dcf247781d5859f7d96b94c9274128ce20ae78eb45e479dba478011b9e10,2026-02-13T05:48:54.994Z,2026-02-15T16:00:26.197Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,9365ffa4a021c0873ea17f94206df851dd699d574029ac0ad37d7f42f9c36335,2026-02-19T10:33:10.631Z,2026-02-19T15:23:55.632Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,058d8d26619cc43ae1951ddb9c8c7dbef56e24e6cc3e9c9a12e775cb7a304b51,2026-02-12T01:34:05.741Z,2026-02-15T18:54:22.756Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,aa8b539ee3686667944d7af5d60400dffde395a011c0882adaa0e36c5b0d22a3,2026-02-11T08:54:43.639Z,2026-02-10T19:17:25.296Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,e82d92fba2a1f8450d6ce8d1765203a5f353e5f0afb5cfdca5e2ada2ae8ce974,2026-02-14T11:40:29.934Z,2026-02-19T09:41:07.164Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
LEG-WS-053,10.10.3.153,C:\Users\svc_backup\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,da28550a9beaf9f77c96e71311967b6f21a308f9d89131594d78837debf20d6d,2026-02-17T13:02:43.678Z,2026-02-11T21:11:03.662Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
MKT-WS-054,10.10.1.154,C:\Users\bwilson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,980d5af4c6cbeb4d7daeb5f688b25c1282ae1e69260b3592e64fedd3cbbb7797,2026-02-11T06:19:47.914Z,2026-02-12T20:32:38.682Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-054,10.10.1.154,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,3617f5180f5224f1cfa5c2ed5d898e106613182bf1585c883a5722bb4f56fc1b,2026-02-14T13:55:55.352Z,2026-02-11T09:16:10.078Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-054,10.10.1.154,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,7b0e1576e1418253a6ac7a1d82b152bd6fedffbfd02a9256af3a1b3cb5a40537,2026-02-12T08:36:19.447Z,2026-02-14T01:11:58.780Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-054,10.10.1.154,C:\Users\bwilson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,07dffb3558eb2d02d7ad1109a4ca9fcc29c690153e3cc0d513171e5fe1c15b01,2026-02-13T06:00:43.915Z,2026-02-10T16:44:07.885Z,ACME\bwilson,Windows Server 2022
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,3af6a07345316a9f73f1424324c3374b8b30226c90068c6325d22f90b0add79f,2026-02-16T21:08:26.488Z,2026-02-17T21:25:46.277Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,782dc134b0b8e55b412d780c0b3dfcc5d0998f312f7f319d57ba8e39e06cd8e8,2026-02-15T04:58:46.698Z,2026-02-11T14:37:55.486Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,346cc8f38df4a795b8c4497f00cf3bb190020ef0f2d6004040e1056ab5549a5e,2026-02-12T20:41:06.567Z,2026-02-11T09:58:00.895Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,9ed3358e394cad72a925efaca08c9ac362e6dd66e9484aa8b8aa6b0980f828c7,2026-02-14T22:27:43.775Z,2026-02-18T22:29:08.888Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,5350debd35e21079ef38f1d3900fce18a5995cdce538c093e74436ca471e09a4,2026-02-14T08:11:54.017Z,2026-02-16T20:51:46.162Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,9c76320c13a88cf7a63ada8166f90ff4a28ba05a393e9f8f6cab48a33126cc0c,2026-02-16T13:01:01.897Z,2026-02-20T17:00:09.009Z,ACME\idavis,Windows Server 2019
|
||||
EXEC-WS-055,10.10.2.155,C:\Users\idavis\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,3f69af120a3550fed16c34ee4d69d25a84255b740193aace6b16f686d67354b6,2026-02-14T00:21:13.260Z,2026-02-16T14:01:10.433Z,ACME\idavis,Windows Server 2019
|
||||
IT-WS-056,10.10.3.156,C:\Users\hbrown\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,f3f34da2997d55930fb150e4dad2841cee5cf927258bb49305a84b5be9bb4665,2026-02-12T18:19:04.587Z,2026-02-13T12:13:55.926Z,ACME\hbrown,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,C:\Users\hbrown\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,d0687e11d73598ae20792317456e89268e739c08c5b066acc5fc056669cef515,2026-02-12T02:06:08.156Z,2026-02-10T15:27:58.479Z,ACME\hbrown,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,C:\Users\hbrown\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,f19c851bdb7469905233ff38c0eb32f4d026777aefd568da9a80c85ea898e3e8,2026-02-10T08:03:43.735Z,2026-02-11T09:29:48.806Z,ACME\hbrown,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,C:\Users\hbrown\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,6209fecb584b3b6528c63e84fc646e22fcae7266378ef4eca3b5e40a696e8935,2026-02-11T03:10:15.588Z,2026-02-15T13:55:52.986Z,ACME\hbrown,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,C:\Users\hbrown\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,7c5adcd92162fa1f9a636949604eee9a9f1b76be1cb81b76fc5d88ed85e8c7d5,2026-02-17T16:49:16.504Z,2026-02-16T23:27:24.169Z,ACME\hbrown,Windows 10 Enterprise
|
||||
IT-WS-056,10.10.3.156,C:\Users\hbrown\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,433891d26a5a2f317d9b3ffc2b0152d710e3b0c4ce79dafdd85b9417bbb01962,2026-02-15T23:49:46.158Z,2026-02-18T13:41:28.915Z,ACME\hbrown,Windows 10 Enterprise
|
||||
HR-WS-057,10.10.1.157,C:\Users\svc_backup\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,159b1af58930df3d714818f7d1d26a8d0236aa9ae67072e4dc568efc6a239104,2026-02-11T05:36:19.999Z,2026-02-20T10:39:43.392Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
HR-WS-057,10.10.1.157,C:\Users\svc_backup\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,7c360af0f5933020a07c1d6eed650fd0f714c4e10e4814beec5bce187c7b0ac7,2026-02-19T13:20:38.501Z,2026-02-15T10:09:00.787Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
HR-WS-057,10.10.1.157,C:\Users\svc_backup\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,838982523731ed5c84539a743366f47f964d269c73bdd177df98706d8d90f174,2026-02-10T18:42:01.615Z,2026-02-15T03:31:22.686Z,ACME\svc_backup,Windows 11 Enterprise
|
||||
FIN-WS-058,10.10.2.158,C:\Users\svc_sql\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,fe7a2c2436e339959bc87cf30b907283b3469ebaf99d7cb4daa020f6a5fbdaa4,2026-02-16T06:35:42.030Z,2026-02-10T22:28:55.148Z,ACME\svc_sql,Windows Server 2022
|
||||
FIN-WS-058,10.10.2.158,C:\Users\svc_sql\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,d9b86651a70c83507d04c49b5fa690c6377c08ca36623cf5732c533b24048313,2026-02-14T16:20:05.890Z,2026-02-18T11:12:28.150Z,ACME\svc_sql,Windows Server 2022
|
||||
FIN-WS-058,10.10.2.158,C:\Users\svc_sql\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,104df32fcf74e7c765528724efe84ab9158695b0a5cd4a86ce05bd99385e91ba,2026-02-11T10:34:18.172Z,2026-02-10T12:10:06.417Z,ACME\svc_sql,Windows Server 2022
|
||||
FIN-WS-058,10.10.2.158,C:\Users\svc_sql\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,c796bdee0bb2b195dd9244dfebc01776627ec5f41f5cb687786d2bee6acacf6a,2026-02-16T09:53:53.186Z,2026-02-20T08:37:42.204Z,ACME\svc_sql,Windows Server 2022
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,6b89e7933f24098acaece345ae94dfde12d89fff310dd789f97ce69270d46eab,2026-02-18T01:35:39.439Z,2026-02-13T02:42:05.599Z,ACME\svc_backup,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,b62da658171c41525451fa9c18528e81c688c7f1997395a51bf39e93f57924e9,2026-02-19T16:05:28.770Z,2026-02-10T14:41:12.435Z,ACME\svc_backup,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,fb5b8f47dda60b1afda78c8c8ff07dec9380ea6e9b521bda6145d6ac6659d3ed,2026-02-14T06:47:04.073Z,2026-02-16T21:30:46.778Z,ACME\svc_backup,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,954380e542e548b7824fcb25fd3c18afb90c03c519a08339452170195a09c47c,2026-02-20T02:57:58.740Z,2026-02-16T03:04:39.739Z,ACME\svc_backup,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,5a4cb370b4ced46ebb17a5443b3db72cf056730a8a0c8aadf454c94f48b9d172,2026-02-14T19:10:46.875Z,2026-02-10T12:12:23.136Z,ACME\svc_backup,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,b8eaa121430f3c485fb8a684516fc7b3cd7b9c39ab2e526456fecd043a939543,2026-02-11T08:02:07.399Z,2026-02-14T06:47:25.094Z,ACME\svc_backup,Windows Server 2019
|
||||
SLS-WS-059,10.10.3.159,C:\Users\svc_backup\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,9a9c9bbc284bc127d351e3b9432bbbd44afbf872a6003e1a30a5aa79482284a7,2026-02-18T10:41:02.695Z,2026-02-16T20:30:42.029Z,ACME\svc_backup,Windows Server 2019
|
||||
ENG-WS-060,10.10.1.160,C:\Users\bwilson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,905845af52b2628c5f8726416dd272ee873dbe70e10ad2aa5e33f30eb9eaffec,2026-02-16T00:33:47.103Z,2026-02-18T09:46:51.688Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-060,10.10.1.160,C:\Users\bwilson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,68d38118d5daa87938602ba745b1bf2f7125419e7c7aaf0ae2cfbef4878b7f8d,2026-02-10T22:44:43.401Z,2026-02-17T17:55:07.034Z,ACME\bwilson,Windows 10 Enterprise
|
||||
ENG-WS-060,10.10.1.160,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,e4f8142754ff57407b8db15b9286f2f1450cbaf26b94e5caa798f297579b4899,2026-02-20T16:26:38.001Z,2026-02-14T15:29:18.592Z,ACME\bwilson,Windows 10 Enterprise
|
||||
LEG-WS-061,10.10.2.161,C:\Users\svc_sql\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,13ffc77c4d2b78c21e6d11207046fcc59271ae77fc1cd9859f26133f7d91f42d,2026-02-18T06:11:22.025Z,2026-02-18T16:02:03.866Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,C:\Users\svc_sql\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,29135c61c8b18b6370224fc250ed146ed968bfd5b367a2512c72c8cdb4f6f0ad,2026-02-13T09:07:54.810Z,2026-02-14T07:35:05.127Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,C:\Users\svc_sql\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,5a3dd12286335b7ff05bc1060aa2564b9108c99f88efe83c95582a9a30b35ec9,2026-02-19T21:59:53.396Z,2026-02-11T19:18:19.845Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,C:\Users\svc_sql\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,a5073ae78724059f1284d5f92dbc68db807d213a2471f16027963320d1f17f4e,2026-02-17T09:03:44.966Z,2026-02-20T17:50:40.944Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,C:\Users\svc_sql\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,6b90f7a64f916de8c91e98edcff21af430ee6afc954f5be53aed088c5581f910,2026-02-11T01:06:46.349Z,2026-02-15T22:35:57.810Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
LEG-WS-061,10.10.2.161,C:\Users\svc_sql\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,561f56f4c0c6994be45a4a9eab5a3d9c81d99553dd851e077b079d64e1b2c58e,2026-02-12T11:30:53.610Z,2026-02-11T00:37:33.548Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,a120ab7df76d7c44788eab5cbb1350b9d03206cf913fca72efe9f6afe51ad123,2026-02-11T09:37:08.530Z,2026-02-16T01:55:38.041Z,ACME\cjohnson,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,23d4e4171da879a726272c724ff0b813ac4715e66b2de06a06c35c9b0e3d3f74,2026-02-11T03:45:04.490Z,2026-02-12T22:34:50.380Z,ACME\cjohnson,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,78c62aa32adb30aa10c99a48a654a17d6ba2565052694fee609faa7ec48f63b0,2026-02-19T22:53:33.538Z,2026-02-18T04:43:04.236Z,ACME\cjohnson,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,cb15d61ce1c4fb56b82d8e1053d916011a5b9c33495392bcc7e0d4e783f37a08,2026-02-13T12:56:03.541Z,2026-02-16T22:39:08.082Z,ACME\cjohnson,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,d8b3929b63b82645d1e98619510bc375a0c67d79ac3851a53498bf1776857831,2026-02-16T20:56:38.831Z,2026-02-11T10:23:30.242Z,ACME\cjohnson,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,8486dcd804924cf07c9a95fb6b8f68e762267cd5516a642792a21ac08f1fb4d6,2026-02-15T21:01:29.305Z,2026-02-20T11:24:38.437Z,ACME\cjohnson,Windows Server 2022
|
||||
MKT-WS-062,10.10.3.162,C:\Users\cjohnson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,6fecf8734c6b547d074936e151d177c5ed93d08df36d872a032fa7c736adf135,2026-02-14T10:27:35.421Z,2026-02-18T00:02:57.434Z,ACME\cjohnson,Windows Server 2022
|
||||
EXEC-WS-063,10.10.1.163,C:\Users\jsmith\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,ebb5c7a8ecd668cc7ff2b1f9036ce5dbf823db9c50e1955111857cf71270f381,2026-02-11T15:45:16.625Z,2026-02-13T09:27:03.334Z,ACME\jsmith,Windows Server 2019
|
||||
EXEC-WS-063,10.10.1.163,C:\Users\jsmith\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,e55cdc89d11cf798a158574c213e1752eb017b02c4a6358a5c22bc7d492f0d9c,2026-02-13T16:05:32.443Z,2026-02-18T09:49:17.263Z,ACME\jsmith,Windows Server 2019
|
||||
EXEC-WS-063,10.10.1.163,C:\Users\jsmith\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,4588f93c0a4a3799e338b88071e9810f9f63e5ba45d32d468f06ce065fa83cad,2026-02-13T03:05:28.663Z,2026-02-17T17:31:52.069Z,ACME\jsmith,Windows Server 2019
|
||||
EXEC-WS-063,10.10.1.163,C:\Users\jsmith\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,5e2830be8d0af5ead2be0cc8cb478869d8b783df064e67577d2752ca55cd05b4,2026-02-12T06:55:43.373Z,2026-02-10T17:25:45.072Z,ACME\jsmith,Windows Server 2019
|
||||
EXEC-WS-063,10.10.1.163,C:\Users\jsmith\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,35e3cec5ecec093d2bbc50dfe4f7f7a8cfd46cc87cad68a2f7dc948440aab6b7,2026-02-10T09:13:59.971Z,2026-02-18T04:28:34.148Z,ACME\jsmith,Windows Server 2019
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,cd5493a1043b4ffb4df3685b46eed615d8662daa79f7a2d02725df040f396b99,2026-02-14T01:25:35.401Z,2026-02-10T15:43:08.147Z,ACME\admin,Windows 10 Enterprise
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,e7319591e20ccfa6586ad0027c25f045334eb150c548c1f43f9854b137d65e2f,2026-02-13T06:53:42.529Z,2026-02-20T00:14:58.202Z,ACME\admin,Windows 10 Enterprise
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,3ebe1653ca6b86b5d791c233e3d5623c1764fafed1b2a1c2a6ced99bb9d67c26,2026-02-18T06:49:49.434Z,2026-02-19T22:37:13.780Z,ACME\admin,Windows 10 Enterprise
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,265343b80173d0c44090e573862e9ad56460cdc3ef0b39402fe0bab449d1e6aa,2026-02-15T14:09:22.053Z,2026-02-16T01:06:10.601Z,ACME\admin,Windows 10 Enterprise
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,904ef6e441709467cf35c634dbca48d1c63f4e2663af34c8c620ea0d255545eb,2026-02-18T18:18:17.617Z,2026-02-14T10:27:13.566Z,ACME\admin,Windows 10 Enterprise
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,c9eb1b9e7bcdf99e7807971a28ee14fc48a75fabb3b80d4cabc2c98c3ba023d2,2026-02-17T12:13:43.373Z,2026-02-12T13:15:59.541Z,ACME\admin,Windows 10 Enterprise
|
||||
IT-WS-064,10.10.2.164,C:\Users\admin\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,53948e33f1bce74320288297cd428cf43f7fe9ac2581289624c2fe1490fb1840,2026-02-18T09:38:15.985Z,2026-02-15T21:18:44.402Z,ACME\admin,Windows 10 Enterprise
|
||||
HR-WS-065,10.10.3.165,C:\Users\svc_sql\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,da6c7298b42163beca4bba0c240a8fa5d3f5578397fd2f51e34a9adcc5469269,2026-02-12T04:23:52.500Z,2026-02-16T23:58:39.393Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
HR-WS-065,10.10.3.165,C:\Users\svc_sql\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,3cae1022a0c930179b7f99bb003c03bda181df2694bfb42d6e589c99cbad73e6,2026-02-10T11:07:23.690Z,2026-02-12T06:31:34.874Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
HR-WS-065,10.10.3.165,C:\Users\svc_sql\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,7e81df3a05666b1ef5b0c95af3275fb209eec0b10a1e6f42fd50b32c9d2a579e,2026-02-16T15:04:42.252Z,2026-02-18T02:34:40.510Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
HR-WS-065,10.10.3.165,C:\Users\svc_sql\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,d6a665d33e9ef3954b6373434f0c80363cf4e458b90ed00991b789c3022c691d,2026-02-14T20:38:17.336Z,2026-02-12T00:57:22.446Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
HR-WS-065,10.10.3.165,C:\Users\svc_sql\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,7984444d1ea4788e1acd5c2a836f19f9b8fd288553a32ed3ab20078d4adae0ce,2026-02-16T02:09:06.353Z,2026-02-11T06:08:07.650Z,ACME\svc_sql,Windows 11 Enterprise
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,31cebee1c461110ebdd28cf66763a00fd56ab84d9596a973bd377a820e54bf2b,2026-02-12T03:27:13.153Z,2026-02-19T06:41:18.841Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,e4e158e8a008145a6be6fdf8c6ead22ae721d87b1776f30d5dd8a169870e363c,2026-02-12T06:51:19.449Z,2026-02-20T00:35:11.198Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,79cf9ce9c3e354d19518c5eda680299d8f023e9bd94f67af94d38feb99b8a038,2026-02-12T05:26:48.392Z,2026-02-15T04:55:42.352Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\salary_comparison.pdf,salary_comparison.pdf,320000,d2e0f1a3b4c5d6e7f8a9b0c1d2e3f4a5,0a4f748d49b234013155ae1ab8f5466a574341149e91cf37d977a5c471be4463,2026-02-14T09:05:34.293Z,2026-02-18T11:28:18.191Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,1e3af3cc86f94fc448501e00bcd272798fff0dce412d505d6235414b1f04c6f0,2026-02-12T19:26:34.776Z,2026-02-14T03:53:56.129Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,8e7001fe08fe15052a04585a55b2108920cee468c4296bb2ff5f4f2c516c3582,2026-02-17T10:38:52.019Z,2026-02-14T02:49:50.727Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,1814dcf2fa95a115bb8e0c5c957c9f84ab6f6749307168119b780e89f3f21186,2026-02-19T14:14:48.533Z,2026-02-17T13:12:41.994Z,ACME\cjohnson,Windows Server 2022
|
||||
FIN-WS-066,10.10.1.166,C:\Users\cjohnson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,f1d81fe35da8a7949fb9e7ea79199df7d74055daa4db4847318511a1eefd25de,2026-02-16T21:21:21.291Z,2026-02-15T12:50:50.170Z,ACME\cjohnson,Windows Server 2022
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,3c05306f88b0dff6f95c7dc3449987bc49bcdb9dfed9e97c2ec0395c66e5a5cd,2026-02-16T10:31:12.435Z,2026-02-17T23:04:17.736Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,4489e51c0cfb9187ab0fdbca080e0bd749a973f50efbf3cff96b993c0fdee23e,2026-02-12T23:14:11.601Z,2026-02-17T14:27:53.486Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,dde3491766c1b33932173b86534a46ee4c4711a7f088adeb4c068140343e909a,2026-02-13T23:55:59.509Z,2026-02-11T04:27:57.242Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,6a90bba572c051df728d448472035a1dd150003caa64038459e168ba59eebea9,2026-02-14T10:27:43.804Z,2026-02-20T01:24:42.718Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,e9fcf345bbd5ef8f80a6360555367c0504abbe1a43222081aeed9f56588bf269,2026-02-18T11:20:46.299Z,2026-02-17T14:55:17.274Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,21c98853d03c890e769fce2a7c794485ae2079670986f9618e3c62bfbc9ecacd,2026-02-10T21:42:37.190Z,2026-02-19T02:50:04.190Z,ACME\bwilson,Windows Server 2019
|
||||
SLS-WS-067,10.10.2.167,C:\Users\bwilson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,3f3c4d9db26b52f7ed6bc0e5a3f10a427b247e558ad65b9cce58a2ef9443f225,2026-02-18T22:05:31.921Z,2026-02-11T00:56:01.038Z,ACME\bwilson,Windows Server 2019
|
||||
ENG-WS-068,10.10.3.168,C:\Users\agarcia\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,ec01e92cadd0b99a66a2518cacad11cc0eef851342555af4cd28a72712d46066,2026-02-10T23:43:17.802Z,2026-02-19T14:30:29.217Z,ACME\agarcia,Windows 10 Enterprise
|
||||
ENG-WS-068,10.10.3.168,C:\Users\agarcia\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,771bc6f9562fbbd805d4d1199bffb7eb0e3b71a4f8fa8ff95f401cb56eaea40a,2026-02-16T22:18:55.736Z,2026-02-10T10:48:05.618Z,ACME\agarcia,Windows 10 Enterprise
|
||||
ENG-WS-068,10.10.3.168,C:\Users\agarcia\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,e3616b6ee97f9c422914e4437d9c7b9116535fb998704b11053cafbef8f5caf0,2026-02-15T10:08:21.739Z,2026-02-17T09:30:52.090Z,ACME\agarcia,Windows 10 Enterprise
|
||||
ENG-WS-068,10.10.3.168,C:\Users\agarcia\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,d46b4ded8da01716bf12c5c1c420af002fb4e12e4e504b1c27f2d0f97fcb05e9,2026-02-16T08:39:49.256Z,2026-02-17T12:39:27.283Z,ACME\agarcia,Windows 10 Enterprise
|
||||
ENG-WS-068,10.10.3.168,C:\Users\agarcia\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,f774be2460f78fe3038b0c36b049ff0c41796d3f98ced16f70ea921857aae059,2026-02-16T02:03:37.545Z,2026-02-19T00:01:17.162Z,ACME\agarcia,Windows 10 Enterprise
|
||||
LEG-WS-069,10.10.1.169,C:\Users\agarcia\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,2e9b2726c3dc07fab60f52a53164ea88be9ac9c51ca3cb2448bc674d33b57e5b,2026-02-19T16:21:41.161Z,2026-02-18T19:48:45.684Z,ACME\agarcia,Windows 11 Enterprise
|
||||
LEG-WS-069,10.10.1.169,C:\Users\agarcia\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,a6443d45766f4f45a04231e4efee599cde37fd581b2f20702092d0aa5dc4c008,2026-02-10T22:40:34.314Z,2026-02-19T23:32:14.355Z,ACME\agarcia,Windows 11 Enterprise
|
||||
LEG-WS-069,10.10.1.169,C:\Users\agarcia\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,8294d41139ac9064f4d340a4a07a4354bc5d543fa05b3c11689640b19af9039d,2026-02-13T14:07:02.261Z,2026-02-15T12:03:43.320Z,ACME\agarcia,Windows 11 Enterprise
|
||||
LEG-WS-069,10.10.1.169,C:\Users\agarcia\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,3943c17b7c3ea9f4271005397a3250e46dc9b9916b5e867872ea58ad67b73db0,2026-02-14T09:22:52.740Z,2026-02-18T08:09:32.149Z,ACME\agarcia,Windows 11 Enterprise
|
||||
MKT-WS-070,10.10.2.170,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,e7faa7c14424a33689ca8ef85e3703c971e21d3448ff4f623d490f98f1242c73,2026-02-12T01:52:38.277Z,2026-02-14T17:11:47.556Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-070,10.10.2.170,C:\Users\bwilson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,0b6da4421540a109a644d4d47ae254a716c786f46d2397d37df4467926a1bda7,2026-02-13T16:01:33.768Z,2026-02-14T06:37:16.953Z,ACME\bwilson,Windows Server 2022
|
||||
MKT-WS-070,10.10.2.170,C:\Users\bwilson\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,0636c7ed5a09944a4446220da010ea788c98cd9e4d6a9ddfc0b60e1fe52c72e2,2026-02-14T22:47:08.689Z,2026-02-12T16:57:54.421Z,ACME\bwilson,Windows Server 2022
|
||||
EXEC-WS-071,10.10.3.171,C:\Users\gwhite\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,23338d5308f157d1122030a212ace72df6c79c3ae165aeca93e16d63ec2f50de,2026-02-14T13:17:49.661Z,2026-02-10T12:24:57.779Z,ACME\gwhite,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,C:\Users\gwhite\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,70f6351a55168625dd99c11e171fdec2674503cdcf99fea97d2debf59630fd85,2026-02-17T05:32:40.045Z,2026-02-19T09:17:43.290Z,ACME\gwhite,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,C:\Users\gwhite\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,1c02887dcea5184a2a63641f595642bdb3b4c14f034fea4e0b2955c6aa1c6119,2026-02-17T04:22:22.064Z,2026-02-13T11:40:16.954Z,ACME\gwhite,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,C:\Users\gwhite\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,f310c638facffd4bdc95b44c650efb600fba5ee3e54f216657a87cdfb0b80532,2026-02-20T17:15:22.759Z,2026-02-17T19:54:04.463Z,ACME\gwhite,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,C:\Users\gwhite\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,2085fe98df0b6834d73b5207d21362268b536dd055a44e5e41618656f7455feb,2026-02-13T05:30:07.408Z,2026-02-16T17:31:27.254Z,ACME\gwhite,Windows Server 2019
|
||||
EXEC-WS-071,10.10.3.171,C:\Users\gwhite\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,fbd1d29f0548f3981cfbcd274ad5e4d825c7862900f62bafbbf718762aa2fb79,2026-02-18T12:59:22.361Z,2026-02-14T11:48:18.756Z,ACME\gwhite,Windows Server 2019
|
||||
IT-WS-072,10.10.1.172,C:\Users\svc_web\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,7692f56a4c44a44e773171dd3ec34024e7d573448f6aa42625f4d3bcd240c39f,2026-02-11T13:37:45.919Z,2026-02-15T12:15:23.430Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-072,10.10.1.172,C:\Users\svc_web\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,03befafc774c352818b391154cf66b6199af4f9eb9e7b5606c77cd36b71facd0,2026-02-20T03:07:57.263Z,2026-02-18T11:06:22.558Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-072,10.10.1.172,C:\Users\svc_web\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,73ac3d2a43c5d87fb74522b354f363875666172460d0e020ac71fa37a6867680,2026-02-13T06:07:58.308Z,2026-02-18T18:30:24.361Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-072,10.10.1.172,C:\Users\svc_web\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,f411eb96a17ea6cb558eba53e545c28f8d784fae2a12aec2b65caef0516c8176,2026-02-18T20:09:22.652Z,2026-02-12T14:24:22.345Z,ACME\svc_web,Windows 10 Enterprise
|
||||
IT-WS-072,10.10.1.172,C:\Users\svc_web\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,e114540379a736d14a5e1f0f82e5fb8e183e0945ae683512418f51e9a9b79346,2026-02-14T14:58:20.198Z,2026-02-12T11:00:24.118Z,ACME\svc_web,Windows 10 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\setup.exe,setup.exe,45000000,e7f5a6b8c9d0e1f2a3b4c5d6e7f8a9b0,3dc6f4e093be361fa151f14bc6077a34c1cea286c15d1a09b22123b6724d419c,2026-02-16T05:59:15.393Z,2026-02-12T00:55:01.385Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\free_movie_2026.torrent,free_movie_2026.torrent,45000,c1d9e0f2a3b4c5d6e7f8a9b0c1d2e3f4,3f0655a01a6b3429b7eb9f1918439cdbd987a0ba4e06479224f64b4e45b01d89,2026-02-14T12:28:20.557Z,2026-02-19T18:11:59.703Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,3fff89118c1a6e529daaebef99c05bd22ecbd04ddcfaa4285ab8184aa25a0494,2026-02-12T01:35:32.044Z,2026-02-18T16:31:31.245Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,f4e11e91d7318d5bc4aef4378611168bb912e9e17f18758da3e94e5d644bd365,2026-02-20T03:28:49.859Z,2026-02-10T10:07:26.992Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\project_plan.pptx,project_plan.pptx,1500000,d6e4f5a7b8c9d0e1f2a3b4c5d6e7f8a9,17faa693ce07c7fa04593fa3959248272361c37e0142e7fe6e91792d0585c177,2026-02-15T22:22:13.349Z,2026-02-15T20:00:23.982Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,fe90a7fc7f462d69d872741de59563f1dcef93a6c99b1b932b4f6b271aaeed2c,2026-02-14T04:11:17.266Z,2026-02-13T23:04:10.054Z,ACME\idavis,Windows 11 Enterprise
|
||||
HR-WS-073,10.10.2.173,C:\Users\idavis\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,94ed3d3bd229ac0657630683527033c8f9fb4a6cdf30a893481f77e4cfb51512,2026-02-13T15:36:48.654Z,2026-02-12T01:59:22.645Z,ACME\idavis,Windows 11 Enterprise
|
||||
FIN-WS-074,10.10.3.174,C:\Users\bwilson\Downloads\meeting_notes.docx,meeting_notes.docx,89000,b4c2d3e5f6a7b8c9d0e1f2a3b4c5d6e7,ce549ff35440924c40c190f8c1b16ead4dc2b558b0c218442535296f4474d785,2026-02-16T10:52:24.259Z,2026-02-10T11:23:13.028Z,ACME\bwilson,Windows Server 2022
|
||||
FIN-WS-074,10.10.3.174,C:\Users\bwilson\Downloads\steam_installer.exe,steam_installer.exe,3500000,b0c8d9e1f2a3b4c5d6e7f8a9b0c1d2e3,0dbf41aa5ff9c756104db0403f9e88cb6fde5f2582bae1da88fe485f50c03ab6,2026-02-11T19:39:06.741Z,2026-02-14T14:19:50.933Z,ACME\bwilson,Windows Server 2022
|
||||
FIN-WS-074,10.10.3.174,C:\Users\bwilson\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,b785bcda268397b9ce5430b7e6516a9f8690489734076972a1d91cf361628175,2026-02-11T14:03:50.795Z,2026-02-15T03:45:06.026Z,ACME\bwilson,Windows Server 2022
|
||||
FIN-WS-074,10.10.3.174,C:\Users\bwilson\Downloads\crack_photoshop.exe,crack_photoshop.exe,12000000,f8a6b7c9d0e1f2a3b4c5d6e7f8a9b0c1,e460da57bc5e48b7ec2c5fbcb7f78427213b17d7c0351a77a8fe9f40e905c981,2026-02-11T00:15:54.201Z,2026-02-16T02:13:40.734Z,ACME\bwilson,Windows Server 2022
|
||||
FIN-WS-074,10.10.3.174,C:\Users\bwilson\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,1d95cfae3e70e193e0c6975efa9fb542f3bc346ef0673554a50ff887617fc597,2026-02-12T18:25:04.690Z,2026-02-18T00:31:07.288Z,ACME\bwilson,Windows Server 2022
|
||||
SLS-WS-075,10.10.1.175,C:\Users\agarcia\Downloads\vpn_config.ovpn,vpn_config.ovpn,1200,c5d3e4f6a7b8c9d0e1f2a3b4c5d6e7f8,bca414326a75be19e908b1315a25c8d20dad7a1623eddb487cbda08537f4a3e2,2026-02-17T08:59:26.488Z,2026-02-14T00:58:23.302Z,ACME\agarcia,Windows Server 2019
|
||||
SLS-WS-075,10.10.1.175,C:\Users\agarcia\Downloads\keygen_v2.exe,keygen_v2.exe,500000,a9b7c8d0e1f2a3b4c5d6e7f8a9b0c1d2,560a7076305f183923e79b0317362875b51580bcc35eefef03deff558cb20032,2026-02-16T15:14:05.569Z,2026-02-14T16:31:21.523Z,ACME\agarcia,Windows Server 2019
|
||||
SLS-WS-075,10.10.1.175,C:\Users\agarcia\Downloads\Q1_Budget_2026.xlsx,Q1_Budget_2026.xlsx,245000,a3f1b2c4d5e6f7a8b9c0d1e2f3a4b5c6,d052a9517af5231576a6bbe0509588d61146330e09b28e77707a35c777859515,2026-02-16T23:34:45.555Z,2026-02-17T23:17:09.444Z,ACME\agarcia,Windows Server 2019
|
||||
|
548
docs/update.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# ThreatHunt — Session Update (February 19–20, 2026)
|
||||
|
||||
**Version: 0.4.0**
|
||||
|
||||
## Summary
|
||||
|
||||
This document covers feature improvements and bug fixes across the ThreatHunt platform.
|
||||
|
||||
---
|
||||
|
||||
## 1. Network Map — Clickable Type Filters (IP / Host / Domain / URL)
|
||||
|
||||
**Request:** "IP, Host, Domain, URL on the network map… can I click on these and they would be filtered in or out"
|
||||
|
||||
**Changes (`NetworkMap.tsx`):**
|
||||
- Legend chips (IP, Host, Domain, URL) are now clickable toggle filters
|
||||
- Added `visibleTypes` state (`Set<NodeType>`) and `filteredGraph` useMemo that filters nodes + edges by visible types
|
||||
- Active chips: filled background with type color, fully opaque
|
||||
- Inactive chips: outlined with type color, dimmed at 50% opacity
|
||||
- Each chip shows node count for that type, e.g. `IP (42)`
|
||||
- At least one type must stay visible (can't disable all four)
|
||||
- Stats chips (nodes/edges) update to reflect the filtered view
|
||||
- All mouse handlers (pan, hover, click, wheel zoom) updated to use `filteredGraph` instead of raw `graph`
|
||||
|
||||
---
|
||||
|
||||
## 2. Network Map — Cleaner Nodes (Brighter Colors, 20% Smaller)
|
||||
|
||||
**Request:** "Clean up the icons, the colours are dull and the icons are too big in the map… shrink by 20%"
|
||||
|
||||
**Changes (`NetworkMap.tsx`):**
|
||||
- Colors bumped to more saturated variants:
|
||||
- IP: `#60a5fa` → `#3b82f6`
|
||||
- Host: `#10b981` → `#22c55e`
|
||||
- Domain: `#f59e0b` → `#eab308`
|
||||
- URL: `#a78bfa` → `#8b5cf6`
|
||||
- Node radius shrunk ~20%:
|
||||
- Max: 22 → 18
|
||||
- Base: 5 → 4
|
||||
- Multiplier: 2.0 → 1.6
|
||||
|
||||
---
|
||||
|
||||
## 3. Dataset Viewer — IOC Column Highlighting
|
||||
|
||||
**Request:** "I'm looking at one of the datasets and 4 IOCs are showing… but I can't see it… is there a way to highlight that"
|
||||
|
||||
**Changes (`DatasetViewer.tsx`):**
|
||||
- IOC columns in the DataGrid are now visually highlighted
|
||||
- **Header:** colored background tint + bold colored text + IOC type label (e.g. `src_ip ◆ IP`)
|
||||
- **Cells:** subtle colored background + left border stripe
|
||||
- Color-coded by IOC type matching the Network Map palette:
|
||||
- IP — blue (`#3b82f6`)
|
||||
- Hostname — green (`#22c55e`)
|
||||
- Domain — amber (`#eab308`)
|
||||
- URL — purple (`#8b5cf6`)
|
||||
- Hashes (MD5/SHA1/SHA256) — rose (`#f43f5e`)
|
||||
- Added `IOC_COLORS` mapping, `iocTypeFor()` helper, and dynamic `headerClassName`/`cellClassName` on DataGrid columns
|
||||
- CSS-in-JS styles injected via DataGrid `sx` prop
|
||||
|
||||
---
|
||||
|
||||
## 4. AUP Scanner — "Social Media (Personal)" → "Social Media" Rename
|
||||
|
||||
**Request:** "On the AUP page there is Social Media (Personal) — can we remove personal, it's messing up with the formatting"
|
||||
|
||||
**Changes (`keyword_defaults.py`):**
|
||||
- Default theme key renamed from `"Social Media (Personal)"` to `"Social Media"`
|
||||
- Added rename migration in `seed_defaults()`: checks for old name in DB, if found renames via SQL UPDATE + commit before normal seeding
|
||||
- Backend log confirmed: `Renamed AUP theme 'Social Media (Personal)' → 'Social Media'`
|
||||
|
||||
---
|
||||
|
||||
## 5. AUP Scanner — Hunt Dropdown (previous session, deployed)
|
||||
|
||||
- Replaced individual dataset checkboxes with a hunt selector dropdown
|
||||
- Selecting a hunt auto-loads and selects all its datasets
|
||||
- Shows dataset/row counts below the dropdown
|
||||
|
||||
---
|
||||
|
||||
## 6. Network Map — Hunt-Scoped Interactive Map (previous session, deployed)
|
||||
|
||||
- Hunt selector dropdown loads only that hunt's datasets
|
||||
- Enriched nodes with hostname, IP, OS metadata
|
||||
- Click-to-inspect MUI Popover showing node details
|
||||
- Zoom (wheel + buttons) and pan (drag) with full viewport transform
|
||||
- Force-directed layout with co-occurrence edges
|
||||
|
||||
---
|
||||
|
||||
## 7. Agent Assist — "Failed to Fetch" Diagnosis
|
||||
|
||||
**Request:** "AI assist failed: Error: Failed to fetch"
|
||||
|
||||
**Diagnosis:**
|
||||
- Backend agent endpoint works correctly (tested via PowerShell `Invoke-RestMethod` through nginx proxy)
|
||||
- Health endpoint healthy — both LLM nodes (Wile + Roadrunner) available
|
||||
- Extra fields sent by frontend (`mode`, `hunt_id`, `conversation_id`) are accepted by Pydantic v2 (ignored, not rejected)
|
||||
- "Failed to fetch" was a transient browser-level network error, not a backend issue
|
||||
- Response time ~5s from LLM — within nginx 120s proxy timeout
|
||||
- **Resolution:** Hard refresh (Ctrl+Shift+R) resolves the issue
|
||||
|
||||
---
|
||||
|
||||
## 8. Performance Fix — /api/hunts Timeout (previous session)
|
||||
|
||||
- Root cause: `Dataset.rows` relationship had `lazy="selectin"` causing SQLAlchemy to cascade-load every DatasetRow when listing hunts
|
||||
- Fix: Changed `Dataset.rows` and `DatasetRow.annotations` to `lazy="noload"` in `backend/app/db/models.py`
|
||||
- Result: Hunts endpoint returns instantly
|
||||
|
||||
---
|
||||
|
||||
## Files Modified This Session
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/src/components/NetworkMap.tsx` | Type filter toggles, brighter colors, smaller nodes |
|
||||
| `frontend/src/components/DatasetViewer.tsx` | IOC column highlighting in DataGrid |
|
||||
| `backend/app/services/keyword_defaults.py` | Theme rename + DB migration |
|
||||
|
||||
## Deployment
|
||||
|
||||
All changes built and deployed via Docker Compose:
|
||||
```
|
||||
docker compose build --no-cache frontend
|
||||
docker compose up -d frontend
|
||||
docker compose build --no-cache backend
|
||||
docker compose up -d backend
|
||||
```
|
||||
|
||||
## Git
|
||||
|
||||
Committed and pushed to GitHub (`main` branch):
|
||||
```
|
||||
92 files changed, 13050 insertions(+), 1097 deletions(-)
|
||||
d0c9f88..9b98ab9 main -> main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Network Picture — Deduplicated Host Inventory (February 20, 2026)
|
||||
|
||||
**Request:** "While files are being dropped in, give me a clean clear network picture — like netstat. No 100 iterations of the same computer. Clean pic: hostname, IP, and any users logged into that machine."
|
||||
|
||||
### What It Does
|
||||
|
||||
New **Net Picture** page that scans all datasets in a hunt and produces a **one-row-per-host** inventory table. If a host appears in 900 netstat rows, it shows once — with all unique IPs, usernames, OS versions, MACs, and ports aggregated via server-side set deduplication. Supports networks with up to 1000 hosts; no artificial caps on unique values per host.
|
||||
|
||||
### Backend
|
||||
|
||||
**New service** — `backend/app/services/network_inventory.py`:
|
||||
- `build_network_picture(db, hunt_id)` streams all dataset rows in batches of 1000
|
||||
- Groups rows by `hostname` (case-insensitive), falls back to `src_ip`/`ip_address` if no hostname column
|
||||
- Per host, aggregates into Python `set()`s: IPs, users, OS, MAC addresses, protocols, open ports, remote targets, dataset sources
|
||||
- Tracks `connection_count` (total rows), `first_seen`/`last_seen` from timestamp columns
|
||||
- Returns sorted by connection count descending
|
||||
|
||||
**New API route** — `backend/app/api/routes/network.py`:
|
||||
- `GET /api/network/picture?hunt_id={id}` → `NetworkPictureResponse`
|
||||
- Response: `hosts[]` (each with hostname, ips, users, os, mac_addresses, protocols, open_ports, remote_targets, datasets, connection_count, first_seen, last_seen) + `summary` (total_hosts, total_connections, total_unique_ips, datasets_scanned)
|
||||
|
||||
**Normalizer additions** — `backend/app/services/normalizer.py`:
|
||||
- Added `mac_address` canonical mapping: `mac`, `mac_address`, `physical_address`, `mac_addr`, `hw_addr`, `ethernet`
|
||||
- Added `connection_state` canonical mapping: `state`, `status`, `tcp_state`, `conn_state`
|
||||
|
||||
### Frontend
|
||||
|
||||
**New component** — `frontend/src/components/NetworkPicture.tsx`:
|
||||
- Hunt selector dropdown → loads network picture from backend
|
||||
- MUI Table with sortable columns: Hostname, IPs, Users, OS, MAC, Connections, Ports
|
||||
- **ChipList** sub-component: shows first 5 values inline as coloured Chips; "+N more" badge expands to show all (no data hidden, just visual overflow control)
|
||||
- Click any row → expand panel showing: remote targets, all open ports, protocols, MAC addresses, dataset sources, time range
|
||||
- Search bar filters by hostname, IP, user, OS, or MAC
|
||||
- Summary stat cards: total hosts, unique IPs, connections, datasets scanned
|
||||
- Colour palette matches Network Map: IP blue, User green, OS amber, MAC purple, Port rose
|
||||
|
||||
**App integration** — `frontend/src/App.tsx`:
|
||||
- New nav item: **Net Picture** with `DevicesIcon`, route `/netpicture`
|
||||
- Import and route for `NetworkPicture` component
|
||||
|
||||
**API client** — `frontend/src/api/client.ts`:
|
||||
- Added `HostEntry`, `PictureSummary`, `NetworkPictureResponse` interfaces
|
||||
- Added `network.picture(huntId)` method
|
||||
|
||||
### Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `backend/app/services/normalizer.py` | Added `mac_address` + `connection_state` mappings |
|
||||
| `backend/app/services/network_inventory.py` | **New** — host aggregation service |
|
||||
| `backend/app/api/routes/network.py` | **New** — `/api/network/picture` endpoint |
|
||||
| `backend/app/main.py` | Registered network router |
|
||||
| `frontend/src/api/client.ts` | Added network picture types + API method |
|
||||
| `frontend/src/components/NetworkPicture.tsx` | **New** — host inventory table component |
|
||||
| `frontend/src/App.tsx` | Added Nav item + route for Net Picture |
|
||||
|
||||
---
|
||||
|
||||
## 10. Test Data — Velociraptor Mock Network CSVs (February 20, 2026)
|
||||
|
||||
**Request:** "Create 10-15 different CSV files you'd expect from Velociraptor for a mock network with 50-100 hosts and random network traffic with a few keywords that would trigger the AUP."
|
||||
|
||||
### What Was Created
|
||||
|
||||
12 realistic Velociraptor-style CSV files in `backend/tests/test_csvs/`, generated by a Python script (`generate_test_csvs.py`) with deterministic seed for reproducibility.
|
||||
|
||||
**Mock Network:**
|
||||
- **82 hosts**: 75 workstations (IT-WS, HR-WS, FIN-WS, SLS-WS, ENG-WS, LEG-WS, MKT-WS, EXEC-WS) + 7 servers (DC-01, DC-02, FILE-01, EXCH-01, WEB-01, SQL-01, PROXY-01)
|
||||
- **14 users**: 10 named users (jsmith, agarcia, bwilson, etc.) + 4 service accounts
|
||||
- **3 subnets**: `10.10.1.x`, `10.10.2.x`, `10.10.3.x`
|
||||
- **Domain**: `acme.local`
|
||||
- **Time range**: Feb 10–20, 2026
|
||||
|
||||
### Files
|
||||
|
||||
| # | File | Rows | Velociraptor Artifact |
|
||||
|---|------|------|----------------------|
|
||||
| 1 | `01_netstat_connections.csv` | 2,012 | `Windows.Network.Netstat` |
|
||||
| 2 | `02_dns_queries.csv` | 2,964 | `Windows.Network.DNS` |
|
||||
| 3 | `03_process_listing.csv` | 1,896 | `Windows.System.Pslist` |
|
||||
| 4 | `04_network_interfaces.csv` | 94 | `Windows.Network.Interfaces` |
|
||||
| 5 | `05_logged_in_users.csv` | 171 | `Windows.Sys.Users` |
|
||||
| 6 | `06_scheduled_tasks.csv` | 410 | `Windows.System.TaskScheduler` |
|
||||
| 7 | `07_browser_history.csv` | 1,586 | `Windows.Application.Chrome.History` |
|
||||
| 8 | `08_sysmon_network.csv` | 2,517 | Sysmon Event ID 3 |
|
||||
| 9 | `09_autoruns.csv` | 342 | `Windows.Sys.AutoRuns` |
|
||||
| 10 | `10_logon_events.csv` | 1,604 | Windows Security (4624/4625) |
|
||||
| 11 | `11_proxy_logs.csv` | 2,605 | Web proxy / filter |
|
||||
| 12 | `12_file_listing.csv` | 421 | `Windows.Search.FileFinder` |
|
||||
|
||||
### AUP Triggers Embedded
|
||||
|
||||
| Category | Examples |
|
||||
|----------|----------|
|
||||
| Gambling | bet365.com, pokerstars.com, draftkings.com |
|
||||
| Gaming | steam.exe, discord.exe, steamcommunity.com, epicgameslauncher.exe |
|
||||
| Streaming | netflix.com, hulu.com, spotify.exe, open.spotify.com |
|
||||
| Downloads / Piracy | thepiratebay.org, 1337x.to, utorrent.exe, qbittorrent.exe, free_movie_2026.torrent |
|
||||
| Adult Content | pornhub.com, onlyfans.com, xvideos.com |
|
||||
| Social Media | facebook.com, tiktok.com, reddit.com |
|
||||
| Job Search | indeed.com, glassdoor.com, linkedin.com/jobs |
|
||||
| Shopping | amazon.com, ebay.com, shein.com |
|
||||
|
||||
AUP keywords appear in: DNS queries (~12%), browser history (~15%), proxy logs (~10%), process listing (~15% of hosts), autoruns (~20% of workstations), sysmon network (~10%), file listing (crack_photoshop.exe, keygen_v2.exe, free_movie_2026.torrent).
|
||||
|
||||
### Files Added
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `backend/tests/generate_test_csvs.py` | **New** — deterministic CSV generator script |
|
||||
| `backend/tests/test_csvs/*.csv` (×12) | **New** — mock Velociraptor test data |
|
||||
|
||||
---
|
||||
|
||||
## 11. Phase 8 — Analyzer Framework & Alerts (February 20, 2026)
|
||||
|
||||
### What It Does
|
||||
|
||||
Automated alerting engine with 6 built-in analyzers that scan dataset rows for suspicious activity and produce scored, MITRE-tagged alerts. Full alert lifecycle (new → acknowledged → in-progress → resolved / false-positive), bulk operations, and configurable alert rules.
|
||||
|
||||
### Backend
|
||||
|
||||
**New service** — `backend/app/services/analyzers.py` (~350 lines):
|
||||
- `BaseAnalyzer` ABC with `name`, `description`, and `async analyze(rows)` method
|
||||
- 6 built-in analyzers:
|
||||
- **EntropyAnalyzer** — Shannon entropy on command_line/url/path fields, threshold 4.5, maps to T1027 (Obfuscated Files or Information)
|
||||
- **SuspiciousCommandAnalyzer** — 19 regex patterns (mimikatz, encoded PowerShell, schtasks, psexec, vssadmin, etc.) with per-pattern MITRE mappings
|
||||
- **NetworkAnomalyAnalyzer** — Beaconing detection (dst IP frequency), suspicious ports (4444, 5555, etc.), large transfers (>10 MB), maps to T1071/T1048/T1571
|
||||
- **FrequencyAnomalyAnalyzer** — Flags values <1% occurrence in process_name/username/event_type fields
|
||||
- **AuthAnomalyAnalyzer** — Brute force (>5 failed logins per user), unusual logon types (3=network, 10=RDP), maps to T1110/T1021
|
||||
- **PersistenceAnalyzer** — Registry Run keys, services, Winlogon, IFEO patterns, maps to T1547/T1543/T1546
|
||||
- `run_all_analyzers(rows, analyzers?)` — runs all or selected analyzers, returns sorted `AlertCandidate` list
|
||||
|
||||
**New API route** — `backend/app/api/routes/alerts.py` (~300 lines):
|
||||
- `GET /api/alerts` — list with filters (status, severity, analyzer, hunt_id, dataset_id)
|
||||
- `GET /api/alerts/stats` — severity/status/analyzer/MITRE breakdowns
|
||||
- `GET /api/alerts/{id}`, `PUT /api/alerts/{id}`, `DELETE /api/alerts/{id}`
|
||||
- `POST /api/alerts/bulk-update` — bulk status changes on selected alert IDs
|
||||
- `GET /api/alerts/analyzers/list` — available analyzer metadata
|
||||
- `POST /api/alerts/analyze` — run analyzers on a dataset/hunt, auto-creates Alert records
|
||||
- Alert Rules CRUD: `GET /api/alerts/rules/list`, `POST /api/alerts/rules`, `PUT /api/alerts/rules/{id}`, `DELETE /api/alerts/rules/{id}`
|
||||
|
||||
**New ORM models** — `backend/app/db/models.py`:
|
||||
- `Alert` — 18 fields (id, title, description, severity, status, analyzer, score, evidence JSON, mitre_technique, tags JSON, hunt_id FK, dataset_id FK, case_id FK, assignee, acknowledged_at, resolved_at, timestamps; indexes on severity/status/hunt/dataset)
|
||||
- `AlertRule` — 10 fields (id, name, description, analyzer, config JSON, severity_override, enabled, hunt_id FK, timestamps; index on analyzer)
|
||||
|
||||
**Alembic migration** — `backend/alembic/versions/b4c2d3e5f6a7_add_alerts_and_alert_rules.py`
|
||||
|
||||
### Frontend
|
||||
|
||||
**New component** — `frontend/src/components/AlertPanel.tsx` (~350 lines):
|
||||
- **Alerts tab**: MUI DataGrid with severity/status chips, score, MITRE technique; checkbox selection for bulk actions (acknowledge / resolve / false-positive); click → detail dialog with evidence JSON viewer
|
||||
- **Stats tab**: Cards for total, per-severity counts, status/analyzer breakdowns, top MITRE techniques
|
||||
- **Rules tab**: Rule cards with enable/disable switch, delete; create rule dialog with analyzer selector
|
||||
- Selector bar: Hunt + Dataset pickers, "Run Analyzers" button, severity/status filters
|
||||
|
||||
**API client** — `frontend/src/api/client.ts`:
|
||||
- Added `AlertData`, `AlertStats`, `AlertRuleData`, `AnalyzerInfo`, `AnalyzeResult` interfaces
|
||||
- Added `alerts` namespace with full CRUD + analyze + rules methods
|
||||
|
||||
**App integration** — `frontend/src/App.tsx`:
|
||||
- New nav item: **Alerts** with `NotificationsActiveIcon`, route `/alerts`
|
||||
|
||||
### Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `backend/app/services/analyzers.py` | **New** — 6 analyzers + runner |
|
||||
| `backend/app/api/routes/alerts.py` | **New** — alerts API (CRUD, stats, bulk, rules) |
|
||||
| `backend/app/db/models.py` | Added `Alert` + `AlertRule` models |
|
||||
| `backend/alembic/versions/b4c2d3e5f6a7_...py` | **New** — alerts migration |
|
||||
| `frontend/src/components/AlertPanel.tsx` | **New** — alerts dashboard |
|
||||
| `frontend/src/api/client.ts` | Added alert types + `alerts` namespace |
|
||||
| `frontend/src/App.tsx` | Added Alerts nav + route |
|
||||
|
||||
---
|
||||
|
||||
## 12. Phase 9 — Investigation Notebooks & Playbooks (February 20, 2026)
|
||||
|
||||
### What It Does
|
||||
|
||||
Cell-based investigation notebooks (markdown / query / code) for documenting hunts, plus a playbook engine with 4 built-in response templates and step-by-step execution tracking.
|
||||
|
||||
### Backend
|
||||
|
||||
**New service** — `backend/app/services/playbook.py` (~250 lines):
|
||||
- `NotebookCell` dataclass + `validate_notebook_cells()` helper
|
||||
- 4 built-in playbook templates:
|
||||
- **Suspicious Process Investigation** (6 steps): identify → process tree → network → analyzers → MITRE → document
|
||||
- **Lateral Movement Hunt** (5 steps): remote tools → auth anomaly → network anomaly → knowledge graph → escalate
|
||||
- **Data Exfiltration Check** (5 steps): large transfers → DNS → timeline → correlate → MITRE + document
|
||||
- **Ransomware Triage** (5 steps): indicators search → all analyzers → persistence → LLM deep → critical case
|
||||
- Each step: order, title, description, action, action_config, expected_outcome
|
||||
|
||||
**New API route** — `backend/app/api/routes/notebooks.py` (~280 lines):
|
||||
- Notebook CRUD: `GET /api/notebooks`, `GET /api/notebooks/{id}`, `POST /api/notebooks`, `PUT /api/notebooks/{id}`, `DELETE /api/notebooks/{id}`
|
||||
- Cell operations: `POST /api/notebooks/{id}/cells/upsert`, `DELETE /api/notebooks/{id}/cells/{cell_id}`
|
||||
- Playbook endpoints: `GET /api/notebooks/playbooks/templates`, `GET /api/notebooks/playbooks/templates/{name}`, `POST /api/notebooks/playbooks/start`, `GET /api/notebooks/playbooks/runs`, `GET /api/notebooks/playbooks/runs/{id}`, `POST /api/notebooks/playbooks/runs/{id}/complete-step`, `POST /api/notebooks/playbooks/runs/{id}/abort`
|
||||
|
||||
**New ORM models** — `backend/app/db/models.py`:
|
||||
- `Notebook` — 10 fields (id, title, description, cells JSON, hunt_id FK, case_id FK, owner_id FK, tags JSON, timestamps; index on hunt_id)
|
||||
- `PlaybookRun` — 12 fields (id, playbook_name, status, current_step, total_steps, step_results JSON, hunt_id FK, case_id FK, started_by, timestamps, completed_at; indexes on hunt_id/status)
|
||||
|
||||
**Alembic migration** — `backend/alembic/versions/c5d3e4f6a7b8_add_notebooks_and_playbook_runs.py`
|
||||
|
||||
### Frontend
|
||||
|
||||
**New component** — `frontend/src/components/InvestigationNotebook.tsx` (~280 lines):
|
||||
- List view: Grid of notebook cards with title, description, cell count, tags, open/delete
|
||||
- Detail view: Cell-based editor with markdown/query/code cell types
|
||||
- Markdown cells render via ReactMarkdown + remarkGfm; code/query cells as monospace pre blocks
|
||||
- Edit mode: multiline TextField with Ctrl+S save shortcut
|
||||
- Cell operations: add (markdown/query/code), edit, save, delete, move up/down
|
||||
|
||||
**New component** — `frontend/src/components/PlaybookManager.tsx` (~280 lines):
|
||||
- **Templates tab**: Grid of template cards with category chips, tags, step count; view detail (MUI Stepper showing all steps) or start new run
|
||||
- **Runs tab**: List of active/completed runs with progress bars
|
||||
- Active run view: Vertical MUI Stepper with step completion, notes field, skip/abort, LinearProgress
|
||||
|
||||
**API client** — `frontend/src/api/client.ts`:
|
||||
- Added `NotebookCell`, `NotebookData`, `PlaybookTemplate`, `PlaybookStep`, `PlaybookTemplateDetail`, `PlaybookRunData` interfaces
|
||||
- Added `notebooks` namespace (CRUD + cell ops) and `playbooks` namespace (templates, runs, step-complete, abort)
|
||||
|
||||
**App integration** — `frontend/src/App.tsx`:
|
||||
- New nav items: **Notebooks** (`MenuBookIcon`, `/notebooks`) and **Playbooks** (`PlaylistPlayIcon`, `/playbooks`)
|
||||
|
||||
### Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `backend/app/services/playbook.py` | **New** — 4 playbook templates + cell validation |
|
||||
| `backend/app/api/routes/notebooks.py` | **New** — notebooks + playbooks API |
|
||||
| `backend/app/db/models.py` | Added `Notebook` + `PlaybookRun` models |
|
||||
| `backend/alembic/versions/c5d3e4f6a7b8_...py` | **New** — notebooks migration |
|
||||
| `frontend/src/components/InvestigationNotebook.tsx` | **New** — cell-based notebook editor |
|
||||
| `frontend/src/components/PlaybookManager.tsx` | **New** — playbook template browser + run stepper |
|
||||
| `frontend/src/api/client.ts` | Added notebook + playbook types + namespaces |
|
||||
| `frontend/src/App.tsx` | Added Notebooks + Playbooks nav + routes |
|
||||
|
||||
---
|
||||
|
||||
## 13. Docker Build Fixes (February 20, 2026)
|
||||
|
||||
Several issues were resolved to get the full 22-page frontend and updated backend deployed in Docker Compose.
|
||||
|
||||
### npm / TypeScript Fixes
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `npm ci` failing: "Missing: yaml@2.8.2 from lock file" | Installed `yaml@2` — `postcss-load-config` (from tailwindcss) required `^2.4.2` but only `1.10.2` was present |
|
||||
| `GridRowSelectionModel` cast to `string[]` | MUI X DataGrid v8 changed to `{ type, ids: Set }` — fixed with `Array.from(model.ids)` |
|
||||
| Recharts `formatter` type mismatch | Changed explicit `number`/`string` params to `any` in `Dashboard.tsx` |
|
||||
| Missing declarations for `cytoscape-dagre` / `cytoscape-cola` | Created `frontend/src/declarations.d.ts` |
|
||||
| Missing `HuntOut` type export | Added `export type HuntOut = Hunt` alias in `client.ts` |
|
||||
| `cytoscape.Stylesheet` no longer exists | Renamed to `cytoscape.StylesheetStyle` in ProcessTree, StorylineGraph, KnowledgeGraph |
|
||||
|
||||
### Backend Startup Fixes
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `init_db()` crash: "table cases already exists" | Rewrote to inspect existing tables and only create missing ones |
|
||||
| Alembic migration replay on startup | DB had all tables (from `init_db()`) but Alembic was stamped at `98ab619418bc` — ran `alembic stamp head` to sync to `c5d3e4f6a7b8` |
|
||||
|
||||
### Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/package.json` / `package-lock.json` | Added `yaml@2` dependency |
|
||||
| `frontend/src/declarations.d.ts` | **New** — ambient module declarations |
|
||||
| `frontend/src/api/client.ts` | Added `HuntOut` type alias |
|
||||
| `frontend/src/components/AlertPanel.tsx` | Fixed `GridRowSelectionModel` handling |
|
||||
| `frontend/src/components/Dashboard.tsx` | Fixed Recharts formatter types |
|
||||
| `frontend/src/components/ProcessTree.tsx` | `Stylesheet` → `StylesheetStyle` |
|
||||
| `frontend/src/components/StorylineGraph.tsx` | `Stylesheet` → `StylesheetStyle` |
|
||||
| `frontend/src/components/KnowledgeGraph.tsx` | `Stylesheet` → `StylesheetStyle` |
|
||||
| `backend/app/db/engine.py` | Safe `init_db()` with inspector |
|
||||
|
||||
---
|
||||
|
||||
## Current Navigation (22 pages)
|
||||
|
||||
Dashboard · Hunts · Datasets · Upload · Agent · Analysis · Annotations · Hypotheses · Correlation · Network Map · Net Picture · Proc Tree · Storyline · Timeline · Search · MITRE Map · Knowledge · Cases · **Alerts** · **Notebooks** · **Playbooks** · AUP Scanner
|
||||
|
||||
## Deployment
|
||||
|
||||
```
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
# Backend: http://localhost:8000 (healthy)
|
||||
# Frontend: http://localhost:3000 (200 OK)
|
||||
```
|
||||
|
||||
## Alembic Migration Chain
|
||||
|
||||
```
|
||||
9790f482da06 (initial schema)
|
||||
↓
|
||||
98ab619418bc (keyword themes + keywords)
|
||||
↓
|
||||
a3b1c2d4e5f6 (cases + activity logs)
|
||||
↓
|
||||
b4c2d3e5f6a7 (alerts + alert rules)
|
||||
↓
|
||||
c5d3e4f6a7b8 (notebooks + playbook runs) ← HEAD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Process Tree — HTTP 500 Fix (February 20, 2026)
|
||||
|
||||
**Request:** "process tree: HTTP 500"
|
||||
|
||||
**Root Cause:** `MissingGreenlet` error in `_fetch_rows` — async SQLAlchemy lazy-loading the `dataset` relationship on `DatasetRow` outside a greenlet context.
|
||||
|
||||
**Fix (`backend/app/services/process_tree.py`):**
|
||||
- Added `selectinload(DatasetRow.dataset)` to the `_fetch_rows` query so the relationship is eagerly loaded within the async session
|
||||
- Endpoint now returns data correctly (verified: 3,908 processes for full hunt)
|
||||
|
||||
---
|
||||
|
||||
## 15. Process Tree — UX Rewrite (February 20, 2026)
|
||||
|
||||
**Request:** "proctree view is horrible… there are 3908 processes and I can't see / zoom in to see and when I do click on one the resolution changes to show the data on the right. Ideally I'd like another dropdown to pick a host."
|
||||
|
||||
**Complete rewrite of `frontend/src/components/ProcessTree.tsx` (~520 lines):**
|
||||
|
||||
- **Host dropdown**: Autocomplete populated by extracting unique hostnames from tree data (82 hosts)
|
||||
- **Server-side hostname filtering**: API `?hostname=` param reduces returned data per view
|
||||
- **Grid layout fallback**: When edges < 10% of nodes (e.g. test data with no parent-child relationships), uses grid layout instead of dagre
|
||||
- **Overlay detail panel**: Absolute-positioned panel on click — no longer reflows the graph
|
||||
- **ResizeObserver**: Keeps Cytoscape canvas in sync with container size changes
|
||||
- **Search/highlight**: Filter processes by name or PID
|
||||
- **Zoom controls**: Fit, zoom-in, zoom-out buttons
|
||||
|
||||
---
|
||||
|
||||
## 16. Process Tree — White Screen Crash Fix (February 20, 2026)
|
||||
|
||||
**Request:** "I picked a different host and the screen went white"
|
||||
|
||||
**Root Cause:** Cytoscape's `destroy()` removed `<canvas>` elements that were children of the same DOM node React was managing, causing `NotFoundError: Failed to execute 'removeChild' on 'Node'` when React tried to reconcile.
|
||||
|
||||
**Fix (`frontend/src/components/ProcessTree.tsx`):**
|
||||
- Separated Cytoscape container into a plain `<div>` with zero React children
|
||||
- Loading spinner and placeholder text are now sibling overlays (not children of the Cytoscape div)
|
||||
- Added explicit `cyRef.current = null` after `destroy()`
|
||||
- Cleanup on unmount prevents stale references
|
||||
- Verified: switching between hosts (DC-01 → EXEC-WS-039 → ENG-WS-052) works with 0 console errors
|
||||
|
||||
---
|
||||
|
||||
## 17. LLM Analysis — Response Parsing Fix (February 20, 2026)
|
||||
|
||||
**Request:** "llm analysis: HTTP 500 — searching for evidence of adult site access"
|
||||
|
||||
**Root Cause Chain:**
|
||||
|
||||
1. **`AttributeError: 'dict' object has no attribute 'strip'`** — `OllamaProvider.generate()` returns a dict (`{"response": "<llm text>", "model": ..., ...}`), but `_parse_analysis` expected a string.
|
||||
|
||||
2. **Wrong dict key extraction** — Initial dict-handling fix looked for `raw.get("analysis")` but Ollama's `/api/generate` endpoint puts the LLM text in the `"response"` key.
|
||||
|
||||
3. **JSON parsing failures** — The 70B model sometimes produces slightly malformed JSON (trailing commas, etc.) that `json.loads` rejects.
|
||||
|
||||
**Fixes (`backend/app/services/llm_analysis.py`):**
|
||||
|
||||
| Change | Detail |
|
||||
|--------|--------|
|
||||
| Dict response extraction | `raw.get("response")` instead of `raw.get("analysis")` — correctly extracts LLM text from Ollama API dict |
|
||||
| Robust JSON parsing | New `_extract_json_candidates()` generator: tries full text, then outermost `{…}` block, then trailing-comma-fixed variant |
|
||||
| Reduced prompt size | `max_sample` 50 → 20 rows, `max_chars` 8000 → 6000 |
|
||||
| Reduced row limit | `/llm-analyze` endpoint loads 2000 rows (was 5000) |
|
||||
| Timeout | `asyncio.wait_for(..., timeout=300)` wraps the LLM call — returns graceful "timed out" message instead of hanging |
|
||||
| Debug logging | `_parse_analysis` logs extraction source, text length, parse success/failure with error details |
|
||||
|
||||
**Results:**
|
||||
- Quick mode (llama3.1:latest on roadrunner): 18s, confidence 0.85, risk score 65, 3 key findings — all structured fields populated
|
||||
- Deep mode (llama3.1:70b on wile): 128–185s, full analysis returned — JSON parsing succeeds when model produces valid JSON, falls back to plain text otherwise
|
||||
|
||||
---
|
||||
|
||||
## 18. Version Bump — 0.3.0 → 0.4.0 (February 20, 2026)
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `backend/app/config.py` | `APP_VERSION` 0.3.0 → 0.4.0 |
|
||||
| `frontend/package.json` | `version` 0.1.0 → 0.4.0 |
|
||||
|
||||
---
|
||||
|
||||
## Files Modified This Session (Sections 14–18)
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `backend/app/services/process_tree.py` | Added `selectinload(DatasetRow.dataset)` to `_fetch_rows` |
|
||||
| `frontend/src/components/ProcessTree.tsx` | Complete rewrite: host dropdown, grid layout, overlay panel, Cytoscape DOM separation |
|
||||
| `backend/app/services/llm_analysis.py` | Fixed dict response extraction, robust JSON parsing, reduced prompt, added timeout |
|
||||
| `backend/app/api/routes/analysis.py` | Row limit 5000 → 2000 |
|
||||
| `backend/app/config.py` | Version 0.3.0 → 0.4.0 |
|
||||
| `frontend/package.json` | Version 0.1.0 → 0.4.0 |
|
||||
|
||||
## Deployment
|
||||
|
||||
```
|
||||
docker compose up -d --build backend
|
||||
# Backend: http://localhost:8000 (healthy, v0.4.0)
|
||||
# Frontend: http://localhost:3000 (200 OK)
|
||||
```
|
||||
2028
frontend/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "threathunt-frontend",
|
||||
"version": "0.1.0",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
@@ -9,11 +9,19 @@
|
||||
"@mui/material": "^7.3.8",
|
||||
"@mui/x-data-grid": "^8.27.1",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"cytoscape": "^3.33.1",
|
||||
"cytoscape-cola": "^2.5.1",
|
||||
"cytoscape-dagre": "^2.5.0",
|
||||
"dagre": "^0.8.5",
|
||||
"notistack": "^3.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"react-scripts": "5.0.1"
|
||||
"react-scripts": "5.0.1",
|
||||
"recharts": "^3.7.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -39,6 +47,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.21.9",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"typescript": "^4.9.5"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* ThreatHunt MUI-powered analyst-assist platform.
|
||||
* ThreatHunt — MUI-powered analyst-assist platform.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
@@ -18,7 +18,17 @@ import ScienceIcon from '@mui/icons-material/Science';
|
||||
import CompareArrowsIcon from '@mui/icons-material/CompareArrows';
|
||||
import GppMaybeIcon from '@mui/icons-material/GppMaybe';
|
||||
import HubIcon from '@mui/icons-material/Hub';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
import DevicesIcon from '@mui/icons-material/Devices';
|
||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||
import TimelineIcon from '@mui/icons-material/Timeline';
|
||||
import ManageSearchIcon from '@mui/icons-material/ManageSearch';
|
||||
import ScheduleIcon from '@mui/icons-material/Schedule';
|
||||
import ShieldIcon from '@mui/icons-material/Shield';
|
||||
import BubbleChartIcon from '@mui/icons-material/BubbleChart';
|
||||
import WorkIcon from '@mui/icons-material/Work';
|
||||
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
||||
import PlaylistPlayIcon from '@mui/icons-material/PlaylistPlay';
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
import theme from './theme';
|
||||
|
||||
@@ -27,13 +37,23 @@ import HuntManager from './components/HuntManager';
|
||||
import DatasetViewer from './components/DatasetViewer';
|
||||
import FileUpload from './components/FileUpload';
|
||||
import AgentPanel from './components/AgentPanel';
|
||||
import EnrichmentPanel from './components/EnrichmentPanel';
|
||||
import AnalysisPanel from './components/AnalysisPanel';
|
||||
import AnnotationPanel from './components/AnnotationPanel';
|
||||
import HypothesisTracker from './components/HypothesisTracker';
|
||||
import CorrelationView from './components/CorrelationView';
|
||||
import AUPScanner from './components/AUPScanner';
|
||||
import NetworkMap from './components/NetworkMap';
|
||||
import AnalysisDashboard from './components/AnalysisDashboard';
|
||||
import NetworkPicture from './components/NetworkPicture';
|
||||
import ProcessTree from './components/ProcessTree';
|
||||
import StorylineGraph from './components/StorylineGraph';
|
||||
import TimelineScrubber from './components/TimelineScrubber';
|
||||
import QueryBar from './components/QueryBar';
|
||||
import MitreMatrix from './components/MitreMatrix';
|
||||
import KnowledgeGraph from './components/KnowledgeGraph';
|
||||
import CaseManager from './components/CaseManager';
|
||||
import AlertPanel from './components/AlertPanel';
|
||||
import InvestigationNotebook from './components/InvestigationNotebook';
|
||||
import PlaybookManager from './components/PlaybookManager';
|
||||
|
||||
const DRAWER_WIDTH = 240;
|
||||
|
||||
@@ -44,13 +64,23 @@ const NAV: NavItem[] = [
|
||||
{ label: 'Hunts', path: '/hunts', icon: <SearchIcon /> },
|
||||
{ label: 'Datasets', path: '/datasets', icon: <StorageIcon /> },
|
||||
{ label: 'Upload', path: '/upload', icon: <UploadFileIcon /> },
|
||||
{ label: 'AI Analysis', path: '/analysis', icon: <AssessmentIcon /> },
|
||||
{ label: 'Agent', path: '/agent', icon: <SmartToyIcon /> },
|
||||
{ label: 'Enrichment', path: '/enrichment', icon: <SecurityIcon /> },
|
||||
{ label: 'Analysis', path: '/analysis', icon: <SecurityIcon /> },
|
||||
{ label: 'Annotations', path: '/annotations', icon: <BookmarkIcon /> },
|
||||
{ label: 'Hypotheses', path: '/hypotheses', icon: <ScienceIcon /> },
|
||||
{ label: 'Correlation', path: '/correlation', icon: <CompareArrowsIcon /> },
|
||||
{ label: 'Network Map', path: '/network', icon: <HubIcon /> },
|
||||
{ label: 'Net Picture', path: '/netpicture', icon: <DevicesIcon /> },
|
||||
{ label: 'Proc Tree', path: '/proctree', icon: <AccountTreeIcon /> },
|
||||
{ label: 'Storyline', path: '/storyline', icon: <TimelineIcon /> },
|
||||
{ label: 'Timeline', path: '/timeline', icon: <ScheduleIcon /> },
|
||||
{ label: 'Search', path: '/search', icon: <ManageSearchIcon /> },
|
||||
{ label: 'MITRE Map', path: '/mitre', icon: <ShieldIcon /> },
|
||||
{ label: 'Knowledge', path: '/knowledge', icon: <BubbleChartIcon /> },
|
||||
{ label: 'Cases', path: '/cases', icon: <WorkIcon /> },
|
||||
{ label: 'Alerts', path: '/alerts', icon: <NotificationsActiveIcon /> },
|
||||
{ label: 'Notebooks', path: '/notebooks', icon: <MenuBookIcon /> },
|
||||
{ label: 'Playbooks', path: '/playbooks', icon: <PlaylistPlayIcon /> },
|
||||
{ label: 'AUP Scanner', path: '/aup', icon: <GppMaybeIcon /> },
|
||||
];
|
||||
|
||||
@@ -112,13 +142,23 @@ function Shell() {
|
||||
<Route path="/hunts" element={<HuntManager />} />
|
||||
<Route path="/datasets" element={<DatasetViewer />} />
|
||||
<Route path="/upload" element={<FileUpload />} />
|
||||
<Route path="/analysis" element={<AnalysisDashboard />} />
|
||||
<Route path="/agent" element={<AgentPanel />} />
|
||||
<Route path="/enrichment" element={<EnrichmentPanel />} />
|
||||
<Route path="/analysis" element={<AnalysisPanel />} />
|
||||
<Route path="/annotations" element={<AnnotationPanel />} />
|
||||
<Route path="/hypotheses" element={<HypothesisTracker />} />
|
||||
<Route path="/correlation" element={<CorrelationView />} />
|
||||
<Route path="/network" element={<NetworkMap />} />
|
||||
<Route path="/netpicture" element={<NetworkPicture />} />
|
||||
<Route path="/proctree" element={<ProcessTree />} />
|
||||
<Route path="/storyline" element={<StorylineGraph />} />
|
||||
<Route path="/timeline" element={<TimelineScrubber />} />
|
||||
<Route path="/search" element={<QueryBar />} />
|
||||
<Route path="/mitre" element={<MitreMatrix />} />
|
||||
<Route path="/knowledge" element={<KnowledgeGraph />} />
|
||||
<Route path="/cases" element={<CaseManager />} />
|
||||
<Route path="/alerts" element={<AlertPanel />} />
|
||||
<Route path="/notebooks" element={<InvestigationNotebook />} />
|
||||
<Route path="/playbooks" element={<PlaybookManager />} />
|
||||
<Route path="/aup" element={<AUPScanner />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* ====================================================================
|
||||
ThreatHunt API Client -- mirrors every backend endpoint.
|
||||
ThreatHunt API Client — mirrors every backend endpoint.
|
||||
All requests go through the CRA proxy (see package.json "proxy").
|
||||
==================================================================== */
|
||||
|
||||
const BASE = ''; // proxied to http://localhost:8000 by CRA
|
||||
|
||||
// -- Helpers --
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
let authToken: string | null = localStorage.getItem('th_token');
|
||||
|
||||
@@ -36,7 +36,7 @@ async function api<T = any>(
|
||||
return res.text() as unknown as T;
|
||||
}
|
||||
|
||||
// -- Auth --
|
||||
// ── Auth ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface UserPayload {
|
||||
id: string; username: string; email: string;
|
||||
@@ -63,7 +63,7 @@ export const auth = {
|
||||
me: () => api<UserPayload>('/api/auth/me'),
|
||||
};
|
||||
|
||||
// -- Hunts --
|
||||
// ── Hunts ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Hunt {
|
||||
id: string; name: string; description: string | null; status: string;
|
||||
@@ -71,6 +71,9 @@ export interface Hunt {
|
||||
dataset_count: number; hypothesis_count: number;
|
||||
}
|
||||
|
||||
/** Alias kept for backward-compat with components that import HuntOut */
|
||||
export type HuntOut = Hunt;
|
||||
|
||||
export const hunts = {
|
||||
list: (skip = 0, limit = 50) =>
|
||||
api<{ hunts: Hunt[]; total: number }>(`/api/hunts?skip=${skip}&limit=${limit}`),
|
||||
@@ -82,7 +85,7 @@ export const hunts = {
|
||||
delete: (id: string) => api(`/api/hunts/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
// -- Datasets --
|
||||
// ── Datasets ─────────────────────────────────────────────────────────
|
||||
|
||||
export interface DatasetSummary {
|
||||
id: string; name: string; filename: string; source_tool: string | null;
|
||||
@@ -92,8 +95,6 @@ export interface DatasetSummary {
|
||||
file_size_bytes: number; encoding: string | null; delimiter: string | null;
|
||||
time_range_start: string | null; time_range_end: string | null;
|
||||
hunt_id: string | null; created_at: string;
|
||||
processing_status?: string; artifact_type?: string | null;
|
||||
error_message?: string | null; file_path?: string | null;
|
||||
}
|
||||
|
||||
export interface UploadResult {
|
||||
@@ -157,7 +158,7 @@ export const datasets = {
|
||||
delete: (id: string) => api(`/api/datasets/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
// -- Agent --
|
||||
// ── Agent ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AssistRequest {
|
||||
query: string;
|
||||
@@ -200,7 +201,7 @@ export const agent = {
|
||||
},
|
||||
};
|
||||
|
||||
// -- Annotations --
|
||||
// ── Annotations ──────────────────────────────────────────────────────
|
||||
|
||||
export interface AnnotationData {
|
||||
id: string; row_id: number | null; dataset_id: string | null;
|
||||
@@ -226,7 +227,7 @@ export const annotations = {
|
||||
delete: (id: string) => api(`/api/annotations/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
// -- Hypotheses --
|
||||
// ── Hypotheses ───────────────────────────────────────────────────────
|
||||
|
||||
export interface HypothesisData {
|
||||
id: string; hunt_id: string | null; title: string; description: string | null;
|
||||
@@ -251,7 +252,7 @@ export const hypotheses = {
|
||||
delete: (id: string) => api(`/api/hypotheses/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
// -- Enrichment --
|
||||
// ── Enrichment ───────────────────────────────────────────────────────
|
||||
|
||||
export interface EnrichmentResult {
|
||||
ioc_value: string; ioc_type: string; source: string; verdict: string;
|
||||
@@ -276,7 +277,7 @@ export const enrichment = {
|
||||
status: () => api<Record<string, any>>('/api/enrichment/status'),
|
||||
};
|
||||
|
||||
// -- Correlation --
|
||||
// ── Correlation ──────────────────────────────────────────────────────
|
||||
|
||||
export interface CorrelationResult {
|
||||
hunt_ids: string[]; summary: string; total_correlations: number;
|
||||
@@ -294,7 +295,7 @@ export const correlation = {
|
||||
api<{ ioc_value: string; occurrences: any[]; total: number }>(`/api/correlation/ioc/${encodeURIComponent(ioc_value)}`),
|
||||
};
|
||||
|
||||
// -- Reports --
|
||||
// ── Reports ──────────────────────────────────────────────────────────
|
||||
|
||||
export const reports = {
|
||||
json: (huntId: string) =>
|
||||
@@ -307,13 +308,256 @@ export const reports = {
|
||||
api<Record<string, any>>(`/api/reports/hunt/${huntId}/summary`),
|
||||
};
|
||||
|
||||
// -- Root / misc --
|
||||
// ── Root / misc ──────────────────────────────────────────────────────
|
||||
|
||||
export const misc = {
|
||||
root: () => api<{ name: string; version: string; status: string }>('/'),
|
||||
};
|
||||
|
||||
// -- AUP Keywords --
|
||||
// ── Network Picture ──────────────────────────────────────────────────
|
||||
|
||||
export interface HostEntry {
|
||||
hostname: string;
|
||||
ips: string[];
|
||||
users: string[];
|
||||
os: string[];
|
||||
mac_addresses: string[];
|
||||
protocols: string[];
|
||||
open_ports: string[];
|
||||
remote_targets: string[];
|
||||
datasets: string[];
|
||||
connection_count: number;
|
||||
first_seen: string | null;
|
||||
last_seen: string | null;
|
||||
}
|
||||
|
||||
export interface PictureSummary {
|
||||
total_hosts: number;
|
||||
total_connections: number;
|
||||
total_unique_ips: number;
|
||||
datasets_scanned: number;
|
||||
}
|
||||
|
||||
export interface NetworkPictureResponse {
|
||||
hosts: HostEntry[];
|
||||
summary: PictureSummary;
|
||||
}
|
||||
|
||||
export const network = {
|
||||
picture: (huntId: string) =>
|
||||
api<NetworkPictureResponse>(`/api/network/picture?hunt_id=${encodeURIComponent(huntId)}`),
|
||||
};
|
||||
|
||||
// ── Analysis (Process Tree / Storyline / Risk) ───────────────────────
|
||||
|
||||
export interface ProcessNodeData {
|
||||
pid: string; ppid: string; name: string; command_line: string;
|
||||
username: string; hostname: string; timestamp: string;
|
||||
dataset_name: string; row_index: number;
|
||||
children: ProcessNodeData[]; extra: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ProcessTreeResponse {
|
||||
trees: ProcessNodeData[];
|
||||
total_processes: number;
|
||||
}
|
||||
|
||||
export interface StorylineNode {
|
||||
data: {
|
||||
id: string; label: string; event_type: string; hostname: string;
|
||||
timestamp: string; pid: string; ppid: string; process_name: string;
|
||||
command_line: string; username: string; src_ip: string; dst_ip: string;
|
||||
dst_port: string; file_path: string; severity: string;
|
||||
dataset_id: string; row_index: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StorylineEdge {
|
||||
data: {
|
||||
id: string; source: string; target: string; relationship: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StorylineResponse {
|
||||
nodes: StorylineNode[];
|
||||
edges: StorylineEdge[];
|
||||
summary: {
|
||||
total_events: number; total_edges: number;
|
||||
hosts: string[]; event_types: Record<string, number>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RiskHost {
|
||||
hostname: string; score: number; signals: string[];
|
||||
event_count: number; process_count: number;
|
||||
network_count: number; file_count: number;
|
||||
}
|
||||
|
||||
export interface RiskSummaryResponse {
|
||||
hosts: RiskHost[];
|
||||
overall_score: number;
|
||||
total_events: number;
|
||||
severity_breakdown: Record<string, number>;
|
||||
}
|
||||
|
||||
export const analysis = {
|
||||
processTree: (params: { dataset_id?: string; hunt_id?: string; hostname?: string }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.dataset_id) q.set('dataset_id', params.dataset_id);
|
||||
if (params.hunt_id) q.set('hunt_id', params.hunt_id);
|
||||
if (params.hostname) q.set('hostname', params.hostname);
|
||||
return api<ProcessTreeResponse>(`/api/analysis/process-tree?${q}`);
|
||||
},
|
||||
storyline: (params: { dataset_id?: string; hunt_id?: string; hostname?: string }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.dataset_id) q.set('dataset_id', params.dataset_id);
|
||||
if (params.hunt_id) q.set('hunt_id', params.hunt_id);
|
||||
if (params.hostname) q.set('hostname', params.hostname);
|
||||
return api<StorylineResponse>(`/api/analysis/storyline?${q}`);
|
||||
},
|
||||
riskSummary: (huntId?: string) => {
|
||||
const q = huntId ? `?hunt_id=${encodeURIComponent(huntId)}` : '';
|
||||
return api<RiskSummaryResponse>(`/api/analysis/risk-summary${q}`);
|
||||
},
|
||||
llmAnalyze: (params: {
|
||||
dataset_id?: string; hunt_id?: string; question?: string;
|
||||
mode?: 'quick' | 'deep'; focus?: string;
|
||||
}) =>
|
||||
api<LLMAnalysisResult>('/api/analysis/llm-analyze', {
|
||||
method: 'POST', body: JSON.stringify(params),
|
||||
}),
|
||||
|
||||
// Timeline & Search
|
||||
timeline: (params: { dataset_id?: string; hunt_id?: string; bins?: number }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.dataset_id) q.set('dataset_id', params.dataset_id);
|
||||
if (params.hunt_id) q.set('hunt_id', params.hunt_id);
|
||||
if (params.bins) q.set('bins', String(params.bins));
|
||||
return api<TimelineBinsResponse>(`/api/analysis/timeline?${q}`);
|
||||
},
|
||||
fieldStats: (params: { dataset_id?: string; hunt_id?: string; fields?: string; top_n?: number }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.dataset_id) q.set('dataset_id', params.dataset_id);
|
||||
if (params.hunt_id) q.set('hunt_id', params.hunt_id);
|
||||
if (params.fields) q.set('fields', params.fields);
|
||||
if (params.top_n) q.set('top_n', String(params.top_n));
|
||||
return api<FieldStatsResponse>(`/api/analysis/field-stats?${q}`);
|
||||
},
|
||||
searchRows: (params: SearchRowsRequest) =>
|
||||
api<SearchRowsResponse>('/api/analysis/search', {
|
||||
method: 'POST', body: JSON.stringify(params),
|
||||
}),
|
||||
|
||||
// MITRE ATT&CK
|
||||
mitreMap: (params: { dataset_id?: string; hunt_id?: string }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.dataset_id) q.set('dataset_id', params.dataset_id);
|
||||
if (params.hunt_id) q.set('hunt_id', params.hunt_id);
|
||||
return api<MitreMapResponse>(`/api/analysis/mitre-map?${q}`);
|
||||
},
|
||||
knowledgeGraph: (params: { dataset_id?: string; hunt_id?: string }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params.dataset_id) q.set('dataset_id', params.dataset_id);
|
||||
if (params.hunt_id) q.set('hunt_id', params.hunt_id);
|
||||
return api<KnowledgeGraphResponse>(`/api/analysis/knowledge-graph?${q}`);
|
||||
},
|
||||
};
|
||||
|
||||
// ── LLM Analysis types ───────────────────────────────────────────────
|
||||
|
||||
export interface LLMAnalysisResult {
|
||||
analysis: string;
|
||||
confidence: number;
|
||||
key_findings: string[];
|
||||
iocs_identified: { type: string; value: string; context: string }[];
|
||||
recommended_actions: string[];
|
||||
mitre_techniques: string[];
|
||||
risk_score: number;
|
||||
model_used: string;
|
||||
node_used: string;
|
||||
latency_ms: number;
|
||||
rows_analyzed: number;
|
||||
dataset_summary: string;
|
||||
}
|
||||
|
||||
// ── Timeline & Search types ──────────────────────────────────────────
|
||||
|
||||
export interface TimelineBin {
|
||||
start: string;
|
||||
end: string;
|
||||
count: number;
|
||||
types: Record<string, number>;
|
||||
}
|
||||
export interface TimelineBinsResponse {
|
||||
bins: TimelineBin[];
|
||||
total: number;
|
||||
time_range: { start: string; end: string };
|
||||
}
|
||||
|
||||
export interface FieldStatEntry {
|
||||
value: string;
|
||||
count: number;
|
||||
pct: number;
|
||||
}
|
||||
export interface FieldStatsResponse {
|
||||
fields: Record<string, { total: number; unique: number; top: FieldStatEntry[] }>;
|
||||
total_rows: number;
|
||||
}
|
||||
|
||||
export interface SearchRowsRequest {
|
||||
dataset_id?: string;
|
||||
hunt_id?: string;
|
||||
query?: string;
|
||||
filters?: Record<string, string>;
|
||||
time_start?: string;
|
||||
time_end?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
export interface SearchRowsResponse {
|
||||
rows: Record<string, any>[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
// ── MITRE ATT&CK types ──────────────────────────────────────────────
|
||||
|
||||
export interface MitreTechnique {
|
||||
id: string;
|
||||
name: string;
|
||||
count: number;
|
||||
evidence: { row_index: number; field: string; value: string; pattern: string }[];
|
||||
}
|
||||
export interface MitreTactic {
|
||||
id: string;
|
||||
name: string;
|
||||
techniques: MitreTechnique[];
|
||||
total_hits: number;
|
||||
}
|
||||
export interface MitreMapResponse {
|
||||
tactics: MitreTactic[];
|
||||
coverage: {
|
||||
tactics_covered: number;
|
||||
tactics_total: number;
|
||||
techniques_matched: number;
|
||||
total_evidence: number;
|
||||
};
|
||||
total_rows: number;
|
||||
}
|
||||
|
||||
export interface KnowledgeGraphResponse {
|
||||
nodes: { data: { id: string; label: string; type: string; color: string; shape: string; tactic?: string } }[];
|
||||
edges: { data: { source: string; target: string; weight: number; label: string } }[];
|
||||
stats: {
|
||||
total_nodes: number;
|
||||
total_edges: number;
|
||||
entity_counts: Record<string, number>;
|
||||
techniques_found: number;
|
||||
};
|
||||
}
|
||||
|
||||
// ── AUP Keywords ─────────────────────────────────────────────────────
|
||||
|
||||
export interface KeywordOut {
|
||||
id: number; theme_id: string; value: string; is_regex: boolean; created_at: string;
|
||||
@@ -371,215 +615,275 @@ export const keywords = {
|
||||
api<ScanResponse>(`/api/keywords/scan/quick?dataset_id=${encodeURIComponent(datasetId)}`),
|
||||
};
|
||||
|
||||
// ── Case Management ──────────────────────────────────────────────────
|
||||
|
||||
// -- Analysis (Phase 2+) --
|
||||
// ── Alerts & Analyzers ───────────────────────────────────────────────
|
||||
|
||||
export interface TriageResultData {
|
||||
id: string; dataset_id: string; row_start: number; row_end: number;
|
||||
risk_score: number; verdict: string;
|
||||
findings: any[] | null; suspicious_indicators: any[] | null;
|
||||
mitre_techniques: any[] | null;
|
||||
model_used: string | null; node_used: string | null;
|
||||
}
|
||||
|
||||
export interface HostProfileData {
|
||||
id: string; hunt_id: string; hostname: string; fqdn: string | null;
|
||||
risk_score: number; risk_level: string;
|
||||
artifact_summary: Record<string, any> | null;
|
||||
timeline_summary: string | null;
|
||||
suspicious_findings: any[] | null;
|
||||
mitre_techniques: any[] | null;
|
||||
llm_analysis: string | null;
|
||||
model_used: string | null;
|
||||
}
|
||||
|
||||
export interface HuntReportData {
|
||||
id: string; hunt_id: string; status: string;
|
||||
exec_summary: string | null; full_report: string | null;
|
||||
findings: any[] | null; recommendations: any[] | null;
|
||||
mitre_mapping: Record<string, any> | null;
|
||||
ioc_table: any[] | null; host_risk_summary: any[] | null;
|
||||
models_used: any[] | null; generation_time_ms: number | null;
|
||||
}
|
||||
|
||||
export interface AnomalyResultData {
|
||||
id: string; dataset_id: string; row_id: number | null;
|
||||
anomaly_score: number; distance_from_centroid: number | null;
|
||||
cluster_id: number | null; is_outlier: boolean;
|
||||
explanation: string | null;
|
||||
}
|
||||
|
||||
export interface HostGroupData {
|
||||
hostname: string;
|
||||
dataset_count: number;
|
||||
total_rows: number;
|
||||
artifact_types: string[];
|
||||
first_seen: string | null;
|
||||
last_seen: string | null;
|
||||
risk_score: number | null;
|
||||
}
|
||||
|
||||
// -- Job queue types (Phase 10) --
|
||||
|
||||
export interface JobData {
|
||||
export interface AlertData {
|
||||
id: string;
|
||||
job_type: string;
|
||||
status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
progress: number;
|
||||
message: string;
|
||||
error: string | null;
|
||||
created_at: number;
|
||||
started_at: number | null;
|
||||
completed_at: number | null;
|
||||
elapsed_ms: number;
|
||||
params: Record<string, any>;
|
||||
title: string;
|
||||
description: string | null;
|
||||
severity: string;
|
||||
status: string;
|
||||
analyzer: string;
|
||||
score: number;
|
||||
evidence: Record<string, any>[];
|
||||
mitre_technique: string | null;
|
||||
tags: string[];
|
||||
hunt_id: string | null;
|
||||
dataset_id: string | null;
|
||||
case_id: string | null;
|
||||
assignee: string | null;
|
||||
acknowledged_at: string | null;
|
||||
resolved_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface JobStats {
|
||||
export interface AlertStats {
|
||||
total: number;
|
||||
queued: number;
|
||||
by_status: Record<string, number>;
|
||||
workers: number;
|
||||
active_workers: number;
|
||||
severity_counts: Record<string, number>;
|
||||
status_counts: Record<string, number>;
|
||||
analyzer_counts: Record<string, number>;
|
||||
top_mitre: { technique: string; count: number }[];
|
||||
}
|
||||
|
||||
export interface LBNodeStatus {
|
||||
healthy: boolean;
|
||||
active_jobs: number;
|
||||
total_completed: number;
|
||||
total_errors: number;
|
||||
avg_latency_ms: number;
|
||||
last_check: number;
|
||||
}
|
||||
|
||||
export const analysis = {
|
||||
// Triage
|
||||
triageResults: (datasetId: string, minRisk = 0) =>
|
||||
api<TriageResultData[]>(`/api/analysis/triage/${datasetId}?min_risk=${minRisk}`),
|
||||
triggerTriage: (datasetId: string) =>
|
||||
api<{ status: string; dataset_id: string }>(`/api/analysis/triage/${datasetId}`, { method: 'POST' }),
|
||||
|
||||
// Host profiles
|
||||
hostProfiles: (huntId: string, minRisk = 0) =>
|
||||
api<HostProfileData[]>(`/api/analysis/profiles/${huntId}?min_risk=${minRisk}`),
|
||||
triggerAllProfiles: (huntId: string) =>
|
||||
api<{ status: string; hunt_id: string }>(`/api/analysis/profiles/${huntId}`, { method: 'POST' }),
|
||||
triggerHostProfile: (huntId: string, hostname: string) =>
|
||||
api<{ status: string }>(`/api/analysis/profiles/${huntId}/${encodeURIComponent(hostname)}`, { method: 'POST' }),
|
||||
|
||||
// Reports
|
||||
listReports: (huntId: string) =>
|
||||
api<HuntReportData[]>(`/api/analysis/reports/${huntId}`),
|
||||
getReport: (huntId: string, reportId: string) =>
|
||||
api<HuntReportData>(`/api/analysis/reports/${huntId}/${reportId}`),
|
||||
generateReport: (huntId: string) =>
|
||||
api<{ status: string; hunt_id: string }>(`/api/analysis/reports/${huntId}/generate`, { method: 'POST' }),
|
||||
|
||||
// Anomaly detection
|
||||
anomalies: (datasetId: string, outliersOnly = false) =>
|
||||
api<AnomalyResultData[]>(`/api/analysis/anomalies/${datasetId}?outliers_only=${outliersOnly}`),
|
||||
triggerAnomalyDetection: (datasetId: string, k = 3, threshold = 0.35) =>
|
||||
api<{ status: string; dataset_id: string }>(
|
||||
`/api/analysis/anomalies/${datasetId}?k=${k}&threshold=${threshold}`, { method: 'POST' },
|
||||
),
|
||||
|
||||
// IOC extraction
|
||||
extractIocs: (datasetId: string) =>
|
||||
api<{ dataset_id: string; iocs: Record<string, string[]>; total: number }>(
|
||||
`/api/analysis/iocs/${datasetId}`,
|
||||
),
|
||||
|
||||
// Host grouping
|
||||
hostGroups: (huntId: string) =>
|
||||
api<{ hunt_id: string; hosts: HostGroupData[] }>(
|
||||
`/api/analysis/hosts/${huntId}`,
|
||||
),
|
||||
|
||||
// Data query (Phase 9) - SSE streaming
|
||||
queryStream: async (datasetId: string, question: string, mode: string = 'quick'): Promise<Response> => {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (authToken) headers['Authorization'] = `Bearer ${authToken}`;
|
||||
return fetch(`${BASE}/api/analysis/query/${datasetId}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ question, mode }),
|
||||
});
|
||||
},
|
||||
|
||||
// Data query (Phase 9) - sync
|
||||
querySync: (datasetId: string, question: string, mode: string = 'quick') =>
|
||||
api<{ dataset_id: string; question: string; answer: string; mode: string }>(
|
||||
`/api/analysis/query/${datasetId}/sync`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ question, mode }),
|
||||
},
|
||||
),
|
||||
|
||||
// Job queue (Phase 10)
|
||||
listJobs: (status?: string, jobType?: string, limit = 50) => {
|
||||
const q = new URLSearchParams();
|
||||
if (status) q.set('status', status);
|
||||
if (jobType) q.set('job_type', jobType);
|
||||
q.set('limit', String(limit));
|
||||
return api<{ jobs: JobData[]; stats: JobStats }>(`/api/analysis/jobs?${q}`);
|
||||
},
|
||||
getJob: (jobId: string) =>
|
||||
api<JobData>(`/api/analysis/jobs/${jobId}`),
|
||||
cancelJob: (jobId: string) =>
|
||||
api<{ status: string; job_id: string }>(`/api/analysis/jobs/${jobId}`, { method: 'DELETE' }),
|
||||
submitJob: (jobType: string, params: Record<string, any> = {}) =>
|
||||
api<{ job_id: string; status: string; job_type: string }>(
|
||||
`/api/analysis/jobs/submit/${jobType}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(params),
|
||||
},
|
||||
),
|
||||
|
||||
// Load balancer (Phase 10)
|
||||
lbStatus: () =>
|
||||
api<Record<string, LBNodeStatus>>('/api/analysis/lb/status'),
|
||||
lbCheck: () =>
|
||||
api<Record<string, LBNodeStatus>>('/api/analysis/lb/check', { method: 'POST' }),
|
||||
};
|
||||
|
||||
// -- Network Topology --
|
||||
|
||||
export interface InventoryHost {
|
||||
export interface AlertRuleData {
|
||||
id: string;
|
||||
hostname: string;
|
||||
fqdn: string;
|
||||
client_id: string;
|
||||
ips: string[];
|
||||
os: string;
|
||||
users: string[];
|
||||
datasets: string[];
|
||||
row_count: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
analyzer: string;
|
||||
config: Record<string, any> | null;
|
||||
severity_override: string | null;
|
||||
enabled: boolean;
|
||||
hunt_id: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface InventoryConnection {
|
||||
source: string;
|
||||
target: string;
|
||||
target_ip: string;
|
||||
port: string;
|
||||
count: number;
|
||||
export interface AnalyzerInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface InventoryStats {
|
||||
total_hosts: number;
|
||||
total_datasets_scanned: number;
|
||||
datasets_with_hosts: number;
|
||||
total_rows_scanned: number;
|
||||
hosts_with_ips: number;
|
||||
hosts_with_users: number;
|
||||
}
|
||||
|
||||
export interface HostInventory {
|
||||
hosts: InventoryHost[];
|
||||
connections: InventoryConnection[];
|
||||
stats: InventoryStats;
|
||||
}
|
||||
|
||||
export const network = {
|
||||
hostInventory: (huntId: string) =>
|
||||
api<HostInventory>(`/api/network/host-inventory?hunt_id=${encodeURIComponent(huntId)}`),
|
||||
export interface AnalyzeResult {
|
||||
candidates_found: number;
|
||||
alerts_created: number;
|
||||
alerts: AlertData[];
|
||||
summary: {
|
||||
by_severity: Record<string, number>;
|
||||
by_analyzer: Record<string, number>;
|
||||
rows_analyzed: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const alerts = {
|
||||
list: (opts?: { status?: string; severity?: string; analyzer?: string; hunt_id?: string; dataset_id?: string; limit?: number; offset?: number }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (opts?.status) q.set('status', opts.status);
|
||||
if (opts?.severity) q.set('severity', opts.severity);
|
||||
if (opts?.analyzer) q.set('analyzer', opts.analyzer);
|
||||
if (opts?.hunt_id) q.set('hunt_id', opts.hunt_id);
|
||||
if (opts?.dataset_id) q.set('dataset_id', opts.dataset_id);
|
||||
if (opts?.limit) q.set('limit', String(opts.limit));
|
||||
if (opts?.offset) q.set('offset', String(opts.offset));
|
||||
return api<{ alerts: AlertData[]; total: number }>(`/api/alerts?${q}`);
|
||||
},
|
||||
stats: (huntId?: string) => {
|
||||
const q = huntId ? `?hunt_id=${encodeURIComponent(huntId)}` : '';
|
||||
return api<AlertStats>(`/api/alerts/stats${q}`);
|
||||
},
|
||||
get: (id: string) => api<AlertData>(`/api/alerts/${id}`),
|
||||
update: (id: string, data: { status?: string; severity?: string; assignee?: string; case_id?: string; tags?: string[] }) =>
|
||||
api<AlertData>(`/api/alerts/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id: string) =>
|
||||
api(`/api/alerts/${id}`, { method: 'DELETE' }),
|
||||
bulkUpdate: (alertIds: string[], status: string) =>
|
||||
api<{ updated: number }>(`/api/alerts/bulk-update?status=${encodeURIComponent(status)}`, {
|
||||
method: 'POST', body: JSON.stringify(alertIds),
|
||||
}),
|
||||
analyzers: () =>
|
||||
api<{ analyzers: AnalyzerInfo[] }>('/api/alerts/analyzers/list'),
|
||||
analyze: (params: { dataset_id?: string; hunt_id?: string; analyzers?: string[]; config?: Record<string, any>; auto_create?: boolean }) =>
|
||||
api<AnalyzeResult>('/api/alerts/analyze', {
|
||||
method: 'POST', body: JSON.stringify(params),
|
||||
}),
|
||||
listRules: (enabled?: boolean) => {
|
||||
const q = enabled !== undefined ? `?enabled=${enabled}` : '';
|
||||
return api<{ rules: AlertRuleData[] }>(`/api/alerts/rules/list${q}`);
|
||||
},
|
||||
createRule: (data: { name: string; description?: string; analyzer: string; config?: Record<string, any>; severity_override?: string; enabled?: boolean; hunt_id?: string }) =>
|
||||
api<AlertRuleData>('/api/alerts/rules', { method: 'POST', body: JSON.stringify(data) }),
|
||||
updateRule: (id: string, data: Partial<AlertRuleData>) =>
|
||||
api<AlertRuleData>(`/api/alerts/rules/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
deleteRule: (id: string) =>
|
||||
api(`/api/alerts/rules/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
// ── Case Management (continued) ──────────────────────────────────────
|
||||
|
||||
export interface CaseData {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
severity: string;
|
||||
tlp: string;
|
||||
pap: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
assignee: string | null;
|
||||
tags: string[];
|
||||
hunt_id: string | null;
|
||||
owner_id: string | null;
|
||||
mitre_techniques: string[];
|
||||
iocs: { type: string; value: string; description?: string }[];
|
||||
started_at: string | null;
|
||||
resolved_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
tasks: CaseTaskData[];
|
||||
}
|
||||
export interface CaseTaskData {
|
||||
id: string;
|
||||
case_id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
status: string;
|
||||
assignee: string | null;
|
||||
order: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
export interface ActivityLogEntry {
|
||||
id: number;
|
||||
action: string;
|
||||
details: Record<string, any> | null;
|
||||
user_id: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const cases = {
|
||||
list: (opts?: { status?: string; hunt_id?: string; limit?: number; offset?: number }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (opts?.status) q.set('status', opts.status);
|
||||
if (opts?.hunt_id) q.set('hunt_id', opts.hunt_id);
|
||||
if (opts?.limit) q.set('limit', String(opts.limit));
|
||||
if (opts?.offset) q.set('offset', String(opts.offset));
|
||||
return api<{ cases: CaseData[]; total: number }>(`/api/cases?${q}`);
|
||||
},
|
||||
get: (id: string) => api<CaseData>(`/api/cases/${id}`),
|
||||
create: (data: Partial<CaseData>) =>
|
||||
api<CaseData>('/api/cases', { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: (id: string, data: Partial<CaseData>) =>
|
||||
api<CaseData>(`/api/cases/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id: string) =>
|
||||
api(`/api/cases/${id}`, { method: 'DELETE' }),
|
||||
addTask: (caseId: string, data: { title: string; description?: string; assignee?: string }) =>
|
||||
api<CaseTaskData>(`/api/cases/${caseId}/tasks`, { method: 'POST', body: JSON.stringify(data) }),
|
||||
updateTask: (caseId: string, taskId: string, data: Partial<CaseTaskData>) =>
|
||||
api<CaseTaskData>(`/api/cases/${caseId}/tasks/${taskId}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
deleteTask: (caseId: string, taskId: string) =>
|
||||
api(`/api/cases/${caseId}/tasks/${taskId}`, { method: 'DELETE' }),
|
||||
activity: (caseId: string, limit = 50) =>
|
||||
api<{ logs: ActivityLogEntry[] }>(`/api/cases/${caseId}/activity?limit=${limit}`),
|
||||
};
|
||||
|
||||
// ── Notebooks & Playbooks ────────────────────────────────────────────
|
||||
|
||||
export interface NotebookCell {
|
||||
id: string;
|
||||
cell_type: string;
|
||||
source: string;
|
||||
output: string | null;
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
export interface NotebookData {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
cells: NotebookCell[];
|
||||
hunt_id: string | null;
|
||||
case_id: string | null;
|
||||
owner_id: string | null;
|
||||
tags: string[];
|
||||
cell_count: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
export interface PlaybookTemplate {
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
step_count: number;
|
||||
}
|
||||
export interface PlaybookStep {
|
||||
order: number;
|
||||
title: string;
|
||||
description: string;
|
||||
action: string;
|
||||
action_config: Record<string, any>;
|
||||
expected_outcome: string;
|
||||
}
|
||||
export interface PlaybookTemplateDetail extends PlaybookTemplate {
|
||||
steps: PlaybookStep[];
|
||||
}
|
||||
export interface PlaybookRunData {
|
||||
id: string;
|
||||
playbook_name: string;
|
||||
status: string;
|
||||
current_step: number;
|
||||
total_steps: number;
|
||||
step_results: { step: number; status: string; notes: string | null; completed_at: string }[];
|
||||
hunt_id: string | null;
|
||||
case_id: string | null;
|
||||
started_by: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
completed_at: string | null;
|
||||
steps?: PlaybookStep[];
|
||||
}
|
||||
|
||||
export const notebooks = {
|
||||
list: (opts?: { hunt_id?: string; limit?: number; offset?: number }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (opts?.hunt_id) q.set('hunt_id', opts.hunt_id);
|
||||
if (opts?.limit) q.set('limit', String(opts.limit));
|
||||
if (opts?.offset) q.set('offset', String(opts.offset));
|
||||
return api<{ notebooks: NotebookData[]; total: number }>(`/api/notebooks?${q}`);
|
||||
},
|
||||
get: (id: string) => api<NotebookData>(`/api/notebooks/${id}`),
|
||||
create: (data: { title: string; description?: string; cells?: Partial<NotebookCell>[]; hunt_id?: string; case_id?: string; tags?: string[] }) =>
|
||||
api<NotebookData>('/api/notebooks', { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: (id: string, data: { title?: string; description?: string; cells?: Partial<NotebookCell>[]; tags?: string[] }) =>
|
||||
api<NotebookData>(`/api/notebooks/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
upsertCell: (notebookId: string, cell: { cell_id: string; cell_type?: string; source?: string; output?: string; metadata?: Record<string, any> }) =>
|
||||
api<NotebookData>(`/api/notebooks/${notebookId}/cells`, { method: 'POST', body: JSON.stringify(cell) }),
|
||||
deleteCell: (notebookId: string, cellId: string) =>
|
||||
api(`/api/notebooks/${notebookId}/cells/${cellId}`, { method: 'DELETE' }),
|
||||
delete: (id: string) =>
|
||||
api(`/api/notebooks/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
|
||||
export const playbooks = {
|
||||
templates: () =>
|
||||
api<{ templates: PlaybookTemplate[] }>('/api/notebooks/playbooks/templates'),
|
||||
templateDetail: (name: string) =>
|
||||
api<PlaybookTemplateDetail>(`/api/notebooks/playbooks/templates/${encodeURIComponent(name)}`),
|
||||
start: (data: { playbook_name: string; hunt_id?: string; case_id?: string; started_by?: string }) =>
|
||||
api<PlaybookRunData>('/api/notebooks/playbooks/start', { method: 'POST', body: JSON.stringify(data) }),
|
||||
listRuns: (opts?: { status?: string; hunt_id?: string }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (opts?.status) q.set('status', opts.status);
|
||||
if (opts?.hunt_id) q.set('hunt_id', opts.hunt_id);
|
||||
return api<{ runs: PlaybookRunData[] }>(`/api/notebooks/playbooks/runs?${q}`);
|
||||
},
|
||||
getRun: (runId: string) =>
|
||||
api<PlaybookRunData>(`/api/notebooks/playbooks/runs/${runId}`),
|
||||
completeStep: (runId: string, data: { notes?: string; status?: string }) =>
|
||||
api<PlaybookRunData>(`/api/notebooks/playbooks/runs/${runId}/complete-step`, {
|
||||
method: 'POST', body: JSON.stringify(data),
|
||||
}),
|
||||
abortRun: (runId: string) =>
|
||||
api<PlaybookRunData>(`/api/notebooks/playbooks/runs/${runId}/abort`, { method: 'POST' }),
|
||||
};
|
||||
556
frontend/src/components/AlertPanel.tsx
Normal file
@@ -0,0 +1,556 @@
|
||||
/**
|
||||
* AlertPanel — Security alert management with analyzer controls.
|
||||
*
|
||||
* Features:
|
||||
* - Alert list with severity/status filtering and sorting
|
||||
* - Run analyzers on demand against datasets/hunts
|
||||
* - Alert detail drill-down with evidence viewer
|
||||
* - Bulk acknowledge/resolve/false-positive actions
|
||||
* - Alert rules management (auto-trigger analyzers)
|
||||
* - Stats dashboard (severity/status/analyzer breakdowns)
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Button, Chip, IconButton, Tooltip,
|
||||
Select, MenuItem, FormControl, InputLabel, Stack, Divider,
|
||||
Dialog, DialogTitle, DialogContent, DialogActions, TextField,
|
||||
LinearProgress, Alert as MuiAlert, Card, CardContent, Tabs, Tab,
|
||||
Checkbox, FormControlLabel, Switch, Badge,
|
||||
} from '@mui/material';
|
||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ReportProblemIcon from '@mui/icons-material/ReportProblem';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import RuleIcon from '@mui/icons-material/Rule';
|
||||
import BarChartIcon from '@mui/icons-material/BarChart';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import {
|
||||
alerts, hunts, datasets,
|
||||
AlertData, AlertStats, AlertRuleData, AnalyzerInfo,
|
||||
Hunt, DatasetSummary,
|
||||
} from '../api/client';
|
||||
|
||||
const SEV_COLORS: Record<string, 'error' | 'warning' | 'info' | 'success' | 'default'> = {
|
||||
critical: 'error', high: 'warning', medium: 'info', low: 'success', info: 'default',
|
||||
};
|
||||
const STATUS_COLORS: Record<string, 'error' | 'warning' | 'info' | 'success' | 'default'> = {
|
||||
new: 'error', acknowledged: 'warning', 'in-progress': 'info',
|
||||
resolved: 'success', 'false-positive': 'default',
|
||||
};
|
||||
|
||||
export default function AlertPanel() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
// Tab state
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
// Alerts list
|
||||
const [alertList, setAlertList] = useState<AlertData[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sevFilter, setSevFilter] = useState<string>('');
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
// Stats
|
||||
const [stats, setStats] = useState<AlertStats | null>(null);
|
||||
|
||||
// Hunt/dataset selectors
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
const [dsList, setDsList] = useState<DatasetSummary[]>([]);
|
||||
const [huntId, setHuntId] = useState('');
|
||||
const [datasetId, setDatasetId] = useState('');
|
||||
|
||||
// Analyzers
|
||||
const [analyzerList, setAnalyzerList] = useState<AnalyzerInfo[]>([]);
|
||||
const [analyzing, setAnalyzing] = useState(false);
|
||||
|
||||
// Rules
|
||||
const [rules, setRules] = useState<AlertRuleData[]>([]);
|
||||
const [ruleDialog, setRuleDialog] = useState(false);
|
||||
const [ruleForm, setRuleForm] = useState({ name: '', description: '', analyzer: '', enabled: true });
|
||||
|
||||
// Detail dialog
|
||||
const [detailAlert, setDetailAlert] = useState<AlertData | null>(null);
|
||||
|
||||
// ── Load data ──────────────────────────────────────────────────────
|
||||
|
||||
const loadAlerts = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const opts: any = { limit: 200 };
|
||||
if (sevFilter) opts.severity = sevFilter;
|
||||
if (statusFilter) opts.status = statusFilter;
|
||||
if (huntId) opts.hunt_id = huntId;
|
||||
if (datasetId) opts.dataset_id = datasetId;
|
||||
const res = await alerts.list(opts);
|
||||
setAlertList(res.alerts);
|
||||
setTotal(res.total);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [sevFilter, statusFilter, huntId, datasetId, enqueueSnackbar]);
|
||||
|
||||
const loadStats = useCallback(async () => {
|
||||
try {
|
||||
const s = await alerts.stats(huntId || undefined);
|
||||
setStats(s);
|
||||
} catch {}
|
||||
}, [huntId]);
|
||||
|
||||
const loadRules = useCallback(async () => {
|
||||
try {
|
||||
const res = await alerts.listRules();
|
||||
setRules(res.rules);
|
||||
} catch {}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
hunts.list().then(r => setHuntList(r.hunts)).catch(() => {});
|
||||
datasets.list(0, 500).then(r => setDsList(r.datasets)).catch(() => {});
|
||||
alerts.analyzers().then(r => setAnalyzerList(r.analyzers)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => { loadAlerts(); loadStats(); }, [loadAlerts, loadStats]);
|
||||
useEffect(() => { loadRules(); }, [loadRules]);
|
||||
|
||||
// ── Analyze ────────────────────────────────────────────────────────
|
||||
|
||||
const runAnalysis = async () => {
|
||||
if (!datasetId && !huntId) {
|
||||
enqueueSnackbar('Select a hunt or dataset first', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
setAnalyzing(true);
|
||||
try {
|
||||
const res = await alerts.analyze({
|
||||
dataset_id: datasetId || undefined,
|
||||
hunt_id: huntId || undefined,
|
||||
auto_create: true,
|
||||
});
|
||||
enqueueSnackbar(
|
||||
`Analysis complete: ${res.candidates_found} findings, ${res.alerts_created} alerts created`,
|
||||
{ variant: 'success' },
|
||||
);
|
||||
loadAlerts();
|
||||
loadStats();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
} finally {
|
||||
setAnalyzing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Bulk actions ───────────────────────────────────────────────────
|
||||
|
||||
const bulkAction = async (status: string) => {
|
||||
if (!selected.length) return;
|
||||
try {
|
||||
await alerts.bulkUpdate(selected, status);
|
||||
enqueueSnackbar(`${selected.length} alerts → ${status}`, { variant: 'success' });
|
||||
setSelected([]);
|
||||
loadAlerts();
|
||||
loadStats();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Single alert actions ───────────────────────────────────────────
|
||||
|
||||
const updateStatus = async (id: string, status: string) => {
|
||||
try {
|
||||
await alerts.update(id, { status });
|
||||
loadAlerts();
|
||||
loadStats();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAlert = async (id: string) => {
|
||||
try {
|
||||
await alerts.delete(id);
|
||||
loadAlerts();
|
||||
loadStats();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Rules ──────────────────────────────────────────────────────────
|
||||
|
||||
const createRule = async () => {
|
||||
if (!ruleForm.name || !ruleForm.analyzer) return;
|
||||
try {
|
||||
await alerts.createRule({
|
||||
name: ruleForm.name,
|
||||
description: ruleForm.description,
|
||||
analyzer: ruleForm.analyzer,
|
||||
enabled: ruleForm.enabled,
|
||||
});
|
||||
enqueueSnackbar('Rule created', { variant: 'success' });
|
||||
setRuleDialog(false);
|
||||
setRuleForm({ name: '', description: '', analyzer: '', enabled: true });
|
||||
loadRules();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRule = async (rule: AlertRuleData) => {
|
||||
try {
|
||||
await alerts.updateRule(rule.id, { enabled: !rule.enabled } as any);
|
||||
loadRules();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRule = async (id: string) => {
|
||||
try {
|
||||
await alerts.deleteRule(id);
|
||||
loadRules();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── DataGrid columns ──────────────────────────────────────────────
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: 'severity', headerName: 'Sev', width: 90,
|
||||
renderCell: (p) => <Chip label={p.value} size="small" color={SEV_COLORS[p.value] || 'default'} />,
|
||||
},
|
||||
{
|
||||
field: 'status', headerName: 'Status', width: 110,
|
||||
renderCell: (p) => <Chip label={p.value} size="small" color={STATUS_COLORS[p.value] || 'default'} variant="outlined" />,
|
||||
},
|
||||
{ field: 'title', headerName: 'Title', flex: 1, minWidth: 250 },
|
||||
{ field: 'analyzer', headerName: 'Analyzer', width: 140 },
|
||||
{
|
||||
field: 'score', headerName: 'Score', width: 80, type: 'number',
|
||||
renderCell: (p) => <Typography variant="body2" fontWeight="bold">{Math.round(p.value)}</Typography>,
|
||||
},
|
||||
{ field: 'mitre_technique', headerName: 'MITRE', width: 90 },
|
||||
{
|
||||
field: 'created_at', headerName: 'Created', width: 150,
|
||||
valueFormatter: (value: string) => value ? new Date(value).toLocaleString() : '',
|
||||
},
|
||||
{
|
||||
field: 'actions', headerName: '', width: 160, sortable: false,
|
||||
renderCell: (p) => (
|
||||
<Stack direction="row" spacing={0.5}>
|
||||
<Tooltip title="View"><IconButton size="small" onClick={() => setDetailAlert(p.row)}><VisibilityIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title="Acknowledge"><IconButton size="small" color="warning" onClick={() => updateStatus(p.row.id, 'acknowledged')}><CheckCircleIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title="Resolve"><IconButton size="small" color="success" onClick={() => updateStatus(p.row.id, 'resolved')}><CheckCircleIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title="Delete"><IconButton size="small" color="error" onClick={() => deleteAlert(p.row.id)}><DeleteIcon fontSize="small" /></IconButton></Tooltip>
|
||||
</Stack>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// ── Stats cards ────────────────────────────────────────────────────
|
||||
|
||||
const StatsView = () => {
|
||||
if (!stats) return <Typography color="text.secondary">No stats available</Typography>;
|
||||
return (
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 2 }}>
|
||||
<Card><CardContent>
|
||||
<Typography variant="subtitle2" color="text.secondary">Total Alerts</Typography>
|
||||
<Typography variant="h3">{stats.total}</Typography>
|
||||
</CardContent></Card>
|
||||
|
||||
{['critical', 'high', 'medium', 'low', 'info'].map(sev => (
|
||||
<Card key={sev}><CardContent>
|
||||
<Typography variant="subtitle2" color="text.secondary">{sev.toUpperCase()}</Typography>
|
||||
<Typography variant="h4" color={SEV_COLORS[sev] === 'error' ? 'error' : SEV_COLORS[sev] === 'warning' ? 'warning.main' : 'text.primary'}>
|
||||
{stats.severity_counts[sev] || 0}
|
||||
</Typography>
|
||||
</CardContent></Card>
|
||||
))}
|
||||
|
||||
<Card sx={{ gridColumn: 'span 2' }}><CardContent>
|
||||
<Typography variant="subtitle2" gutterBottom>By Status</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
{Object.entries(stats.status_counts).map(([s, c]) => (
|
||||
<Chip key={s} label={`${s}: ${c}`} color={STATUS_COLORS[s] || 'default'} size="small" />
|
||||
))}
|
||||
</Stack>
|
||||
</CardContent></Card>
|
||||
|
||||
<Card sx={{ gridColumn: 'span 2' }}><CardContent>
|
||||
<Typography variant="subtitle2" gutterBottom>By Analyzer</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
{Object.entries(stats.analyzer_counts).map(([a, c]) => (
|
||||
<Chip key={a} label={`${a}: ${c}`} size="small" variant="outlined" />
|
||||
))}
|
||||
</Stack>
|
||||
</CardContent></Card>
|
||||
|
||||
{stats.top_mitre.length > 0 && (
|
||||
<Card sx={{ gridColumn: 'span 2' }}><CardContent>
|
||||
<Typography variant="subtitle2" gutterBottom>Top MITRE Techniques</Typography>
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
{stats.top_mitre.map(m => (
|
||||
<Chip key={m.technique} label={`${m.technique} (${m.count})`} size="small" color="error" variant="outlined" />
|
||||
))}
|
||||
</Stack>
|
||||
</CardContent></Card>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Rules view ─────────────────────────────────────────────────────
|
||||
|
||||
const RulesView = () => (
|
||||
<Box>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
|
||||
<Typography variant="h6">Alert Rules ({rules.length})</Typography>
|
||||
<Button variant="contained" startIcon={<RuleIcon />} onClick={() => setRuleDialog(true)}>
|
||||
New Rule
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
{rules.map(rule => (
|
||||
<Paper key={rule.id} sx={{ p: 2, mb: 1 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" fontWeight="bold">
|
||||
{rule.name}
|
||||
<Chip label={rule.analyzer} size="small" sx={{ ml: 1 }} />
|
||||
{rule.severity_override && <Chip label={rule.severity_override} size="small" color={SEV_COLORS[rule.severity_override] || 'default'} sx={{ ml: 0.5 }} />}
|
||||
</Typography>
|
||||
{rule.description && <Typography variant="body2" color="text.secondary">{rule.description}</Typography>}
|
||||
</Box>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Switch checked={rule.enabled} onChange={() => toggleRule(rule)} size="small" />
|
||||
<IconButton size="small" color="error" onClick={() => deleteRule(rule.id)}><DeleteIcon fontSize="small" /></IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
|
||||
{rules.length === 0 && (
|
||||
<Typography color="text.secondary" textAlign="center" py={4}>
|
||||
No alert rules configured. Create a rule to auto-trigger analyzers.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{/* Header */}
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h5">
|
||||
<Badge badgeContent={stats?.severity_counts?.critical || 0} color="error" sx={{ mr: 2 }}>
|
||||
<ReportProblemIcon />
|
||||
</Badge>
|
||||
Alerts & Analyzers
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<Button variant="outlined" startIcon={<RefreshIcon />} onClick={() => { loadAlerts(); loadStats(); }}>
|
||||
Refresh
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Selector bar */}
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select value={huntId} label="Hunt" onChange={e => { setHuntId(e.target.value); setDatasetId(''); }}>
|
||||
<MenuItem value="">All hunts</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
||||
<InputLabel>Dataset</InputLabel>
|
||||
<Select value={datasetId} label="Dataset" onChange={e => setDatasetId(e.target.value)}>
|
||||
<MenuItem value="">All datasets</MenuItem>
|
||||
{dsList.map(d => <MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="warning"
|
||||
startIcon={<PlayArrowIcon />}
|
||||
onClick={runAnalysis}
|
||||
disabled={analyzing || (!huntId && !datasetId)}
|
||||
>
|
||||
{analyzing ? 'Analyzing…' : 'Run Analyzers'}
|
||||
</Button>
|
||||
|
||||
<Divider orientation="vertical" flexItem />
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||
<InputLabel>Severity</InputLabel>
|
||||
<Select value={sevFilter} label="Severity" onChange={e => setSevFilter(e.target.value)}>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{['critical', 'high', 'medium', 'low', 'info'].map(s => <MenuItem key={s} value={s}>{s}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 140 }}>
|
||||
<InputLabel>Status</InputLabel>
|
||||
<Select value={statusFilter} label="Status" onChange={e => setStatusFilter(e.target.value)}>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{['new', 'acknowledged', 'in-progress', 'resolved', 'false-positive'].map(s => <MenuItem key={s} value={s}>{s}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
{analyzing && <LinearProgress sx={{ mt: 1 }} />}
|
||||
</Paper>
|
||||
|
||||
{/* Tabs */}
|
||||
<Tabs value={tab} onChange={(_, v) => setTab(v)}>
|
||||
<Tab label={`Alerts (${total})`} />
|
||||
<Tab icon={<BarChartIcon />} label="Stats" />
|
||||
<Tab icon={<RuleIcon />} label={`Rules (${rules.length})`} />
|
||||
</Tabs>
|
||||
|
||||
{/* Tab panels */}
|
||||
{tab === 0 && (
|
||||
<Box>
|
||||
{/* Bulk actions */}
|
||||
{selected.length > 0 && (
|
||||
<Paper sx={{ p: 1, mb: 1, bgcolor: 'action.hover' }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Typography variant="body2">{selected.length} selected</Typography>
|
||||
<Button size="small" onClick={() => bulkAction('acknowledged')}>Acknowledge</Button>
|
||||
<Button size="small" onClick={() => bulkAction('resolved')}>Resolve</Button>
|
||||
<Button size="small" color="error" onClick={() => bulkAction('false-positive')}>False Positive</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
<Paper sx={{ height: 600 }}>
|
||||
{loading && <LinearProgress />}
|
||||
<DataGrid
|
||||
rows={alertList}
|
||||
columns={columns}
|
||||
density="compact"
|
||||
checkboxSelection
|
||||
onRowSelectionModelChange={(model) => setSelected(Array.from(model.ids) as string[])}
|
||||
pageSizeOptions={[25, 50, 100]}
|
||||
initialState={{ pagination: { paginationModel: { pageSize: 50 } } }}
|
||||
sx={{ border: 'none' }}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{tab === 1 && <StatsView />}
|
||||
{tab === 2 && <RulesView />}
|
||||
|
||||
{/* Detail dialog */}
|
||||
<Dialog open={!!detailAlert} onClose={() => setDetailAlert(null)} maxWidth="md" fullWidth>
|
||||
{detailAlert && (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<Chip label={detailAlert.severity} color={SEV_COLORS[detailAlert.severity] || 'default'} size="small" sx={{ mr: 1 }} />
|
||||
{detailAlert.title}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Typography variant="body1" gutterBottom>{detailAlert.description}</Typography>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
<Stack direction="row" spacing={2} mb={2}>
|
||||
<Chip label={`Analyzer: ${detailAlert.analyzer}`} variant="outlined" />
|
||||
<Chip label={`Score: ${Math.round(detailAlert.score)}`} variant="outlined" />
|
||||
{detailAlert.mitre_technique && <Chip label={detailAlert.mitre_technique} color="error" variant="outlined" />}
|
||||
<Chip label={detailAlert.status} color={STATUS_COLORS[detailAlert.status] || 'default'} />
|
||||
</Stack>
|
||||
|
||||
{detailAlert.tags?.length > 0 && (
|
||||
<Stack direction="row" spacing={0.5} mb={2}>
|
||||
{detailAlert.tags.map((t, i) => <Chip key={i} label={t} size="small" />)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>Evidence</Typography>
|
||||
<Paper variant="outlined" sx={{ p: 2, maxHeight: 300, overflow: 'auto' }}>
|
||||
<pre style={{ margin: 0, fontSize: '0.8rem', whiteSpace: 'pre-wrap' }}>
|
||||
{JSON.stringify(detailAlert.evidence, null, 2)}
|
||||
</pre>
|
||||
</Paper>
|
||||
|
||||
<Stack direction="row" spacing={2} mt={2}>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Created: {new Date(detailAlert.created_at).toLocaleString()}
|
||||
</Typography>
|
||||
{detailAlert.acknowledged_at && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Acknowledged: {new Date(detailAlert.acknowledged_at).toLocaleString()}
|
||||
</Typography>
|
||||
)}
|
||||
{detailAlert.resolved_at && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Resolved: {new Date(detailAlert.resolved_at).toLocaleString()}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => { updateStatus(detailAlert.id, 'acknowledged'); setDetailAlert(null); }}>
|
||||
Acknowledge
|
||||
</Button>
|
||||
<Button onClick={() => { updateStatus(detailAlert.id, 'resolved'); setDetailAlert(null); }} color="success">
|
||||
Resolve
|
||||
</Button>
|
||||
<Button onClick={() => { updateStatus(detailAlert.id, 'false-positive'); setDetailAlert(null); }}>
|
||||
False Positive
|
||||
</Button>
|
||||
<Button onClick={() => setDetailAlert(null)}>Close</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Dialog>
|
||||
|
||||
{/* Create rule dialog */}
|
||||
<Dialog open={ruleDialog} onClose={() => setRuleDialog(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Create Alert Rule</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} mt={1}>
|
||||
<TextField
|
||||
label="Rule Name" fullWidth required
|
||||
value={ruleForm.name} onChange={e => setRuleForm(f => ({ ...f, name: e.target.value }))}
|
||||
/>
|
||||
<TextField
|
||||
label="Description" fullWidth multiline rows={2}
|
||||
value={ruleForm.description} onChange={e => setRuleForm(f => ({ ...f, description: e.target.value }))}
|
||||
/>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Analyzer</InputLabel>
|
||||
<Select value={ruleForm.analyzer} label="Analyzer" onChange={e => setRuleForm(f => ({ ...f, analyzer: e.target.value }))}>
|
||||
{analyzerList.map(a => <MenuItem key={a.name} value={a.name}>{a.name} — {a.description}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={ruleForm.enabled} onChange={e => setRuleForm(f => ({ ...f, enabled: e.target.checked }))} />}
|
||||
label="Enabled"
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setRuleDialog(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={createRule} disabled={!ruleForm.name || !ruleForm.analyzer}>
|
||||
Create
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
315
frontend/src/components/AnalysisPanel.tsx
Normal file
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* AnalysisPanel — LLM-powered threat analysis of datasets.
|
||||
*
|
||||
* Replaces the old EnrichmentPanel (which required VT/AbuseIPDB/Shodan API
|
||||
* keys). Uses Wile (70B) and Roadrunner (fast) to perform deep threat analysis
|
||||
* of uploaded forensic data, returning structured findings, IOCs, MITRE
|
||||
* techniques, and actionable recommendations — all rendered in markdown.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Paper, Typography, Stack, Alert, CircularProgress, Chip,
|
||||
FormControl, InputLabel, Select, MenuItem, TextField, Button,
|
||||
Divider, LinearProgress, Grid, ToggleButton, ToggleButtonGroup,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import SpeedIcon from '@mui/icons-material/Speed';
|
||||
import PsychologyIcon from '@mui/icons-material/Psychology';
|
||||
import SecurityIcon from '@mui/icons-material/Security';
|
||||
import BugReportIcon from '@mui/icons-material/BugReport';
|
||||
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import {
|
||||
analysis, hunts, datasets, type Hunt, type DatasetSummary,
|
||||
type LLMAnalysisResult,
|
||||
} from '../api/client';
|
||||
|
||||
const FOCUS_OPTIONS = [
|
||||
{ value: '', label: 'General', icon: <SecurityIcon fontSize="small" /> },
|
||||
{ value: 'threats', label: 'Threats', icon: <BugReportIcon fontSize="small" /> },
|
||||
{ value: 'anomalies', label: 'Anomalies', icon: <TravelExploreIcon fontSize="small" /> },
|
||||
{ value: 'lateral_movement', label: 'Lateral Mvmt', icon: <SecurityIcon fontSize="small" /> },
|
||||
{ value: 'exfil', label: 'Exfiltration', icon: <SecurityIcon fontSize="small" /> },
|
||||
{ value: 'persistence', label: 'Persistence', icon: <SecurityIcon fontSize="small" /> },
|
||||
{ value: 'recon', label: 'Recon', icon: <TravelExploreIcon fontSize="small" /> },
|
||||
];
|
||||
|
||||
const SEV_COLORS: Record<string, string> = {
|
||||
critical: '#ef4444', high: '#f97316', medium: '#eab308', low: '#3b82f6', info: '#6b7280',
|
||||
};
|
||||
|
||||
function RiskGauge({ score }: { score: number }) {
|
||||
const color = score >= 75 ? '#ef4444' : score >= 50 ? '#f97316' : score >= 25 ? '#eab308' : '#10b981';
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress variant="determinate" value={score} size={80}
|
||||
thickness={5} sx={{ color }} />
|
||||
<Box sx={{
|
||||
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, color }}>{score}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" display="block" color="text.secondary">Risk Score</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AnalysisPanel() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
const [datasetList, setDatasetList] = useState<DatasetSummary[]>([]);
|
||||
const [selectedHunt, setSelectedHunt] = useState('');
|
||||
const [selectedDataset, setSelectedDataset] = useState('');
|
||||
const [question, setQuestion] = useState('');
|
||||
const [mode, setMode] = useState<'quick' | 'deep'>('deep');
|
||||
const [focus, setFocus] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState<LLMAnalysisResult | null>(null);
|
||||
|
||||
/* load hunts + datasets */
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const [h, d] = await Promise.all([
|
||||
hunts.list(0, 100),
|
||||
datasets.list(0, 200),
|
||||
]);
|
||||
setHuntList(h.hunts);
|
||||
setDatasetList(d.datasets);
|
||||
if (h.hunts.length > 0) setSelectedHunt(h.hunts[0].id);
|
||||
} catch {}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const huntDatasets = selectedHunt
|
||||
? datasetList.filter(d => d.hunt_id === selectedHunt)
|
||||
: datasetList;
|
||||
|
||||
const runAnalysis = useCallback(async () => {
|
||||
if (!selectedHunt && !selectedDataset) {
|
||||
enqueueSnackbar('Select a hunt or dataset', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await analysis.llmAnalyze({
|
||||
dataset_id: selectedDataset || undefined,
|
||||
hunt_id: selectedDataset ? undefined : selectedHunt,
|
||||
question: question || undefined,
|
||||
mode,
|
||||
focus: focus || undefined,
|
||||
});
|
||||
setResult(res);
|
||||
enqueueSnackbar(`Analysis complete in ${(res.latency_ms / 1000).toFixed(1)}s`, { variant: 'success' });
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message || 'Analysis failed', { variant: 'error' });
|
||||
}
|
||||
setLoading(false);
|
||||
}, [selectedHunt, selectedDataset, question, mode, focus, enqueueSnackbar]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>LLM Threat Analysis</Typography>
|
||||
|
||||
{/* Controls */}
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Stack spacing={2}>
|
||||
<Stack direction="row" spacing={1.5} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select label="Hunt" value={selectedHunt}
|
||||
onChange={e => { setSelectedHunt(e.target.value); setSelectedDataset(''); }}>
|
||||
{huntList.map(h => (
|
||||
<MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
||||
<InputLabel>Dataset</InputLabel>
|
||||
<Select label="Dataset" value={selectedDataset}
|
||||
onChange={e => setSelectedDataset(e.target.value)}>
|
||||
<MenuItem value="">All in hunt</MenuItem>
|
||||
{huntDatasets.map(d => (
|
||||
<MenuItem key={d.id} value={d.id}>
|
||||
{d.name} ({d.row_count} rows)
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<ToggleButtonGroup size="small" value={mode} exclusive
|
||||
onChange={(_, v) => v && setMode(v)}>
|
||||
<ToggleButton value="quick">
|
||||
<Tooltip title="Quick (Roadrunner fast model)"><SpeedIcon fontSize="small" /></Tooltip>
|
||||
</ToggleButton>
|
||||
<ToggleButton value="deep">
|
||||
<Tooltip title="Deep (Wile 70B model)"><PsychologyIcon fontSize="small" /></Tooltip>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 150 }}>
|
||||
<InputLabel>Focus</InputLabel>
|
||||
<Select label="Focus" value={focus} onChange={e => setFocus(e.target.value)}>
|
||||
{FOCUS_OPTIONS.map(f => (
|
||||
<MenuItem key={f.value} value={f.value}>{f.label}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={1.5} alignItems="center">
|
||||
<TextField
|
||||
size="small" fullWidth
|
||||
label="Ask a specific question (optional)"
|
||||
placeholder="e.g. Is there evidence of lateral movement via PsExec?"
|
||||
value={question}
|
||||
onChange={e => setQuestion(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && runAnalysis()}
|
||||
/>
|
||||
<Button
|
||||
variant="contained" startIcon={loading ? <CircularProgress size={16} /> : <PlayArrowIcon />}
|
||||
onClick={runAnalysis} disabled={loading || (!selectedHunt && !selectedDataset)}
|
||||
sx={{ minWidth: 140 }}
|
||||
>
|
||||
{loading ? 'Analyzing...' : 'Analyze'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{loading && <LinearProgress sx={{ mt: 1 }} />}
|
||||
</Paper>
|
||||
|
||||
{/* Results */}
|
||||
{result && (
|
||||
<Grid container spacing={2}>
|
||||
{/* Left: main analysis */}
|
||||
<Grid size={{ xs: 12, md: 8 }}>
|
||||
<Paper sx={{ p: 2.5, maxHeight: 'calc(100vh - 320px)', overflow: 'auto' }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 2 }}>
|
||||
<RiskGauge score={result.risk_score} />
|
||||
<Box sx={{ flex: 1, ml: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{result.rows_analyzed.toLocaleString()} rows analyzed • {result.model_used} on {result.node_used}
|
||||
• {(result.latency_ms / 1000).toFixed(1)}s
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Confidence: {(result.confidence * 100).toFixed(0)}%
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Box sx={{
|
||||
'& h1': { fontSize: '1.4rem', mt: 2, mb: 1 },
|
||||
'& h2': { fontSize: '1.2rem', mt: 2, mb: 1 },
|
||||
'& h3': { fontSize: '1rem', mt: 1.5, mb: 0.5 },
|
||||
'& p': { mb: 1 },
|
||||
'& ul, & ol': { pl: 3, mb: 1 },
|
||||
'& code': { bgcolor: 'action.hover', px: 0.5, borderRadius: 0.5, fontFamily: 'monospace', fontSize: '0.85em' },
|
||||
'& pre': { bgcolor: 'background.default', p: 1.5, borderRadius: 1, overflow: 'auto' },
|
||||
'& table': { width: '100%', borderCollapse: 'collapse', mb: 2 },
|
||||
'& th, & td': { border: '1px solid', borderColor: 'divider', p: 0.5, fontSize: '0.85rem' },
|
||||
}}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{result.analysis}
|
||||
</ReactMarkdown>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{/* Right: structured findings */}
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<Stack spacing={2}>
|
||||
{/* Key findings */}
|
||||
{result.key_findings.length > 0 && (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Key Findings</Typography>
|
||||
<Stack spacing={0.5}>
|
||||
{result.key_findings.map((f, i) => (
|
||||
<Alert key={i} severity="warning" variant="outlined" sx={{ py: 0 }}>
|
||||
<Typography variant="body2">{f}</Typography>
|
||||
</Alert>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* IOCs identified */}
|
||||
{result.iocs_identified.length > 0 && (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>IOCs Identified</Typography>
|
||||
<Stack spacing={0.5}>
|
||||
{result.iocs_identified.map((ioc, i) => (
|
||||
<Stack key={i} direction="row" spacing={0.5} alignItems="center">
|
||||
<Chip label={ioc.type} size="small" color="error" variant="outlined" />
|
||||
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12 }}>
|
||||
{ioc.value}
|
||||
</Typography>
|
||||
{ioc.context && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
— {ioc.context}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* MITRE techniques */}
|
||||
{result.mitre_techniques.length > 0 && (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>MITRE ATT&CK</Typography>
|
||||
<Stack direction="row" spacing={0.5} flexWrap="wrap" useFlexGap>
|
||||
{result.mitre_techniques.map((t, i) => (
|
||||
<Chip key={i} label={t} size="small" color="info" variant="outlined"
|
||||
sx={{ fontSize: 11 }} />
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* Recommended actions */}
|
||||
{result.recommended_actions.length > 0 && (
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Recommended Actions</Typography>
|
||||
<Stack spacing={0.5}>
|
||||
{result.recommended_actions.map((a, i) => (
|
||||
<Typography key={i} variant="body2">
|
||||
{i + 1}. {a}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* Empty state */}
|
||||
{!result && !loading && (
|
||||
<Paper sx={{ p: 4, textAlign: 'center' }}>
|
||||
<PsychologyIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Select a dataset and click Analyze
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Uses Wile (70B) or Roadrunner for AI-powered threat analysis of your forensic data.
|
||||
No external API keys required.
|
||||
</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
409
frontend/src/components/CaseManager.tsx
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* CaseManager — case management with Kanban task board, TLP/PAP badges,
|
||||
* activity timeline, MITRE technique tags, and IOC lists.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Stack, Chip, Button, TextField,
|
||||
FormControl, InputLabel, Select, MenuItem, CircularProgress,
|
||||
Alert, IconButton, Dialog, DialogTitle, DialogContent,
|
||||
DialogActions, Divider, Tooltip, Card, CardContent, CardActions,
|
||||
Grid, Badge,
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import {
|
||||
cases, type CaseData, type CaseTaskData, type ActivityLogEntry,
|
||||
} from '../api/client';
|
||||
|
||||
const STATUSES = ['open', 'in-progress', 'resolved', 'closed'];
|
||||
const SEVERITIES = ['info', 'low', 'medium', 'high', 'critical'];
|
||||
const TLPS = ['white', 'green', 'amber', 'red'];
|
||||
const PRIORITIES = [
|
||||
{ value: 1, label: 'P1 — Urgent' },
|
||||
{ value: 2, label: 'P2 — High' },
|
||||
{ value: 3, label: 'P3 — Medium' },
|
||||
{ value: 4, label: 'P4 — Low' },
|
||||
];
|
||||
|
||||
const SEV_COLORS: Record<string, 'default' | 'info' | 'success' | 'warning' | 'error'> = {
|
||||
info: 'info', low: 'success', medium: 'warning', high: 'error', critical: 'error',
|
||||
};
|
||||
const TLP_COLORS: Record<string, string> = {
|
||||
white: '#fff', green: '#22c55e', amber: '#f59e0b', red: '#ef4444',
|
||||
};
|
||||
const TASK_COLUMNS = ['todo', 'in-progress', 'done'];
|
||||
const TASK_COL_LABELS: Record<string, string> = { 'todo': 'To Do', 'in-progress': 'In Progress', 'done': 'Done' };
|
||||
|
||||
export default function CaseManager() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [caseList, setCaseList] = useState<CaseData[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filterStatus, setFilterStatus] = useState('');
|
||||
const [selectedCase, setSelectedCase] = useState<CaseData | null>(null);
|
||||
const [activityLog, setActivityLog] = useState<ActivityLogEntry[]>([]);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [taskDlgOpen, setTaskDlgOpen] = useState(false);
|
||||
const [form, setForm] = useState({
|
||||
title: '', description: '', severity: 'medium', tlp: 'amber', pap: 'amber', priority: 2,
|
||||
assignee: '', tags: '',
|
||||
});
|
||||
const [taskForm, setTaskForm] = useState({ title: '', description: '', assignee: '' });
|
||||
|
||||
const loadCases = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const r = await cases.list({ status: filterStatus || undefined, limit: 100 });
|
||||
setCaseList(r.cases);
|
||||
setTotal(r.total);
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
setLoading(false);
|
||||
}, [filterStatus, enqueueSnackbar]);
|
||||
|
||||
useEffect(() => { loadCases(); }, [loadCases]);
|
||||
|
||||
const loadCaseDetail = async (id: string) => {
|
||||
try {
|
||||
const [c, a] = await Promise.all([cases.get(id), cases.activity(id)]);
|
||||
setSelectedCase(c);
|
||||
setActivityLog(a.logs);
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
const tags = form.tags ? form.tags.split(',').map(t => t.trim()).filter(Boolean) : [];
|
||||
await cases.create({
|
||||
title: form.title,
|
||||
description: form.description || undefined,
|
||||
severity: form.severity,
|
||||
tlp: form.tlp,
|
||||
pap: form.pap,
|
||||
priority: form.priority,
|
||||
assignee: form.assignee || undefined,
|
||||
tags,
|
||||
} as any);
|
||||
enqueueSnackbar('Case created', { variant: 'success' });
|
||||
setCreateOpen(false);
|
||||
loadCases();
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
const handleStatusChange = async (caseId: string, newStatus: string) => {
|
||||
try {
|
||||
await cases.update(caseId, { status: newStatus } as any);
|
||||
enqueueSnackbar(`Status → ${newStatus}`, { variant: 'info' });
|
||||
if (selectedCase?.id === caseId) loadCaseDetail(caseId);
|
||||
loadCases();
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!window.confirm('Delete this case?')) return;
|
||||
try {
|
||||
await cases.delete(id);
|
||||
enqueueSnackbar('Case deleted', { variant: 'info' });
|
||||
if (selectedCase?.id === id) setSelectedCase(null);
|
||||
loadCases();
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
const handleAddTask = async () => {
|
||||
if (!selectedCase) return;
|
||||
try {
|
||||
await cases.addTask(selectedCase.id, {
|
||||
title: taskForm.title,
|
||||
description: taskForm.description || undefined,
|
||||
assignee: taskForm.assignee || undefined,
|
||||
});
|
||||
enqueueSnackbar('Task added', { variant: 'success' });
|
||||
setTaskDlgOpen(false);
|
||||
loadCaseDetail(selectedCase.id);
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
const handleTaskStatusChange = async (taskId: string, newStatus: string) => {
|
||||
if (!selectedCase) return;
|
||||
try {
|
||||
await cases.updateTask(selectedCase.id, taskId, { status: newStatus } as any);
|
||||
loadCaseDetail(selectedCase.id);
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
const handleDeleteTask = async (taskId: string) => {
|
||||
if (!selectedCase) return;
|
||||
try {
|
||||
await cases.deleteTask(selectedCase.id, taskId);
|
||||
loadCaseDetail(selectedCase.id);
|
||||
} catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); }
|
||||
};
|
||||
|
||||
if (loading) return <Box sx={{ p: 4 }}><CircularProgress /></Box>;
|
||||
|
||||
// ── Case Detail View ───────────────────────────────────────────────
|
||||
|
||||
if (selectedCase) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
|
||||
<IconButton onClick={() => setSelectedCase(null)}><ArrowBackIcon /></IconButton>
|
||||
<Typography variant="h5">{selectedCase.title}</Typography>
|
||||
<Chip label={selectedCase.severity} size="small" color={SEV_COLORS[selectedCase.severity] || 'default'} />
|
||||
<Chip label={`TLP:${selectedCase.tlp.toUpperCase()}`} size="small"
|
||||
sx={{ background: TLP_COLORS[selectedCase.tlp], color: selectedCase.tlp === 'white' ? '#000' : '#fff' }} />
|
||||
<Chip label={selectedCase.status} size="small" variant="outlined" />
|
||||
</Stack>
|
||||
|
||||
{selectedCase.description && (
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">{selectedCase.description}</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* Status control */}
|
||||
<Paper sx={{ p: 1.5, mb: 2 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Typography variant="caption" fontWeight={700}>Status:</Typography>
|
||||
{STATUSES.map(s => (
|
||||
<Button key={s} size="small"
|
||||
variant={selectedCase.status === s ? 'contained' : 'outlined'}
|
||||
onClick={() => handleStatusChange(selectedCase.id, s)}>
|
||||
{s}
|
||||
</Button>
|
||||
))}
|
||||
<Box sx={{ flex: 1 }} />
|
||||
{selectedCase.assignee && (
|
||||
<Chip label={`Assigned: ${selectedCase.assignee}`} size="small" variant="outlined" />
|
||||
)}
|
||||
<Chip label={`P${selectedCase.priority}`} size="small" />
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Tags + MITRE */}
|
||||
{(selectedCase.tags.length > 0 || selectedCase.mitre_techniques.length > 0) && (
|
||||
<Paper sx={{ p: 1.5, mb: 2 }}>
|
||||
<Stack direction="row" spacing={0.5} flexWrap="wrap">
|
||||
{selectedCase.tags.map(t => <Chip key={t} label={t} size="small" variant="outlined" />)}
|
||||
{selectedCase.mitre_techniques.map(t => (
|
||||
<Chip key={t} label={t} size="small" color="error" variant="outlined" />
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* Kanban Task Board */}
|
||||
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1 }}>
|
||||
<Typography variant="h6">Tasks</Typography>
|
||||
<IconButton size="small" onClick={() => {
|
||||
setTaskForm({ title: '', description: '', assignee: '' });
|
||||
setTaskDlgOpen(true);
|
||||
}}><AddIcon /></IconButton>
|
||||
</Stack>
|
||||
|
||||
<Grid container spacing={2} sx={{ mb: 2 }}>
|
||||
{TASK_COLUMNS.map(col => {
|
||||
const colTasks = (selectedCase.tasks || []).filter(t => t.status === col);
|
||||
return (
|
||||
<Grid key={col} size={{ xs: 12, md: 4 }}>
|
||||
<Paper sx={{ p: 1, minHeight: 200, background: 'rgba(255,255,255,0.02)' }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, textAlign: 'center' }}>
|
||||
{TASK_COL_LABELS[col]}
|
||||
<Badge badgeContent={colTasks.length} color="primary" sx={{ ml: 1 }} />
|
||||
</Typography>
|
||||
<Stack spacing={1}>
|
||||
{colTasks.map(task => (
|
||||
<Card key={task.id} variant="outlined" sx={{ position: 'relative' }}>
|
||||
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
||||
<Typography variant="body2" fontWeight={600}>{task.title}</Typography>
|
||||
{task.description && (
|
||||
<Typography variant="caption" color="text.secondary">{task.description}</Typography>
|
||||
)}
|
||||
{task.assignee && (
|
||||
<Chip label={task.assignee} size="small" sx={{ mt: 0.5 }} />
|
||||
)}
|
||||
</CardContent>
|
||||
<CardActions sx={{ py: 0, px: 1 }}>
|
||||
{col !== 'todo' && (
|
||||
<Button size="small" onClick={() =>
|
||||
handleTaskStatusChange(task.id, col === 'done' ? 'in-progress' : 'todo')}>
|
||||
←
|
||||
</Button>
|
||||
)}
|
||||
{col !== 'done' && (
|
||||
<Button size="small" onClick={() =>
|
||||
handleTaskStatusChange(task.id, col === 'todo' ? 'in-progress' : 'done')}>
|
||||
→
|
||||
</Button>
|
||||
)}
|
||||
<Box sx={{ flex: 1 }} />
|
||||
<IconButton size="small" color="error" onClick={() => handleDeleteTask(task.id)}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
|
||||
{/* Activity Log */}
|
||||
{activityLog.length > 0 && (
|
||||
<>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>Activity</Typography>
|
||||
<Paper sx={{ p: 1.5, maxHeight: 200, overflow: 'auto' }}>
|
||||
{activityLog.map(l => (
|
||||
<Stack key={l.id} direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 140 }}>
|
||||
{l.created_at ? new Date(l.created_at).toLocaleString() : ''}
|
||||
</Typography>
|
||||
<Chip label={l.action} size="small" variant="outlined" />
|
||||
{l.details && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{JSON.stringify(l.details).slice(0, 100)}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
))}
|
||||
</Paper>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Task create dialog */}
|
||||
<Dialog open={taskDlgOpen} onClose={() => setTaskDlgOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>New Task</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField label="Title" fullWidth value={taskForm.title}
|
||||
onChange={e => setTaskForm(f => ({ ...f, title: e.target.value }))} />
|
||||
<TextField label="Description" fullWidth multiline rows={2} value={taskForm.description}
|
||||
onChange={e => setTaskForm(f => ({ ...f, description: e.target.value }))} />
|
||||
<TextField label="Assignee" fullWidth value={taskForm.assignee}
|
||||
onChange={e => setTaskForm(f => ({ ...f, assignee: e.target.value }))} />
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTaskDlgOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleAddTask} disabled={!taskForm.title.trim()}>Create</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Case List View ─────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ mb: 2 }}>
|
||||
<Typography variant="h5">Cases ({total})</Typography>
|
||||
<Button variant="contained" startIcon={<AddIcon />}
|
||||
onClick={() => {
|
||||
setForm({ title: '', description: '', severity: 'medium', tlp: 'amber', pap: 'amber', priority: 2, assignee: '', tags: '' });
|
||||
setCreateOpen(true);
|
||||
}}>New Case</Button>
|
||||
</Stack>
|
||||
|
||||
<Paper sx={{ p: 1.5, mb: 2 }}>
|
||||
<Stack direction="row" spacing={1.5}>
|
||||
<FormControl size="small" sx={{ minWidth: 140 }}>
|
||||
<InputLabel>Status</InputLabel>
|
||||
<Select label="Status" value={filterStatus}
|
||||
onChange={e => setFilterStatus(e.target.value)}>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{STATUSES.map(s => <MenuItem key={s} value={s}>{s}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<Stack spacing={1}>
|
||||
{caseList.map(c => (
|
||||
<Paper key={c.id} sx={{ p: 1.5, cursor: 'pointer', '&:hover': { borderColor: 'primary.main' } }}
|
||||
variant="outlined"
|
||||
onClick={() => loadCaseDetail(c.id)}>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<Typography variant="body1" fontWeight={600} sx={{ flex: 1 }}>{c.title}</Typography>
|
||||
<Chip label={c.severity} size="small" color={SEV_COLORS[c.severity] || 'default'} />
|
||||
<Chip label={`TLP:${c.tlp.toUpperCase()}`} size="small"
|
||||
sx={{ background: TLP_COLORS[c.tlp], color: c.tlp === 'white' ? '#000' : '#fff', fontSize: '0.65rem' }} />
|
||||
<Chip label={c.status} size="small" variant="outlined" />
|
||||
<Chip label={`P${c.priority}`} size="small" />
|
||||
{c.assignee && <Chip label={c.assignee} size="small" variant="outlined" />}
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{c.tasks.length} tasks
|
||||
</Typography>
|
||||
<IconButton size="small" color="error" onClick={e => { e.stopPropagation(); handleDelete(c.id); }}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
{caseList.length === 0 && (
|
||||
<Alert severity="info">No cases found. Create one to get started.</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Create case dialog */}
|
||||
<Dialog open={createOpen} onClose={() => setCreateOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>New Case</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField label="Title" fullWidth value={form.title}
|
||||
onChange={e => setForm(f => ({ ...f, title: e.target.value }))} />
|
||||
<TextField label="Description" fullWidth multiline rows={3} value={form.description}
|
||||
onChange={e => setForm(f => ({ ...f, description: e.target.value }))} />
|
||||
<Stack direction="row" spacing={2}>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>Severity</InputLabel>
|
||||
<Select label="Severity" value={form.severity}
|
||||
onChange={e => setForm(f => ({ ...f, severity: e.target.value }))}>
|
||||
{SEVERITIES.map(s => <MenuItem key={s} value={s}>{s}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>Priority</InputLabel>
|
||||
<Select label="Priority" value={form.priority}
|
||||
onChange={e => setForm(f => ({ ...f, priority: Number(e.target.value) }))}>
|
||||
{PRIORITIES.map(p => <MenuItem key={p.value} value={p.value}>{p.label}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>TLP</InputLabel>
|
||||
<Select label="TLP" value={form.tlp}
|
||||
onChange={e => setForm(f => ({ ...f, tlp: e.target.value }))}>
|
||||
{TLPS.map(t => <MenuItem key={t} value={t}>{t.toUpperCase()}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>PAP</InputLabel>
|
||||
<Select label="PAP" value={form.pap}
|
||||
onChange={e => setForm(f => ({ ...f, pap: e.target.value }))}>
|
||||
{TLPS.map(t => <MenuItem key={t} value={t}>{t.toUpperCase()}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<TextField label="Assignee" fullWidth value={form.assignee}
|
||||
onChange={e => setForm(f => ({ ...f, assignee: e.target.value }))} />
|
||||
<TextField label="Tags (comma-separated)" fullWidth value={form.tags}
|
||||
onChange={e => setForm(f => ({ ...f, tags: e.target.value }))} />
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleCreate} disabled={!form.title.trim()}>Create</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
278
frontend/src/components/ContextMenu.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* ContextMenu — right-click radial menu for analyst actions on cells / rows.
|
||||
* Re-usable across DatasetViewer, NetworkMap, ProcessTree, StorylineGraph, etc.
|
||||
*
|
||||
* Actions:
|
||||
* - Annotate (open annotation dialog)
|
||||
* - Copy value
|
||||
* - Search for value (navigates to Search page)
|
||||
* - Enrich IOC (stub for future)
|
||||
* - Add to hypothesis
|
||||
* - Mark as suspicious / benign
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Menu, MenuItem, ListItemIcon, ListItemText, Divider,
|
||||
Dialog, DialogTitle, DialogContent, DialogActions,
|
||||
Button, TextField, FormControl, InputLabel, Select,
|
||||
Stack, Typography, Chip,
|
||||
} from '@mui/material';
|
||||
import BookmarkAddIcon from '@mui/icons-material/BookmarkAdd';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import FlagIcon from '@mui/icons-material/Flag';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import ScienceIcon from '@mui/icons-material/Science';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { annotations } from '../api/client';
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ContextTarget {
|
||||
/** The cell / node value the user right-clicked on */
|
||||
value: string;
|
||||
/** The field / column name */
|
||||
field?: string;
|
||||
/** Dataset ID if applicable */
|
||||
datasetId?: string;
|
||||
/** Row index if applicable */
|
||||
rowIndex?: number;
|
||||
/** Extra context for display */
|
||||
extra?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface ContextMenuProps {
|
||||
anchorPosition: { top: number; left: number } | null;
|
||||
target: ContextTarget | null;
|
||||
onClose: () => void;
|
||||
/** Optional callback after an annotation is created */
|
||||
onAnnotated?: () => void;
|
||||
}
|
||||
|
||||
// ── Severity + Tag options ───────────────────────────────────────────
|
||||
|
||||
const SEVERITIES = ['info', 'low', 'medium', 'high', 'critical'] as const;
|
||||
const TAGS = ['suspicious', 'benign', 'needs-review', 'false-positive', 'true-positive', 'escalate'] as const;
|
||||
const SEV_COLORS: Record<string, 'default' | 'info' | 'success' | 'warning' | 'error'> = {
|
||||
info: 'info', low: 'success', medium: 'warning', high: 'error', critical: 'error',
|
||||
};
|
||||
|
||||
// ── Component ────────────────────────────────────────────────────────
|
||||
|
||||
export default function ContextMenu({ anchorPosition, target, onClose, onAnnotated }: ContextMenuProps) {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
const [annOpen, setAnnOpen] = useState(false);
|
||||
const [form, setForm] = useState({ text: '', severity: 'medium', tag: 'suspicious' });
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
if (target?.value) {
|
||||
navigator.clipboard.writeText(target.value);
|
||||
enqueueSnackbar('Copied to clipboard', { variant: 'info' });
|
||||
}
|
||||
onClose();
|
||||
}, [target, enqueueSnackbar, onClose]);
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
if (target?.value) {
|
||||
// Navigate to search page with the value pre-loaded via query param
|
||||
navigate(`/search?q=${encodeURIComponent(target.value)}`);
|
||||
}
|
||||
onClose();
|
||||
}, [target, navigate, onClose]);
|
||||
|
||||
const handleQuickAnnotate = useCallback(async (tag: string, severity: string) => {
|
||||
if (!target) return;
|
||||
try {
|
||||
const text = target.field
|
||||
? `${tag}: ${target.field}="${target.value}"`
|
||||
: `${tag}: "${target.value}"`;
|
||||
await annotations.create({
|
||||
text,
|
||||
severity,
|
||||
tag,
|
||||
dataset_id: target.datasetId,
|
||||
row_id: target.rowIndex,
|
||||
});
|
||||
enqueueSnackbar(`Marked as ${tag}`, { variant: 'success' });
|
||||
onAnnotated?.();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
onClose();
|
||||
}, [target, enqueueSnackbar, onClose, onAnnotated]);
|
||||
|
||||
const handleAnnotateOpen = () => {
|
||||
setForm({
|
||||
text: target?.field
|
||||
? `${target.field}="${target.value}"`
|
||||
: target?.value || '',
|
||||
severity: 'medium',
|
||||
tag: 'suspicious',
|
||||
});
|
||||
setAnnOpen(true);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleAnnotateSubmit = async () => {
|
||||
if (!target) return;
|
||||
try {
|
||||
await annotations.create({
|
||||
text: form.text,
|
||||
severity: form.severity,
|
||||
tag: form.tag,
|
||||
dataset_id: target.datasetId,
|
||||
row_id: target.rowIndex,
|
||||
});
|
||||
enqueueSnackbar('Annotation created', { variant: 'success' });
|
||||
onAnnotated?.();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
setAnnOpen(false);
|
||||
};
|
||||
|
||||
const handleHypothesis = () => {
|
||||
// Navigate to hypotheses page — user can create there
|
||||
navigate('/hypotheses');
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Context menu */}
|
||||
<Menu
|
||||
open={!!anchorPosition}
|
||||
onClose={onClose}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={anchorPosition ?? undefined}
|
||||
slotProps={{ paper: { sx: { minWidth: 220 } } }}
|
||||
>
|
||||
{target && (
|
||||
<MenuItem disabled sx={{ opacity: '1 !important', py: 0.5 }}>
|
||||
<Typography variant="caption" color="text.secondary" noWrap sx={{ maxWidth: 260 }}>
|
||||
{target.field ? `${target.field}: ` : ''}
|
||||
<strong>{String(target.value).slice(0, 60)}</strong>
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={handleCopy}>
|
||||
<ListItemIcon><ContentCopyIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Copy value</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleSearch}>
|
||||
<ListItemIcon><SearchIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Search for this</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={handleAnnotateOpen}>
|
||||
<ListItemIcon><BookmarkAddIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Annotate…</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={() => handleQuickAnnotate('suspicious', 'high')}>
|
||||
<ListItemIcon><WarningAmberIcon fontSize="small" color="warning" /></ListItemIcon>
|
||||
<ListItemText>Mark suspicious</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={() => handleQuickAnnotate('benign', 'info')}>
|
||||
<ListItemIcon><CheckCircleOutlineIcon fontSize="small" color="success" /></ListItemIcon>
|
||||
<ListItemText>Mark benign</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={() => handleQuickAnnotate('escalate', 'critical')}>
|
||||
<ListItemIcon><FlagIcon fontSize="small" color="error" /></ListItemIcon>
|
||||
<ListItemText>Escalate</ListItemText>
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
<MenuItem onClick={handleHypothesis}>
|
||||
<ListItemIcon><ScienceIcon fontSize="small" /></ListItemIcon>
|
||||
<ListItemText>Add to hypothesis</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Full annotation dialog */}
|
||||
<Dialog open={annOpen} onClose={() => setAnnOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
Annotate
|
||||
{target?.field && (
|
||||
<Chip label={target.field} size="small" sx={{ ml: 1 }} />
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
label="Annotation text" fullWidth multiline rows={3}
|
||||
value={form.text}
|
||||
onChange={e => setForm(f => ({ ...f, text: e.target.value }))}
|
||||
/>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>Severity</InputLabel>
|
||||
<Select label="Severity" value={form.severity}
|
||||
onChange={e => setForm(f => ({ ...f, severity: e.target.value }))}>
|
||||
{SEVERITIES.map(s => (
|
||||
<MenuItem key={s} value={s}>
|
||||
<Chip label={s} size="small" color={SEV_COLORS[s] || 'default'} variant="outlined" sx={{ mr: 1 }} />
|
||||
{s}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>Tag</InputLabel>
|
||||
<Select label="Tag" value={form.tag}
|
||||
onChange={e => setForm(f => ({ ...f, tag: e.target.value }))}>
|
||||
{TAGS.map(t => <MenuItem key={t} value={t}>{t}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
{target?.datasetId && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Dataset: {target.datasetId}
|
||||
{target.rowIndex != null && ` · Row: ${target.rowIndex}`}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setAnnOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={handleAnnotateSubmit} disabled={!form.text.trim()}>
|
||||
Create
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Hook for easy integration ────────────────────────────────────────
|
||||
|
||||
export function useContextMenu() {
|
||||
const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(null);
|
||||
const [menuTarget, setMenuTarget] = useState<ContextTarget | null>(null);
|
||||
|
||||
const openMenu = useCallback((e: React.MouseEvent, target: ContextTarget) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setMenuPos({ top: e.clientY, left: e.clientX });
|
||||
setMenuTarget(target);
|
||||
}, []);
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
setMenuPos(null);
|
||||
setMenuTarget(null);
|
||||
}, []);
|
||||
|
||||
return { menuPos, menuTarget, openMenu, closeMenu };
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* Dashboard — overview cards with hunt stats, node health, recent activity.
|
||||
* Dashboard — CrowdScore-style triage overview with risk scoring,
|
||||
* severity breakdown, top riskiest hosts, node health, and recent hunts.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Box, Grid, Paper, Typography, Chip, CircularProgress,
|
||||
Stack, Alert,
|
||||
Stack, Alert, LinearProgress, Divider,
|
||||
} from '@mui/material';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
@@ -13,15 +14,65 @@ import SecurityIcon from '@mui/icons-material/Security';
|
||||
import ScienceIcon from '@mui/icons-material/Science';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import { hunts, datasets, hypotheses, agent, misc, type Hunt, type DatasetSummary, type HealthInfo } from '../api/client';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import ShieldIcon from '@mui/icons-material/Shield';
|
||||
import {
|
||||
PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis,
|
||||
Tooltip as RechartsTooltip, Legend,
|
||||
} from 'recharts';
|
||||
import {
|
||||
hunts, datasets, hypotheses, agent, misc, analysis,
|
||||
type Hunt, type DatasetSummary, type HealthInfo, type RiskSummaryResponse,
|
||||
} from '../api/client';
|
||||
|
||||
/* ── Severity palette ─────────────────────────────────────────────── */
|
||||
const SEV_COLORS: Record<string, string> = {
|
||||
critical: '#ef4444',
|
||||
high: '#f97316',
|
||||
medium: '#eab308',
|
||||
low: '#3b82f6',
|
||||
info: '#6b7280',
|
||||
};
|
||||
|
||||
/* ── CrowdScore gauge component ───────────────────────────────────── */
|
||||
function CrowdScore({ score }: { score: number }) {
|
||||
const color = score >= 75 ? '#ef4444' : score >= 50 ? '#f97316' : score >= 25 ? '#eab308' : '#10b981';
|
||||
const label = score >= 75 ? 'CRITICAL' : score >= 50 ? 'HIGH' : score >= 25 ? 'MODERATE' : 'LOW';
|
||||
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center', py: 2 }}>
|
||||
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<CircularProgress
|
||||
variant="determinate" value={score} size={140}
|
||||
thickness={6} sx={{ color, '& .MuiCircularProgress-circle': { strokeLinecap: 'round' } }}
|
||||
/>
|
||||
<Box sx={{
|
||||
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Typography variant="h3" sx={{ fontWeight: 700, color, lineHeight: 1 }}>
|
||||
{score}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color, fontWeight: 600, mt: 0.5 }}>
|
||||
{label}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
Overall Threat Score
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Stat card ────────────────────────────────────────────────────── */
|
||||
function StatCard({ title, value, icon, color }: { title: string; value: string | number; icon: React.ReactNode; color: string }) {
|
||||
return (
|
||||
<Paper sx={{ p: 2.5 }}>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Stack direction="row" alignItems="center" spacing={2}>
|
||||
<Box sx={{ color, fontSize: 40, display: 'flex' }}>{icon}</Box>
|
||||
<Box sx={{ color, fontSize: 36, display: 'flex' }}>{icon}</Box>
|
||||
<Box>
|
||||
<Typography variant="h4">{value}</Typography>
|
||||
<Typography variant="h4" sx={{ fontWeight: 600 }}>{value}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{title}</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -29,6 +80,7 @@ function StatCard({ title, value, icon, color }: { title: string; value: string
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Node status chip ─────────────────────────────────────────────── */
|
||||
function NodeStatus({ label, available }: { label: string; available: boolean }) {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
@@ -49,7 +101,8 @@ export default function Dashboard() {
|
||||
const [huntList, setHunts] = useState<Hunt[]>([]);
|
||||
const [datasetList, setDatasets] = useState<DatasetSummary[]>([]);
|
||||
const [hypoCount, setHypoCount] = useState(0);
|
||||
const [apiInfo, setApiInfo] = useState<{ name: string; version: string; status: string } | null>(null);
|
||||
const [apiInfo, setApiInfo] = useState<{ name?: string; version?: string; status?: string } | null>(null);
|
||||
const [risk, setRisk] = useState<RiskSummaryResponse | null>(null);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +120,13 @@ export default function Dashboard() {
|
||||
setDatasets(ds.datasets);
|
||||
setHypoCount(hy.total);
|
||||
setApiInfo(info);
|
||||
|
||||
// Fetch risk for the first active hunt
|
||||
const activeHunt = ht.hunts.find((h: Hunt) => h.status === 'active');
|
||||
if (activeHunt) {
|
||||
const riskData = await analysis.riskSummary(activeHunt.id).catch(() => null);
|
||||
setRisk(riskData);
|
||||
}
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
@@ -81,29 +141,123 @@ export default function Dashboard() {
|
||||
const activeHunts = huntList.filter(h => h.status === 'active').length;
|
||||
const totalRows = datasetList.reduce((s, d) => s + d.row_count, 0);
|
||||
|
||||
/* severity pie data */
|
||||
const sevData = risk?.severity_breakdown
|
||||
? Object.entries(risk.severity_breakdown)
|
||||
.filter(([_, v]) => v > 0)
|
||||
.map(([name, value]) => ({ name, value }))
|
||||
: [];
|
||||
|
||||
/* top 10 riskiest hosts */
|
||||
const topHosts = risk?.hosts?.slice(0, 10) || [];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Dashboard</Typography>
|
||||
|
||||
{/* Stat cards */}
|
||||
<Grid container spacing={2} sx={{ mb: 3 }}>
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
{/* Row 1: CrowdScore + stats */}
|
||||
<Grid container spacing={2} sx={{ mb: 2 }}>
|
||||
<Grid size={{ xs: 12, md: 3 }}>
|
||||
<Paper sx={{ height: '100%' }}>
|
||||
<CrowdScore score={risk?.overall_score || 0} />
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 9 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 6, sm: 3 }}>
|
||||
<StatCard title="Active Hunts" value={activeHunts} icon={<SearchIcon fontSize="inherit" />} color="#60a5fa" />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
<Grid size={{ xs: 6, sm: 3 }}>
|
||||
<StatCard title="Datasets" value={datasetList.length} icon={<StorageIcon fontSize="inherit" />} color="#f472b6" />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
<Grid size={{ xs: 6, sm: 3 }}>
|
||||
<StatCard title="Total Rows" value={totalRows.toLocaleString()} icon={<SecurityIcon fontSize="inherit" />} color="#10b981" />
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||
<Grid size={{ xs: 6, sm: 3 }}>
|
||||
<StatCard title="Hypotheses" value={hypoCount} icon={<ScienceIcon fontSize="inherit" />} color="#f59e0b" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Node health + API info */}
|
||||
{/* Alert signal summary */}
|
||||
{risk && risk.hosts.length > 0 && (
|
||||
<Paper sx={{ p: 2, mt: 2 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap">
|
||||
<WarningAmberIcon sx={{ color: '#f97316' }} />
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
{risk.hosts.filter(h => h.score >= 50).length} high-risk hosts
|
||||
</Typography>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{risk.total_events.toLocaleString()} events analyzed
|
||||
</Typography>
|
||||
{risk.hosts.slice(0, 3).flatMap(h => h.signals.slice(0, 2)).filter((v, i, a) => a.indexOf(v) === i).map(s => (
|
||||
<Chip key={s} label={s} size="small" color="warning" variant="outlined" />
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Row 2: Severity pie + Top riskiest hosts */}
|
||||
<Grid container spacing={2} sx={{ mb: 2 }}>
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<Paper sx={{ p: 2, height: 320 }}>
|
||||
<Typography variant="h6" gutterBottom>Severity Breakdown</Typography>
|
||||
{sevData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<PieChart>
|
||||
<Pie data={sevData} dataKey="value" nameKey="name" cx="50%" cy="50%"
|
||||
outerRadius={90} innerRadius={45} paddingAngle={2} label={({ name, value }) => `${name}: ${value}`}>
|
||||
{sevData.map((entry) => (
|
||||
<Cell key={entry.name} fill={SEV_COLORS[entry.name] || '#6b7280'} />
|
||||
))}
|
||||
</Pie>
|
||||
<RechartsTooltip />
|
||||
<Legend />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 250 }}>
|
||||
<Typography color="text.secondary">No data</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 8 }}>
|
||||
<Paper sx={{ p: 2, height: 320 }}>
|
||||
<Typography variant="h6" gutterBottom>Top 10 Riskiest Hosts</Typography>
|
||||
{topHosts.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={topHosts} layout="vertical" margin={{ left: 80 }}>
|
||||
<XAxis type="number" domain={[0, 100]} />
|
||||
<YAxis type="category" dataKey="hostname" width={70} tick={{ fontSize: 11 }} />
|
||||
<RechartsTooltip
|
||||
formatter={(value: any, name: any) => [`${value}/100`, 'Risk Score']}
|
||||
labelFormatter={(label: any) => `Host: ${label}`}
|
||||
/>
|
||||
<Bar dataKey="score" radius={[0, 4, 4, 0]}>
|
||||
{topHosts.map((entry, idx) => (
|
||||
<Cell key={idx}
|
||||
fill={entry.score >= 75 ? '#ef4444' : entry.score >= 50 ? '#f97316'
|
||||
: entry.score >= 25 ? '#eab308' : '#10b981'}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 250 }}>
|
||||
<Typography color="text.secondary">No risk data — select a hunt with datasets</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Row 3: Node health + Recent hunts */}
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<Paper sx={{ p: 2.5 }}>
|
||||
<Typography variant="h6" gutterBottom>LLM Cluster Health</Typography>
|
||||
<Stack spacing={1.5}>
|
||||
@@ -111,42 +265,74 @@ export default function Dashboard() {
|
||||
<NodeStatus label="Roadrunner (100.110.190.11)" available={health?.nodes?.roadrunner?.available ?? false} />
|
||||
<NodeStatus label="SANS RAG (Open WebUI)" available={health?.rag?.available ?? false} />
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Paper sx={{ p: 2.5 }}>
|
||||
<Typography variant="h6" gutterBottom>API Status</Typography>
|
||||
<Stack spacing={1}>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{apiInfo ? `${apiInfo.name} — ${apiInfo.version}` : 'Unreachable'}
|
||||
{apiInfo ? `${apiInfo.name || 'ThreatHunt'} v${apiInfo.version || '?'}` : 'API unreachable'} — {apiInfo?.status ?? 'unknown'}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Status: {apiInfo?.status ?? 'unknown'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Recent hunts */}
|
||||
{huntList.length > 0 && (
|
||||
<Paper sx={{ p: 2.5, mt: 2 }}>
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<Paper sx={{ p: 2.5 }}>
|
||||
<Typography variant="h6" gutterBottom>Recent Hunts</Typography>
|
||||
{huntList.length > 0 ? (
|
||||
<Stack spacing={1}>
|
||||
{huntList.slice(0, 5).map(h => (
|
||||
{huntList.slice(0, 6).map(h => (
|
||||
<Stack key={h.id} direction="row" alignItems="center" spacing={1}>
|
||||
<Chip label={h.status} size="small"
|
||||
color={h.status === 'active' ? 'success' : h.status === 'closed' ? 'default' : 'warning'}
|
||||
variant="outlined" />
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{h.name}</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, flex: 1 }}>{h.name}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{h.dataset_count} datasets · {h.hypothesis_count} hypotheses
|
||||
{h.dataset_count}ds · {h.hypothesis_count}hyp
|
||||
</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : (
|
||||
<Typography color="text.secondary">No hunts yet</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{/* Risk signal details for top hosts */}
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<Paper sx={{ p: 2.5 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<ShieldIcon sx={{ color: '#60a5fa' }} />
|
||||
<Typography variant="h6">Risk Signals</Typography>
|
||||
</Stack>
|
||||
{topHosts.filter(h => h.signals.length > 0).slice(0, 5).map(h => (
|
||||
<Box key={h.hostname} sx={{ mb: 1.5 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, minWidth: 100 }}>
|
||||
{h.hostname}
|
||||
</Typography>
|
||||
<LinearProgress
|
||||
variant="determinate" value={h.score}
|
||||
sx={{ flex: 1, height: 6, borderRadius: 3,
|
||||
'& .MuiLinearProgress-bar': {
|
||||
bgcolor: h.score >= 75 ? '#ef4444' : h.score >= 50 ? '#f97316'
|
||||
: h.score >= 25 ? '#eab308' : '#10b981' } }}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, minWidth: 30 }}>
|
||||
{h.score}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={0.5} sx={{ mt: 0.5 }} flexWrap="wrap">
|
||||
{h.signals.map(s => (
|
||||
<Chip key={s} label={s} size="small" variant="outlined"
|
||||
sx={{ fontSize: 10, height: 20 }} />
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
))}
|
||||
{topHosts.filter(h => h.signals.length > 0).length === 0 && (
|
||||
<Typography color="text.secondary" variant="body2">No risk signals detected</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { datasets, enrichment, type DatasetSummary } from '../api/client';
|
||||
import ContextMenu, { useContextMenu, type ContextTarget } from './ContextMenu';
|
||||
|
||||
export default function DatasetViewer() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
@@ -24,6 +25,7 @@ export default function DatasetViewer() {
|
||||
const [paginationModel, setPaginationModel] = useState<GridPaginationModel>({ page: 0, pageSize: 50 });
|
||||
const [rowLoading, setRowLoading] = useState(false);
|
||||
const [enriching, setEnriching] = useState(false);
|
||||
const { menuPos, menuTarget, openMenu, closeMenu } = useContextMenu();
|
||||
|
||||
const loadList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -169,7 +171,19 @@ export default function DatasetViewer() {
|
||||
|
||||
{/* Data grid */}
|
||||
{selected ? (
|
||||
<Paper sx={{ height: 520 }}>
|
||||
<Paper
|
||||
sx={{ height: 520 }}
|
||||
onContextMenu={e => {
|
||||
// Find cell value from the DataGrid event target
|
||||
const cell = (e.target as HTMLElement).closest('.MuiDataGrid-cell');
|
||||
if (!cell) return;
|
||||
const field = cell.getAttribute('data-field') || '';
|
||||
const value = cell.textContent || '';
|
||||
const rowEl = cell.closest('.MuiDataGrid-row');
|
||||
const rowIdx = rowEl ? parseInt(rowEl.getAttribute('data-rowindex') || '0', 10) : undefined;
|
||||
openMenu(e, { value, field, datasetId: selected.id, rowIndex: rowIdx });
|
||||
}}
|
||||
>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
@@ -183,7 +197,7 @@ export default function DatasetViewer() {
|
||||
density="compact"
|
||||
sx={{
|
||||
border: 'none',
|
||||
'& .MuiDataGrid-cell': { fontSize: '0.8rem' },
|
||||
'& .MuiDataGrid-cell': { fontSize: '0.8rem', cursor: 'context-menu' },
|
||||
'& .MuiDataGrid-columnHeader': { fontWeight: 700 },
|
||||
// IOC column highlights
|
||||
...Object.fromEntries(
|
||||
@@ -201,6 +215,9 @@ export default function DatasetViewer() {
|
||||
) : (
|
||||
<Alert severity="info">Upload a CSV to get started.</Alert>
|
||||
)}
|
||||
|
||||
{/* Right-click context menu */}
|
||||
<ContextMenu anchorPosition={menuPos} target={menuTarget} onClose={closeMenu} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
372
frontend/src/components/InvestigationNotebook.tsx
Normal file
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* InvestigationNotebook — Cell-based investigation documentation.
|
||||
*
|
||||
* Features:
|
||||
* - Create/list/open notebooks
|
||||
* - Markdown + query cells with add/edit/delete
|
||||
* - Real-time save on cell changes
|
||||
* - Link notebooks to hunts/cases
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Button, IconButton, TextField, Chip,
|
||||
Stack, Divider, Select, MenuItem, FormControl, InputLabel,
|
||||
Dialog, DialogTitle, DialogContent, DialogActions,
|
||||
Card, CardContent, CardActions, Tooltip, ToggleButton, ToggleButtonGroup,
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import CodeIcon from '@mui/icons-material/Code';
|
||||
import NotesIcon from '@mui/icons-material/Notes';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import {
|
||||
notebooks, hunts,
|
||||
NotebookData, NotebookCell, Hunt,
|
||||
} from '../api/client';
|
||||
|
||||
const CELL_TYPE_ICONS: Record<string, React.ReactNode> = {
|
||||
markdown: <NotesIcon fontSize="small" />,
|
||||
query: <SearchIcon fontSize="small" />,
|
||||
code: <CodeIcon fontSize="small" />,
|
||||
};
|
||||
|
||||
export default function InvestigationNotebook() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
// List view state
|
||||
const [nbList, setNbList] = useState<NotebookData[]>([]);
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
const [huntFilter, setHuntFilter] = useState('');
|
||||
|
||||
// Detail view state
|
||||
const [activeNb, setActiveNb] = useState<NotebookData | null>(null);
|
||||
const [editingCell, setEditingCell] = useState<string | null>(null);
|
||||
const [cellSource, setCellSource] = useState('');
|
||||
|
||||
// Create dialog
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [newTitle, setNewTitle] = useState('');
|
||||
const [newDesc, setNewDesc] = useState('');
|
||||
const [newHunt, setNewHunt] = useState('');
|
||||
|
||||
// ── Load ───────────────────────────────────────────────────────────
|
||||
|
||||
const loadList = useCallback(async () => {
|
||||
try {
|
||||
const opts: any = {};
|
||||
if (huntFilter) opts.hunt_id = huntFilter;
|
||||
const res = await notebooks.list(opts);
|
||||
setNbList(res.notebooks);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
}, [huntFilter, enqueueSnackbar]);
|
||||
|
||||
useEffect(() => {
|
||||
hunts.list().then(r => setHuntList(r.hunts)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => { loadList(); }, [loadList]);
|
||||
|
||||
// ── Create ─────────────────────────────────────────────────────────
|
||||
|
||||
const createNotebook = async () => {
|
||||
if (!newTitle) return;
|
||||
try {
|
||||
const nb = await notebooks.create({
|
||||
title: newTitle,
|
||||
description: newDesc || undefined,
|
||||
hunt_id: newHunt || undefined,
|
||||
});
|
||||
enqueueSnackbar('Notebook created', { variant: 'success' });
|
||||
setCreateOpen(false);
|
||||
setNewTitle(''); setNewDesc(''); setNewHunt('');
|
||||
setActiveNb(nb);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Open ───────────────────────────────────────────────────────────
|
||||
|
||||
const openNotebook = async (id: string) => {
|
||||
try {
|
||||
const nb = await notebooks.get(id);
|
||||
setActiveNb(nb);
|
||||
setEditingCell(null);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const refreshNotebook = async () => {
|
||||
if (!activeNb) return;
|
||||
try {
|
||||
const nb = await notebooks.get(activeNb.id);
|
||||
setActiveNb(nb);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
// ── Cell operations ────────────────────────────────────────────────
|
||||
|
||||
const addCell = async (type: string) => {
|
||||
if (!activeNb) return;
|
||||
const cellId = `cell-${Date.now()}`;
|
||||
const placeholder = type === 'markdown'
|
||||
? '## New section\n\nWrite your notes here...'
|
||||
: type === 'query'
|
||||
? '# Search query\nprocess_name:powershell.exe'
|
||||
: '# Code cell\n';
|
||||
try {
|
||||
const nb = await notebooks.upsertCell(activeNb.id, {
|
||||
cell_id: cellId,
|
||||
cell_type: type,
|
||||
source: placeholder,
|
||||
});
|
||||
setActiveNb(nb);
|
||||
setEditingCell(cellId);
|
||||
setCellSource(placeholder);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const saveCell = async (cellId: string) => {
|
||||
if (!activeNb) return;
|
||||
try {
|
||||
const nb = await notebooks.upsertCell(activeNb.id, {
|
||||
cell_id: cellId,
|
||||
source: cellSource,
|
||||
});
|
||||
setActiveNb(nb);
|
||||
setEditingCell(null);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteCell = async (cellId: string) => {
|
||||
if (!activeNb) return;
|
||||
try {
|
||||
await notebooks.deleteCell(activeNb.id, cellId);
|
||||
refreshNotebook();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const moveCell = async (cellId: string, direction: 'up' | 'down') => {
|
||||
if (!activeNb) return;
|
||||
const cells = [...activeNb.cells];
|
||||
const idx = cells.findIndex(c => c.id === cellId);
|
||||
if (idx < 0) return;
|
||||
const targetIdx = direction === 'up' ? idx - 1 : idx + 1;
|
||||
if (targetIdx < 0 || targetIdx >= cells.length) return;
|
||||
[cells[idx], cells[targetIdx]] = [cells[targetIdx], cells[idx]];
|
||||
try {
|
||||
const nb = await notebooks.update(activeNb.id, { cells });
|
||||
setActiveNb(nb);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteNotebook = async (id: string) => {
|
||||
try {
|
||||
await notebooks.delete(id);
|
||||
enqueueSnackbar('Notebook deleted', { variant: 'success' });
|
||||
if (activeNb?.id === id) setActiveNb(null);
|
||||
loadList();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Cell renderer ──────────────────────────────────────────────────
|
||||
|
||||
const renderCell = (cell: NotebookCell, index: number) => {
|
||||
const isEditing = editingCell === cell.id;
|
||||
return (
|
||||
<Paper key={cell.id} variant="outlined" sx={{ mb: 1, position: 'relative' }}>
|
||||
{/* Cell header */}
|
||||
<Stack direction="row" alignItems="center" sx={{ px: 1, py: 0.5, bgcolor: 'action.hover' }}>
|
||||
{CELL_TYPE_ICONS[cell.cell_type] || <NotesIcon fontSize="small" />}
|
||||
<Typography variant="caption" sx={{ ml: 0.5, flexGrow: 1 }}>
|
||||
{cell.cell_type} • #{index + 1}
|
||||
</Typography>
|
||||
<Tooltip title="Move up"><IconButton size="small" onClick={() => moveCell(cell.id, 'up')} disabled={index === 0}><ArrowUpwardIcon fontSize="small" /></IconButton></Tooltip>
|
||||
<Tooltip title="Move down"><IconButton size="small" onClick={() => moveCell(cell.id, 'down')} disabled={index === (activeNb?.cells.length || 0) - 1}><ArrowDownwardIcon fontSize="small" /></IconButton></Tooltip>
|
||||
{!isEditing && (
|
||||
<Tooltip title="Edit"><IconButton size="small" onClick={() => { setEditingCell(cell.id); setCellSource(cell.source); }}><EditIcon fontSize="small" /></IconButton></Tooltip>
|
||||
)}
|
||||
{isEditing && (
|
||||
<Tooltip title="Save"><IconButton size="small" color="primary" onClick={() => saveCell(cell.id)}><SaveIcon fontSize="small" /></IconButton></Tooltip>
|
||||
)}
|
||||
<Tooltip title="Delete"><IconButton size="small" color="error" onClick={() => deleteCell(cell.id)}><DeleteIcon fontSize="small" /></IconButton></Tooltip>
|
||||
</Stack>
|
||||
<Divider />
|
||||
|
||||
{/* Cell body */}
|
||||
<Box sx={{ p: 2 }}>
|
||||
{isEditing ? (
|
||||
<TextField
|
||||
multiline fullWidth
|
||||
minRows={4}
|
||||
value={cellSource}
|
||||
onChange={e => setCellSource(e.target.value)}
|
||||
onKeyDown={e => { if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveCell(cell.id); } }}
|
||||
placeholder="Type here... (Ctrl+S to save)"
|
||||
sx={{ fontFamily: cell.cell_type !== 'markdown' ? 'monospace' : undefined }}
|
||||
/>
|
||||
) : cell.cell_type === 'markdown' ? (
|
||||
<Box sx={{ '& h1,& h2,& h3': { mt: 1, mb: 0.5 }, '& p': { mb: 1 }, '& code': { bgcolor: 'action.hover', px: 0.5, borderRadius: 0.5 } }}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{cell.source}</ReactMarkdown>
|
||||
</Box>
|
||||
) : (
|
||||
<pre style={{ margin: 0, fontFamily: 'monospace', fontSize: '0.85rem', whiteSpace: 'pre-wrap', color: '#e0e0e0', backgroundColor: '#1e1e1e', padding: '12px', borderRadius: '4px' }}>
|
||||
{cell.source}
|
||||
</pre>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Cell output */}
|
||||
{cell.output && (
|
||||
<>
|
||||
<Divider />
|
||||
<Box sx={{ p: 1, bgcolor: 'grey.900' }}>
|
||||
<Typography variant="caption" color="text.secondary">Output:</Typography>
|
||||
<pre style={{ margin: 0, fontSize: '0.8rem', whiteSpace: 'pre-wrap' }}>{cell.output}</pre>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Detail view ────────────────────────────────────────────────────
|
||||
|
||||
if (activeNb) {
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
||||
<Button startIcon={<ArrowBackIcon />} onClick={() => { setActiveNb(null); loadList(); }}>
|
||||
Back
|
||||
</Button>
|
||||
<Typography variant="h5" sx={{ flexGrow: 1 }}>{activeNb.title}</Typography>
|
||||
<Chip label={`${activeNb.cells.length} cells`} size="small" />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Updated: {new Date(activeNb.updated_at).toLocaleString()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{activeNb.description && (
|
||||
<Typography variant="body2" color="text.secondary" mb={2}>{activeNb.description}</Typography>
|
||||
)}
|
||||
|
||||
{/* Cells */}
|
||||
{activeNb.cells.map((cell, i) => renderCell(cell, i))}
|
||||
|
||||
{/* Add cell toolbar */}
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1, textAlign: 'center' }}>
|
||||
<Stack direction="row" spacing={1} justifyContent="center">
|
||||
<Button size="small" startIcon={<NotesIcon />} onClick={() => addCell('markdown')}>
|
||||
+ Markdown
|
||||
</Button>
|
||||
<Button size="small" startIcon={<SearchIcon />} onClick={() => addCell('query')}>
|
||||
+ Query
|
||||
</Button>
|
||||
<Button size="small" startIcon={<CodeIcon />} onClick={() => addCell('code')}>
|
||||
+ Code
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ── List view ──────────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
|
||||
<Typography variant="h5">Investigation Notebooks</Typography>
|
||||
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setCreateOpen(true)}>
|
||||
New Notebook
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" spacing={2} mb={2}>
|
||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
||||
<InputLabel>Filter by Hunt</InputLabel>
|
||||
<Select value={huntFilter} label="Filter by Hunt" onChange={e => setHuntFilter(e.target.value)}>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 2 }}>
|
||||
{nbList.map(nb => (
|
||||
<Card key={nb.id} variant="outlined">
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>{nb.title}</Typography>
|
||||
{nb.description && (
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>{nb.description}</Typography>
|
||||
)}
|
||||
<Stack direction="row" spacing={1} flexWrap="wrap">
|
||||
<Chip label={`${nb.cell_count} cells`} size="small" />
|
||||
{nb.tags?.map((t, i) => <Chip key={i} label={t} size="small" variant="outlined" />)}
|
||||
</Stack>
|
||||
<Typography variant="caption" color="text.secondary" display="block" mt={1}>
|
||||
Updated: {new Date(nb.updated_at).toLocaleString()}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" onClick={() => openNotebook(nb.id)}>Open</Button>
|
||||
<IconButton size="small" color="error" onClick={() => deleteNotebook(nb.id)}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{nbList.length === 0 && (
|
||||
<Typography color="text.secondary" textAlign="center" py={6}>
|
||||
No notebooks yet. Create one to start documenting your investigation.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Create dialog */}
|
||||
<Dialog open={createOpen} onClose={() => setCreateOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Create Notebook</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} mt={1}>
|
||||
<TextField label="Title" fullWidth required value={newTitle} onChange={e => setNewTitle(e.target.value)} />
|
||||
<TextField label="Description" fullWidth multiline rows={2} value={newDesc} onChange={e => setNewDesc(e.target.value)} />
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Linked Hunt</InputLabel>
|
||||
<Select value={newHunt} label="Linked Hunt" onChange={e => setNewHunt(e.target.value)}>
|
||||
<MenuItem value="">None</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setCreateOpen(false)}>Cancel</Button>
|
||||
<Button variant="contained" onClick={createNotebook} disabled={!newTitle}>Create</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
273
frontend/src/components/KnowledgeGraph.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* KnowledgeGraph — entity-to-technique knowledge graph visualization
|
||||
* using Cytoscape.js with cola (force-directed) layout.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Stack, FormControl, InputLabel, Select,
|
||||
MenuItem, CircularProgress, Alert, Chip, ToggleButton,
|
||||
ToggleButtonGroup, IconButton, Tooltip,
|
||||
} from '@mui/material';
|
||||
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
||||
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
||||
import FitScreenIcon from '@mui/icons-material/FitScreen';
|
||||
import cytoscape from 'cytoscape';
|
||||
import dagre from 'cytoscape-dagre';
|
||||
import cola from 'cytoscape-cola';
|
||||
import {
|
||||
hunts, datasets, analysis,
|
||||
type HuntOut, type DatasetSummary, type KnowledgeGraphResponse,
|
||||
} from '../api/client';
|
||||
|
||||
cytoscape.use(dagre);
|
||||
cytoscape.use(cola);
|
||||
|
||||
const CY_STYLE: cytoscape.StylesheetStyle[] = [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
label: 'data(label)',
|
||||
'font-size': 9,
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': '80px',
|
||||
color: '#ddd',
|
||||
'text-outline-color': '#111',
|
||||
'text-outline-width': 1,
|
||||
'background-color': 'data(color)',
|
||||
shape: 'data(shape)' as any,
|
||||
width: 30,
|
||||
height: 30,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[type="technique"]',
|
||||
style: {
|
||||
width: 40,
|
||||
height: 26,
|
||||
'font-size': 7,
|
||||
'background-color': '#ef4444',
|
||||
shape: 'round-tag' as any,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
width: 'mapData(weight, 1, 20, 1, 4)',
|
||||
'line-color': 'rgba(150,150,150,0.4)',
|
||||
'curve-style': 'bezier',
|
||||
'target-arrow-shape': 'none',
|
||||
label: 'data(label)',
|
||||
'font-size': 7,
|
||||
color: '#888',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge[weight > 3]',
|
||||
style: {
|
||||
'line-color': 'rgba(239,68,68,0.4)',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: ':selected',
|
||||
style: {
|
||||
'border-width': 3,
|
||||
'border-color': '#f59e0b',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function KnowledgeGraph() {
|
||||
const cyRef = useRef<cytoscape.Core | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [huntList, setHuntList] = useState<HuntOut[]>([]);
|
||||
const [dsList, setDsList] = useState<DatasetSummary[]>([]);
|
||||
const [activeHunt, setActiveHunt] = useState('');
|
||||
const [activeDs, setActiveDs] = useState('');
|
||||
const [data, setData] = useState<KnowledgeGraphResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [layout, setLayout] = useState<'cola' | 'dagre'>('cola');
|
||||
const [selectedNode, setSelectedNode] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
hunts.list(0, 200).then(r => {
|
||||
setHuntList(r.hunts);
|
||||
if (r.hunts.length > 0) setActiveHunt(r.hunts[0].id);
|
||||
}).catch(() => {});
|
||||
datasets.list(0, 200).then(r => setDsList(r.datasets)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
if (!activeDs && !activeHunt) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const r = await analysis.knowledgeGraph({
|
||||
dataset_id: activeDs || undefined,
|
||||
hunt_id: activeHunt || undefined,
|
||||
});
|
||||
setData(r);
|
||||
} catch (e: any) { setError(e.message); }
|
||||
setLoading(false);
|
||||
}, [activeDs, activeHunt]);
|
||||
|
||||
useEffect(() => { fetchData(); }, [fetchData]);
|
||||
|
||||
// Render Cytoscape
|
||||
useEffect(() => {
|
||||
if (!data || !containerRef.current) return;
|
||||
if (data.nodes.length === 0) return;
|
||||
|
||||
if (cyRef.current) cyRef.current.destroy();
|
||||
const cy = cytoscape({
|
||||
container: containerRef.current,
|
||||
elements: { nodes: data.nodes, edges: data.edges },
|
||||
style: CY_STYLE,
|
||||
layout: layout === 'cola'
|
||||
? { name: 'cola', animate: false, nodeSpacing: 20, edgeLength: 120 } as any
|
||||
: { name: 'dagre', rankDir: 'LR', nodeSep: 40, edgeSep: 10, rankSep: 80 } as any,
|
||||
});
|
||||
|
||||
cy.on('tap', 'node', (e) => {
|
||||
setSelectedNode(e.target.data());
|
||||
});
|
||||
cy.on('tap', (e) => {
|
||||
if (e.target === cy) setSelectedNode(null);
|
||||
});
|
||||
|
||||
cyRef.current = cy;
|
||||
return () => { cy.destroy(); };
|
||||
}, [data, layout]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Knowledge Graph</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select label="Hunt" value={activeHunt}
|
||||
onChange={e => { setActiveHunt(e.target.value); setActiveDs(''); }}>
|
||||
<MenuItem value="">— none —</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 220 }}>
|
||||
<InputLabel>Dataset</InputLabel>
|
||||
<Select label="Dataset" value={activeDs}
|
||||
onChange={e => setActiveDs(e.target.value)}>
|
||||
<MenuItem value="">— all datasets —</MenuItem>
|
||||
{dsList.map(d => <MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<ToggleButtonGroup size="small" exclusive value={layout}
|
||||
onChange={(_, v) => { if (v) setLayout(v); }}>
|
||||
<ToggleButton value="cola">Force</ToggleButton>
|
||||
<ToggleButton value="dagre">Hierarchy</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
{data?.stats && (
|
||||
<>
|
||||
<Chip label={`${data.stats.total_nodes} nodes`} size="small" color="primary" variant="outlined" />
|
||||
<Chip label={`${data.stats.total_edges} edges`} size="small" variant="outlined" />
|
||||
<Chip label={`${data.stats.techniques_found} techniques`} size="small" color="error" variant="outlined" />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
{loading && <CircularProgress sx={{ display: 'block', mx: 'auto', my: 4 }} />}
|
||||
|
||||
{!loading && data && data.nodes.length > 0 && (
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Paper sx={{ flex: 1, position: 'relative' }}>
|
||||
{/* Controls */}
|
||||
<Stack direction="row" spacing={0.5} sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1 }}>
|
||||
<Tooltip title="Zoom in">
|
||||
<IconButton size="small" onClick={() => cyRef.current?.zoom(cyRef.current.zoom() * 1.2)}>
|
||||
<ZoomInIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Zoom out">
|
||||
<IconButton size="small" onClick={() => cyRef.current?.zoom(cyRef.current.zoom() / 1.2)}>
|
||||
<ZoomOutIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Fit">
|
||||
<IconButton size="small" onClick={() => cyRef.current?.fit()}>
|
||||
<FitScreenIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Box ref={containerRef} sx={{ width: '100%', height: 520 }} />
|
||||
</Paper>
|
||||
|
||||
{/* Detail panel + Legend */}
|
||||
<Paper sx={{ width: 260, p: 2, maxHeight: 560, overflow: 'auto' }}>
|
||||
<Typography variant="subtitle2" gutterBottom>Legend</Typography>
|
||||
<Stack spacing={0.5} sx={{ mb: 2 }}>
|
||||
{[
|
||||
{ type: 'host', color: '#3b82f6', shape: 'rect' },
|
||||
{ type: 'user', color: '#10b981', shape: 'circle' },
|
||||
{ type: 'ip', color: '#8b5cf6', shape: 'diamond' },
|
||||
{ type: 'process', color: '#f59e0b', shape: 'hexagon' },
|
||||
{ type: 'technique', color: '#ef4444', shape: 'tag' },
|
||||
].map(e => (
|
||||
<Stack key={e.type} direction="row" alignItems="center" spacing={1}>
|
||||
<Box sx={{
|
||||
width: 14, height: 14, borderRadius: e.shape === 'circle' ? '50%' : 2,
|
||||
background: e.color,
|
||||
}} />
|
||||
<Typography variant="caption">{e.type}</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
{data.stats.entity_counts && (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>Entity Counts</Typography>
|
||||
<Stack spacing={0.3} sx={{ mb: 2 }}>
|
||||
{Object.entries(data.stats.entity_counts).map(([k, v]) => (
|
||||
<Stack key={k} direction="row" justifyContent="space-between">
|
||||
<Typography variant="caption">{k}</Typography>
|
||||
<Typography variant="caption" fontWeight={700}>{v}</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedNode && (
|
||||
<>
|
||||
<Typography variant="subtitle2" gutterBottom>Selected</Typography>
|
||||
<Stack spacing={0.5}>
|
||||
<Chip label={selectedNode.type} size="small"
|
||||
sx={{ background: selectedNode.color, color: '#fff' }} />
|
||||
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
|
||||
{selectedNode.label}
|
||||
</Typography>
|
||||
{selectedNode.tactic && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Tactic: {selectedNode.tactic}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{!loading && data && data.nodes.length === 0 && (
|
||||
<Alert severity="info">No entities or techniques found in the selected data.</Alert>
|
||||
)}
|
||||
{!loading && !data && !error && (
|
||||
<Alert severity="info">Select a hunt or dataset to build the knowledge graph.</Alert>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
234
frontend/src/components/MitreMatrix.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* MitreMatrix — ATT&CK heat-map matrix + evidence drill-down.
|
||||
* Shows tactics as columns, techniques as cells with hit-count heat coloring.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Stack, FormControl, InputLabel, Select,
|
||||
MenuItem, CircularProgress, Alert, Chip, Tooltip, Dialog,
|
||||
DialogTitle, DialogContent, DialogActions, Button, Table,
|
||||
TableBody, TableCell, TableContainer, TableHead, TableRow,
|
||||
LinearProgress,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
hunts, datasets, analysis,
|
||||
type HuntOut, type DatasetSummary, type MitreMapResponse,
|
||||
type MitreTactic, type MitreTechnique,
|
||||
} from '../api/client';
|
||||
|
||||
function heatColor(count: number, max: number): string {
|
||||
if (count === 0) return 'transparent';
|
||||
const ratio = Math.min(count / Math.max(max, 1), 1);
|
||||
if (ratio > 0.7) return 'rgba(239,68,68,0.7)'; // red
|
||||
if (ratio > 0.4) return 'rgba(249,115,22,0.6)'; // orange
|
||||
if (ratio > 0.15) return 'rgba(234,179,8,0.5)'; // yellow
|
||||
return 'rgba(59,130,246,0.4)'; // blue
|
||||
}
|
||||
|
||||
export default function MitreMatrix() {
|
||||
const [huntList, setHuntList] = useState<HuntOut[]>([]);
|
||||
const [dsList, setDsList] = useState<DatasetSummary[]>([]);
|
||||
const [activeHunt, setActiveHunt] = useState('');
|
||||
const [activeDs, setActiveDs] = useState('');
|
||||
const [data, setData] = useState<MitreMapResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [selectedTech, setSelectedTech] = useState<MitreTechnique | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
hunts.list(0, 200).then(r => {
|
||||
setHuntList(r.hunts);
|
||||
if (r.hunts.length > 0) setActiveHunt(r.hunts[0].id);
|
||||
}).catch(() => {});
|
||||
datasets.list(0, 200).then(r => setDsList(r.datasets)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
if (!activeDs && !activeHunt) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const r = await analysis.mitreMap({
|
||||
dataset_id: activeDs || undefined,
|
||||
hunt_id: activeHunt || undefined,
|
||||
});
|
||||
setData(r);
|
||||
} catch (e: any) { setError(e.message); }
|
||||
setLoading(false);
|
||||
}, [activeDs, activeHunt]);
|
||||
|
||||
useEffect(() => { fetchData(); }, [fetchData]);
|
||||
|
||||
const maxHits = data ? Math.max(...data.tactics.flatMap(t => t.techniques.map(te => te.count)), 1) : 1;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>MITRE ATT&CK Matrix</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select label="Hunt" value={activeHunt}
|
||||
onChange={e => { setActiveHunt(e.target.value); setActiveDs(''); }}>
|
||||
<MenuItem value="">— none —</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 220 }}>
|
||||
<InputLabel>Dataset</InputLabel>
|
||||
<Select label="Dataset" value={activeDs}
|
||||
onChange={e => setActiveDs(e.target.value)}>
|
||||
<MenuItem value="">— all datasets —</MenuItem>
|
||||
{dsList.map(d => <MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{data && (
|
||||
<>
|
||||
<Chip label={`${data.coverage.tactics_covered}/${data.coverage.tactics_total} tactics`} size="small" color="info" variant="outlined" />
|
||||
<Chip label={`${data.coverage.techniques_matched} techniques`} size="small" color="warning" variant="outlined" />
|
||||
<Chip label={`${data.coverage.total_evidence} evidence hits`} size="small" color="error" variant="outlined" />
|
||||
<Chip label={`${data.total_rows.toLocaleString()} rows scanned`} size="small" variant="outlined" />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
{loading && <CircularProgress sx={{ display: 'block', mx: 'auto', my: 4 }} />}
|
||||
|
||||
{/* ATT&CK Matrix Grid */}
|
||||
{data && !loading && (
|
||||
<Paper sx={{ p: 1, overflow: 'auto' }}>
|
||||
<Stack direction="row" spacing={0.5} sx={{ minWidth: data.tactics.length * 140 }}>
|
||||
{data.tactics.map(tactic => (
|
||||
<Box key={tactic.id} sx={{ flex: '0 0 140px', minWidth: 140 }}>
|
||||
{/* Tactic header */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 0.5,
|
||||
mb: 0.5,
|
||||
background: tactic.total_hits > 0
|
||||
? 'rgba(99,102,241,0.2)'
|
||||
: 'rgba(100,100,100,0.1)',
|
||||
textAlign: 'center',
|
||||
borderBottom: '2px solid',
|
||||
borderColor: tactic.total_hits > 0 ? 'primary.main' : 'divider',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" fontWeight={700} sx={{ fontSize: '0.65rem', lineHeight: 1.2 }}>
|
||||
{tactic.name}
|
||||
</Typography>
|
||||
{tactic.total_hits > 0 && (
|
||||
<Typography variant="caption" display="block" color="warning.main" sx={{ fontSize: '0.6rem' }}>
|
||||
{tactic.total_hits} hits
|
||||
</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{/* Technique cells */}
|
||||
<Stack spacing={0.3}>
|
||||
{tactic.techniques.map(tech => (
|
||||
<Tooltip
|
||||
key={tech.id}
|
||||
title={`${tech.id}: ${tech.name} — ${tech.count} matches`}
|
||||
arrow
|
||||
>
|
||||
<Paper
|
||||
elevation={0}
|
||||
onClick={() => setSelectedTech(tech)}
|
||||
sx={{
|
||||
p: 0.5,
|
||||
cursor: 'pointer',
|
||||
background: heatColor(tech.count, maxHits),
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
transform: 'scale(1.02)',
|
||||
},
|
||||
transition: 'all 0.15s',
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" fontWeight={600}
|
||||
sx={{ fontSize: '0.6rem', lineHeight: 1.1, display: 'block' }}>
|
||||
{tech.id}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary"
|
||||
sx={{ fontSize: '0.55rem', lineHeight: 1.1 }}>
|
||||
{tech.name}
|
||||
</Typography>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={Math.min((tech.count / maxHits) * 100, 100)}
|
||||
sx={{ height: 2, mt: 0.3, borderRadius: 1 }}
|
||||
color={tech.count > maxHits * 0.5 ? 'error' : 'primary'}
|
||||
/>
|
||||
</Paper>
|
||||
</Tooltip>
|
||||
))}
|
||||
{tactic.techniques.length === 0 && (
|
||||
<Typography variant="caption" color="text.disabled"
|
||||
sx={{ fontSize: '0.6rem', textAlign: 'center', py: 1 }}>
|
||||
No matches
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{!loading && !data && !error && (
|
||||
<Alert severity="info">Select a hunt or dataset to map to MITRE ATT&CK.</Alert>
|
||||
)}
|
||||
|
||||
{/* Evidence drill-down dialog */}
|
||||
<Dialog open={!!selectedTech} onClose={() => setSelectedTech(null)} maxWidth="md" fullWidth>
|
||||
<DialogTitle>
|
||||
{selectedTech?.id}: {selectedTech?.name}
|
||||
<Chip label={`${selectedTech?.count} hits`} size="small" color="warning" sx={{ ml: 1 }} />
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Evidence samples (up to 5 shown)
|
||||
</Typography>
|
||||
<TableContainer>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Row</TableCell>
|
||||
<TableCell>Field</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
<TableCell>Pattern</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{selectedTech?.evidence.map((ev, i) => (
|
||||
<TableRow key={i} hover>
|
||||
<TableCell>{ev.row_index}</TableCell>
|
||||
<TableCell><Chip label={ev.field || '—'} size="small" /></TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" sx={{ maxWidth: 300, wordBreak: 'break-all' }}>
|
||||
{ev.value}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="caption" fontFamily="monospace">{ev.pattern}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setSelectedTech(null)}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
403
frontend/src/components/NetworkPicture.tsx
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* NetworkPicture — deduplicated host inventory view.
|
||||
*
|
||||
* Select a hunt → server scans all datasets, groups by hostname,
|
||||
* returns one row per unique host with IPs, users, OS, MAC, ports.
|
||||
* No duplicates — sets handle dedup. All unique values shown.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Stack, Alert, Chip, TextField, Tooltip,
|
||||
LinearProgress, FormControl, InputLabel, Select, MenuItem,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
|
||||
TableSortLabel, Collapse, IconButton,
|
||||
} from '@mui/material';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
import ComputerIcon from '@mui/icons-material/Computer';
|
||||
import RouterIcon from '@mui/icons-material/Router';
|
||||
import PeopleIcon from '@mui/icons-material/People';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
import {
|
||||
hunts, network,
|
||||
type Hunt, type HostEntry, type PictureSummary,
|
||||
} from '../api/client';
|
||||
|
||||
// ── Colour palette (matches NetworkMap) ──────────────────────────────
|
||||
|
||||
const CHIP_COLORS = {
|
||||
ip: '#3b82f6',
|
||||
user: '#22c55e',
|
||||
os: '#eab308',
|
||||
mac: '#8b5cf6',
|
||||
port: '#f43f5e',
|
||||
proto: '#06b6d4',
|
||||
};
|
||||
|
||||
// ── Collapsible chip list — show first N, expand for all ─────────────
|
||||
|
||||
function ChipList({ items, color, max = 5 }: { items: string[]; color: string; max?: number }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
if (items.length === 0) return <Typography variant="body2" color="text.secondary">—</Typography>;
|
||||
const show = expanded ? items : items.slice(0, max);
|
||||
const more = items.length - max;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, alignItems: 'center' }}>
|
||||
{show.map(v => (
|
||||
<Chip
|
||||
key={v} label={v} size="small" variant="outlined"
|
||||
sx={{ borderColor: color, color, fontSize: '0.75rem', height: 22 }}
|
||||
/>
|
||||
))}
|
||||
{more > 0 && !expanded && (
|
||||
<Chip
|
||||
label={`+${more} more`} size="small"
|
||||
onClick={() => setExpanded(true)}
|
||||
sx={{
|
||||
bgcolor: color, color: '#fff', fontSize: '0.7rem', height: 22,
|
||||
cursor: 'pointer', '&:hover': { opacity: 0.85 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{expanded && more > 0 && (
|
||||
<Chip
|
||||
label="less" size="small"
|
||||
onClick={() => setExpanded(false)}
|
||||
sx={{
|
||||
fontSize: '0.7rem', height: 22, cursor: 'pointer',
|
||||
'&:hover': { opacity: 0.85 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Stat card ────────────────────────────────────────────────────────
|
||||
|
||||
function StatCard({ label, value, icon }: { label: string; value: number | string; icon: React.ReactNode }) {
|
||||
return (
|
||||
<Paper elevation={1} sx={{ px: 2, py: 1.5, minWidth: 140, textAlign: 'center' }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center" justifyContent="center">
|
||||
{icon}
|
||||
<Typography variant="h5" fontWeight={700}>{value}</Typography>
|
||||
</Stack>
|
||||
<Typography variant="caption" color="text.secondary">{label}</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Sort helpers ─────────────────────────────────────────────────────
|
||||
|
||||
type SortKey = 'hostname' | 'ips' | 'users' | 'connection_count';
|
||||
type SortDir = 'asc' | 'desc';
|
||||
|
||||
function sortHosts(hosts: HostEntry[], key: SortKey, dir: SortDir): HostEntry[] {
|
||||
const cmp = (a: HostEntry, b: HostEntry): number => {
|
||||
switch (key) {
|
||||
case 'hostname': return a.hostname.localeCompare(b.hostname);
|
||||
case 'ips': return a.ips.length - b.ips.length;
|
||||
case 'users': return a.users.length - b.users.length;
|
||||
case 'connection_count': return a.connection_count - b.connection_count;
|
||||
default: return 0;
|
||||
}
|
||||
};
|
||||
const sorted = [...hosts].sort(cmp);
|
||||
return dir === 'desc' ? sorted.reverse() : sorted;
|
||||
}
|
||||
|
||||
// ── Expanded row detail panel ────────────────────────────────────────
|
||||
|
||||
function HostDetail({ host }: { host: HostEntry }) {
|
||||
return (
|
||||
<Box sx={{ p: 2, bgcolor: 'background.default' }}>
|
||||
<Stack spacing={1.5}>
|
||||
{host.remote_targets.length > 0 && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>Remote Targets ({host.remote_targets.length})</Typography>
|
||||
<ChipList items={host.remote_targets} color={CHIP_COLORS.ip} max={50} />
|
||||
</Box>
|
||||
)}
|
||||
{host.open_ports.length > 0 && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>All Open Ports ({host.open_ports.length})</Typography>
|
||||
<ChipList items={host.open_ports} color={CHIP_COLORS.port} max={50} />
|
||||
</Box>
|
||||
)}
|
||||
{host.protocols.length > 0 && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>Protocols</Typography>
|
||||
<ChipList items={host.protocols} color={CHIP_COLORS.proto} max={20} />
|
||||
</Box>
|
||||
)}
|
||||
{host.mac_addresses.length > 0 && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>MAC Addresses</Typography>
|
||||
<ChipList items={host.mac_addresses} color={CHIP_COLORS.mac} max={20} />
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>Datasets</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{host.datasets.join(', ') || '—'}
|
||||
</Typography>
|
||||
</Box>
|
||||
{(host.first_seen || host.last_seen) && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>Time Range</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{host.first_seen || '?'} → {host.last_seen || '?'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Main component ───────────────────────────────────────────────────
|
||||
|
||||
export default function NetworkPicture() {
|
||||
// Hunt selector
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
const [selectedHunt, setSelectedHunt] = useState('');
|
||||
|
||||
// Data
|
||||
const [hosts, setHosts] = useState<HostEntry[]>([]);
|
||||
const [summary, setSummary] = useState<PictureSummary | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
// Table state
|
||||
const [search, setSearch] = useState('');
|
||||
const [sortKey, setSortKey] = useState<SortKey>('connection_count');
|
||||
const [sortDir, setSortDir] = useState<SortDir>('desc');
|
||||
const [expandedRow, setExpandedRow] = useState<string | null>(null);
|
||||
|
||||
// Load hunts on mount
|
||||
useEffect(() => {
|
||||
hunts.list(0, 200).then(r => setHuntList(r.hunts)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Load network picture when hunt changes
|
||||
const loadPicture = useCallback(async (huntId: string) => {
|
||||
if (!huntId) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setHosts([]);
|
||||
setSummary(null);
|
||||
setExpandedRow(null);
|
||||
try {
|
||||
const resp = await network.picture(huntId);
|
||||
setHosts(resp.hosts);
|
||||
setSummary(resp.summary);
|
||||
} catch (e: any) {
|
||||
setError(e.message || 'Failed to load network picture');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedHunt) loadPicture(selectedHunt);
|
||||
}, [selectedHunt, loadPicture]);
|
||||
|
||||
// Filter + sort
|
||||
const filtered = useMemo(() => {
|
||||
const q = search.toLowerCase().trim();
|
||||
let list = hosts;
|
||||
if (q) {
|
||||
list = hosts.filter(h =>
|
||||
h.hostname.toLowerCase().includes(q) ||
|
||||
h.ips.some(ip => ip.includes(q)) ||
|
||||
h.users.some(u => u.toLowerCase().includes(q)) ||
|
||||
h.os.some(o => o.toLowerCase().includes(q)) ||
|
||||
h.mac_addresses.some(m => m.toLowerCase().includes(q))
|
||||
);
|
||||
}
|
||||
return sortHosts(list, sortKey, sortDir);
|
||||
}, [hosts, search, sortKey, sortDir]);
|
||||
|
||||
const handleSort = (key: SortKey) => {
|
||||
if (sortKey === key) {
|
||||
setSortDir(d => d === 'asc' ? 'desc' : 'asc');
|
||||
} else {
|
||||
setSortKey(key);
|
||||
setSortDir(key === 'connection_count' ? 'desc' : 'asc');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExpand = (hostname: string) => {
|
||||
setExpandedRow(prev => prev === hostname ? null : hostname);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom fontWeight={700}>
|
||||
Network Picture
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
Deduplicated host inventory — one row per machine. Hostname, IPs, users, OS, MACs aggregated from all datasets.
|
||||
</Typography>
|
||||
|
||||
{/* Hunt selector */}
|
||||
<Stack direction="row" spacing={2} sx={{ mt: 2, mb: 2 }} alignItems="center">
|
||||
<FormControl size="small" sx={{ minWidth: 300 }}>
|
||||
<InputLabel>Select Hunt</InputLabel>
|
||||
<Select
|
||||
value={selectedHunt}
|
||||
label="Select Hunt"
|
||||
onChange={e => setSelectedHunt(e.target.value)}
|
||||
>
|
||||
{huntList.map(h => (
|
||||
<MenuItem key={h.id} value={h.id}>
|
||||
{h.name} ({h.dataset_count} datasets)
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{selectedHunt && (
|
||||
<Tooltip title="Refresh">
|
||||
<IconButton onClick={() => loadPicture(selectedHunt)} size="small">
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
size="small" placeholder="Search hostname, IP, user, OS, MAC…"
|
||||
value={search} onChange={e => setSearch(e.target.value)}
|
||||
sx={{ minWidth: 280 }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{loading && <LinearProgress sx={{ mb: 2 }} />}
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
|
||||
{/* Summary stats */}
|
||||
{summary && !loading && (
|
||||
<Stack direction="row" spacing={2} sx={{ mb: 2 }} flexWrap="wrap" useFlexGap>
|
||||
<StatCard label="Hosts" value={summary.total_hosts} icon={<ComputerIcon color="primary" />} />
|
||||
<StatCard label="Unique IPs" value={summary.total_unique_ips} icon={<RouterIcon color="secondary" />} />
|
||||
<StatCard label="Connections" value={summary.total_connections.toLocaleString()} icon={<DnsIcon color="info" />} />
|
||||
<StatCard label="Datasets Scanned" value={summary.datasets_scanned} icon={<PeopleIcon color="success" />} />
|
||||
{search && (
|
||||
<Paper elevation={1} sx={{ px: 2, py: 1.5, textAlign: 'center' }}>
|
||||
<Typography variant="h5" fontWeight={700}>{filtered.length}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">Matching filter</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Host table */}
|
||||
{!loading && hosts.length > 0 && (
|
||||
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 320px)' }}>
|
||||
<Table stickyHeader size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width={32} />
|
||||
<TableCell>
|
||||
<TableSortLabel
|
||||
active={sortKey === 'hostname'} direction={sortKey === 'hostname' ? sortDir : 'asc'}
|
||||
onClick={() => handleSort('hostname')}
|
||||
>
|
||||
Hostname
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableSortLabel
|
||||
active={sortKey === 'ips'} direction={sortKey === 'ips' ? sortDir : 'asc'}
|
||||
onClick={() => handleSort('ips')}
|
||||
>
|
||||
IPs
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableSortLabel
|
||||
active={sortKey === 'users'} direction={sortKey === 'users' ? sortDir : 'asc'}
|
||||
onClick={() => handleSort('users')}
|
||||
>
|
||||
Users
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell>OS</TableCell>
|
||||
<TableCell>MAC</TableCell>
|
||||
<TableCell>
|
||||
<TableSortLabel
|
||||
active={sortKey === 'connection_count'} direction={sortKey === 'connection_count' ? sortDir : 'asc'}
|
||||
onClick={() => handleSort('connection_count')}
|
||||
>
|
||||
Connections
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
<TableCell>Ports</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{filtered.map(host => {
|
||||
const isExpanded = expandedRow === host.hostname;
|
||||
return (
|
||||
<React.Fragment key={host.hostname}>
|
||||
<TableRow
|
||||
hover
|
||||
sx={{ cursor: 'pointer', '& > *': { borderBottom: isExpanded ? 'none' : undefined } }}
|
||||
onClick={() => toggleExpand(host.hostname)}
|
||||
>
|
||||
<TableCell>
|
||||
<IconButton size="small">
|
||||
{isExpanded ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" fontWeight={700}>{host.hostname}</Typography>
|
||||
</TableCell>
|
||||
<TableCell><ChipList items={host.ips} color={CHIP_COLORS.ip} /></TableCell>
|
||||
<TableCell><ChipList items={host.users} color={CHIP_COLORS.user} /></TableCell>
|
||||
<TableCell>
|
||||
{host.os.length > 0
|
||||
? host.os.join(', ')
|
||||
: <Typography variant="body2" color="text.secondary">—</Typography>}
|
||||
</TableCell>
|
||||
<TableCell><ChipList items={host.mac_addresses} color={CHIP_COLORS.mac} /></TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={host.connection_count.toLocaleString()}
|
||||
size="small" color="primary" variant="outlined"
|
||||
sx={{ fontWeight: 700 }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell><ChipList items={host.open_ports.slice(0, 5)} color={CHIP_COLORS.port} max={5} /></TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} sx={{ p: 0, border: 0 }}>
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<HostDetail host={host} />
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{/* Empty state */}
|
||||
{!loading && selectedHunt && hosts.length === 0 && !error && (
|
||||
<Alert severity="info" sx={{ mt: 2 }}>
|
||||
No hosts found. Upload datasets with hostname/IP columns to this hunt.
|
||||
</Alert>
|
||||
)}
|
||||
{!selectedHunt && (
|
||||
<Alert severity="info" sx={{ mt: 2 }}>
|
||||
Select a hunt to view the network picture.
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
453
frontend/src/components/PlaybookManager.tsx
Normal file
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* PlaybookManager — Pre-defined investigation playbooks.
|
||||
*
|
||||
* Features:
|
||||
* - Browse built-in playbook templates
|
||||
* - Start a playbook run linked to a hunt/case
|
||||
* - Step-by-step execution with notes and status tracking
|
||||
* - View past runs and their results
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Button, Chip, Stack, Divider,
|
||||
Card, CardContent, CardActions, Stepper, Step, StepLabel, StepContent,
|
||||
TextField, Dialog, DialogTitle, DialogContent, DialogActions,
|
||||
FormControl, InputLabel, Select, MenuItem, Tabs, Tab,
|
||||
LinearProgress, IconButton, Tooltip,
|
||||
} from '@mui/material';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import StopIcon from '@mui/icons-material/Stop';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import SkipNextIcon from '@mui/icons-material/SkipNext';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import {
|
||||
playbooks, hunts,
|
||||
PlaybookTemplate, PlaybookTemplateDetail, PlaybookRunData, Hunt,
|
||||
} from '../api/client';
|
||||
|
||||
const CATEGORY_COLORS: Record<string, 'error' | 'primary' | 'secondary' | 'warning' | 'info' | 'success'> = {
|
||||
incident_response: 'error',
|
||||
threat_hunting: 'primary',
|
||||
compliance: 'info',
|
||||
};
|
||||
|
||||
const STATUS_COLORS: Record<string, 'warning' | 'success' | 'error' | 'default'> = {
|
||||
'in-progress': 'warning',
|
||||
completed: 'success',
|
||||
aborted: 'error',
|
||||
};
|
||||
|
||||
export default function PlaybookManager() {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
// Templates
|
||||
const [templates, setTemplates] = useState<PlaybookTemplate[]>([]);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<PlaybookTemplateDetail | null>(null);
|
||||
|
||||
// Runs
|
||||
const [runs, setRuns] = useState<PlaybookRunData[]>([]);
|
||||
const [activeRun, setActiveRun] = useState<PlaybookRunData | null>(null);
|
||||
|
||||
// Start dialog
|
||||
const [startDialog, setStartDialog] = useState(false);
|
||||
const [startTemplate, setStartTemplate] = useState('');
|
||||
const [startHunt, setStartHunt] = useState('');
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
|
||||
// Step notes
|
||||
const [stepNotes, setStepNotes] = useState('');
|
||||
|
||||
// ── Load ───────────────────────────────────────────────────────────
|
||||
|
||||
const loadTemplates = useCallback(async () => {
|
||||
try {
|
||||
const res = await playbooks.templates();
|
||||
setTemplates(res.templates);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
}, [enqueueSnackbar]);
|
||||
|
||||
const loadRuns = useCallback(async () => {
|
||||
try {
|
||||
const res = await playbooks.listRuns();
|
||||
setRuns(res.runs);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
}, [enqueueSnackbar]);
|
||||
|
||||
useEffect(() => {
|
||||
loadTemplates();
|
||||
loadRuns();
|
||||
hunts.list().then(r => setHuntList(r.hunts)).catch(() => {});
|
||||
}, [loadTemplates, loadRuns]);
|
||||
|
||||
// ── Template detail ────────────────────────────────────────────────
|
||||
|
||||
const viewTemplate = async (name: string) => {
|
||||
try {
|
||||
const detail = await playbooks.templateDetail(name);
|
||||
setSelectedTemplate(detail);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Start run ──────────────────────────────────────────────────────
|
||||
|
||||
const startRun = async () => {
|
||||
if (!startTemplate) return;
|
||||
try {
|
||||
const run = await playbooks.start({
|
||||
playbook_name: startTemplate,
|
||||
hunt_id: startHunt || undefined,
|
||||
});
|
||||
enqueueSnackbar('Playbook started!', { variant: 'success' });
|
||||
setStartDialog(false);
|
||||
setStartTemplate(''); setStartHunt('');
|
||||
setActiveRun(run);
|
||||
setTab(1);
|
||||
loadRuns();
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Open run detail ────────────────────────────────────────────────
|
||||
|
||||
const openRun = async (runId: string) => {
|
||||
try {
|
||||
const run = await playbooks.getRun(runId);
|
||||
setActiveRun(run);
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Complete step ──────────────────────────────────────────────────
|
||||
|
||||
const completeStep = async (status: string = 'completed') => {
|
||||
if (!activeRun) return;
|
||||
try {
|
||||
const run = await playbooks.completeStep(activeRun.id, {
|
||||
notes: stepNotes || undefined,
|
||||
status,
|
||||
});
|
||||
setActiveRun(run);
|
||||
setStepNotes('');
|
||||
loadRuns();
|
||||
if (run.status === 'completed') {
|
||||
enqueueSnackbar('Playbook completed!', { variant: 'success' });
|
||||
}
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
const abortRun = async () => {
|
||||
if (!activeRun) return;
|
||||
try {
|
||||
const run = await playbooks.abortRun(activeRun.id);
|
||||
setActiveRun(run);
|
||||
loadRuns();
|
||||
enqueueSnackbar('Playbook aborted', { variant: 'warning' });
|
||||
} catch (e: any) {
|
||||
enqueueSnackbar(e.message, { variant: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
// ── Active run view ────────────────────────────────────────────────
|
||||
|
||||
if (activeRun) {
|
||||
const steps = activeRun.steps || [];
|
||||
const currentIdx = activeRun.current_step - 1;
|
||||
const isActive = activeRun.status === 'in-progress';
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" alignItems="center" spacing={2} mb={2}>
|
||||
<Button onClick={() => setActiveRun(null)}>Back</Button>
|
||||
<Typography variant="h5" sx={{ flexGrow: 1 }}>{activeRun.playbook_name}</Typography>
|
||||
<Chip label={activeRun.status} color={STATUS_COLORS[activeRun.status] || 'default'} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Step {activeRun.current_step} / {activeRun.total_steps}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={(activeRun.step_results.length / activeRun.total_steps) * 100}
|
||||
sx={{ mb: 3, height: 8, borderRadius: 4 }}
|
||||
/>
|
||||
|
||||
<Stepper activeStep={currentIdx} orientation="vertical">
|
||||
{steps.map((step, i) => {
|
||||
const result = activeRun.step_results.find(r => r.step === step.order);
|
||||
const isCurrent = i === currentIdx && isActive;
|
||||
|
||||
return (
|
||||
<Step key={step.order} completed={!!result}>
|
||||
<StepLabel
|
||||
optional={
|
||||
result ? (
|
||||
<Chip
|
||||
label={result.status}
|
||||
size="small"
|
||||
color={result.status === 'completed' ? 'success' : 'default'}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<Typography variant="subtitle1" fontWeight={isCurrent ? 'bold' : 'normal'}>
|
||||
{step.title}
|
||||
</Typography>
|
||||
</StepLabel>
|
||||
<StepContent>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
{step.description}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mt: 1, mb: 1 }}>
|
||||
<Chip label={`Action: ${step.action}`} size="small" variant="outlined" sx={{ mr: 1 }} />
|
||||
<Typography variant="caption" color="text.secondary" display="block" mt={0.5}>
|
||||
Expected: {step.expected_outcome}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{result?.notes && (
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 1, bgcolor: 'action.hover' }}>
|
||||
<Typography variant="caption" color="text.secondary">Notes:</Typography>
|
||||
<Typography variant="body2">{result.notes}</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{isCurrent && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
label="Step notes (optional)"
|
||||
multiline rows={2} fullWidth size="small"
|
||||
value={stepNotes}
|
||||
onChange={e => setStepNotes(e.target.value)}
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<Button
|
||||
variant="contained" size="small"
|
||||
startIcon={<CheckCircleIcon />}
|
||||
onClick={() => completeStep('completed')}
|
||||
>
|
||||
Complete Step
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined" size="small"
|
||||
startIcon={<SkipNextIcon />}
|
||||
onClick={() => completeStep('skipped')}
|
||||
>
|
||||
Skip
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined" color="error" size="small"
|
||||
startIcon={<StopIcon />}
|
||||
onClick={abortRun}
|
||||
>
|
||||
Abort
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
</StepContent>
|
||||
</Step>
|
||||
);
|
||||
})}
|
||||
</Stepper>
|
||||
|
||||
{activeRun.status === 'completed' && (
|
||||
<Paper sx={{ p: 2, mt: 2, bgcolor: 'success.dark', color: 'white' }}>
|
||||
<Typography variant="h6">Playbook Completed</Typography>
|
||||
<Typography variant="body2">
|
||||
All {activeRun.total_steps} steps finished at {activeRun.completed_at ? new Date(activeRun.completed_at).toLocaleString() : 'N/A'}
|
||||
</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Main view ──────────────────────────────────────────────────────
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
|
||||
<Typography variant="h5">Playbooks</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<PlayArrowIcon />}
|
||||
onClick={() => setStartDialog(true)}
|
||||
>
|
||||
Start Playbook
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ mb: 2 }}>
|
||||
<Tab label={`Templates (${templates.length})`} />
|
||||
<Tab label={`Runs (${runs.length})`} icon={<HistoryIcon />} iconPosition="start" />
|
||||
</Tabs>
|
||||
|
||||
{/* Templates tab */}
|
||||
{tab === 0 && (
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(350px, 1fr))', gap: 2 }}>
|
||||
{templates.map(t => (
|
||||
<Card key={t.name} variant="outlined">
|
||||
<CardContent>
|
||||
<Stack direction="row" alignItems="center" spacing={1} mb={1}>
|
||||
<Typography variant="h6">{t.name}</Typography>
|
||||
<Chip
|
||||
label={t.category.replace('_', ' ')}
|
||||
size="small"
|
||||
color={CATEGORY_COLORS[t.category] || 'default'}
|
||||
/>
|
||||
</Stack>
|
||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||
{t.description}
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={0.5} flexWrap="wrap">
|
||||
<Chip label={`${t.step_count} steps`} size="small" variant="outlined" />
|
||||
{t.tags.map((tag, i) => <Chip key={i} label={tag} size="small" />)}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" onClick={() => viewTemplate(t.name)}>View Steps</Button>
|
||||
<Button
|
||||
size="small" variant="contained"
|
||||
startIcon={<PlayArrowIcon />}
|
||||
onClick={() => { setStartTemplate(t.name); setStartDialog(true); }}
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Runs tab */}
|
||||
{tab === 1 && (
|
||||
<Box>
|
||||
{runs.map(run => (
|
||||
<Paper key={run.id} sx={{ p: 2, mb: 1 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" fontWeight="bold">
|
||||
{run.playbook_name}
|
||||
<Chip label={run.status} size="small" color={STATUS_COLORS[run.status] || 'default'} sx={{ ml: 1 }} />
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Step {run.current_step}/{run.total_steps} • Started {new Date(run.created_at).toLocaleString()}
|
||||
{run.started_by && ` by ${run.started_by}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={(run.step_results.length / run.total_steps) * 100}
|
||||
sx={{ width: 100, height: 8, borderRadius: 4, alignSelf: 'center' }}
|
||||
/>
|
||||
<Tooltip title="Open">
|
||||
<IconButton onClick={() => openRun(run.id)}>
|
||||
<VisibilityIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
{runs.length === 0 && (
|
||||
<Typography color="text.secondary" textAlign="center" py={4}>
|
||||
No playbook runs yet. Start one from the Templates tab.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Template detail dialog */}
|
||||
<Dialog open={!!selectedTemplate} onClose={() => setSelectedTemplate(null)} maxWidth="md" fullWidth>
|
||||
{selectedTemplate && (
|
||||
<>
|
||||
<DialogTitle>
|
||||
{selectedTemplate.name}
|
||||
<Chip label={selectedTemplate.category.replace('_', ' ')} size="small" color={CATEGORY_COLORS[selectedTemplate.category] || 'default'} sx={{ ml: 1 }} />
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Typography variant="body1" gutterBottom>{selectedTemplate.description}</Typography>
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Typography variant="subtitle2" gutterBottom>Steps ({selectedTemplate.steps?.length || 0})</Typography>
|
||||
<Stepper orientation="vertical">
|
||||
{(selectedTemplate.steps || []).map((step, i) => (
|
||||
<Step key={i} active>
|
||||
<StepLabel>
|
||||
<Typography variant="subtitle2">{step.title}</Typography>
|
||||
</StepLabel>
|
||||
<StepContent>
|
||||
<Typography variant="body2" color="text.secondary">{step.description}</Typography>
|
||||
<Chip label={`Action: ${step.action}`} size="small" variant="outlined" sx={{ mt: 0.5 }} />
|
||||
<Typography variant="caption" display="block" mt={0.5}>
|
||||
Expected: {step.expected_outcome}
|
||||
</Typography>
|
||||
</StepContent>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setSelectedTemplate(null)}>Close</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<PlayArrowIcon />}
|
||||
onClick={() => {
|
||||
setStartTemplate(selectedTemplate.name);
|
||||
setSelectedTemplate(null);
|
||||
setStartDialog(true);
|
||||
}}
|
||||
>
|
||||
Start This Playbook
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Dialog>
|
||||
|
||||
{/* Start run dialog */}
|
||||
<Dialog open={startDialog} onClose={() => setStartDialog(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Start Playbook Run</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} mt={1}>
|
||||
<FormControl fullWidth required>
|
||||
<InputLabel>Playbook</InputLabel>
|
||||
<Select value={startTemplate} label="Playbook" onChange={e => setStartTemplate(e.target.value)}>
|
||||
{templates.map(t => <MenuItem key={t.name} value={t.name}>{t.name} ({t.step_count} steps)</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Linked Hunt</InputLabel>
|
||||
<Select value={startHunt} label="Linked Hunt" onChange={e => setStartHunt(e.target.value)}>
|
||||
<MenuItem value="">None</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setStartDialog(false)}>Cancel</Button>
|
||||
<Button variant="contained" startIcon={<PlayArrowIcon />} onClick={startRun} disabled={!startTemplate}>
|
||||
Start
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
526
frontend/src/components/ProcessTree.tsx
Normal file
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* ProcessTree — interactive hierarchical process tree view.
|
||||
*
|
||||
* Key UX improvements:
|
||||
* - Hunt dropdown auto-fetches the full tree to extract hostnames.
|
||||
* - Host dropdown lets the user pick a single host (server-side filter).
|
||||
* - Detail panel is absolutely positioned so it never re-flows the graph.
|
||||
* - ResizeObserver keeps Cytoscape in sync with the container.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Paper, Typography, Stack, Alert, CircularProgress,
|
||||
FormControl, InputLabel, Select, MenuItem, Chip, TextField,
|
||||
IconButton, Tooltip, Divider, Autocomplete,
|
||||
} from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
||||
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
||||
import CenterFocusStrongIcon from '@mui/icons-material/CenterFocusStrong';
|
||||
import cytoscape, { Core, NodeSingular } from 'cytoscape';
|
||||
// @ts-ignore
|
||||
import dagre from 'cytoscape-dagre';
|
||||
import {
|
||||
analysis, hunts, datasets, type Hunt, type DatasetSummary,
|
||||
type ProcessNodeData,
|
||||
} from '../api/client';
|
||||
|
||||
cytoscape.use(dagre);
|
||||
|
||||
/* ── helpers ───────────────────────────────────────────────────────── */
|
||||
|
||||
/** Recursively collect unique hostnames from process tree roots */
|
||||
function collectHostnames(trees: ProcessNodeData[]): string[] {
|
||||
const set = new Set<string>();
|
||||
const walk = (n: ProcessNodeData) => {
|
||||
if (n.hostname) set.add(n.hostname);
|
||||
n.children.forEach(walk);
|
||||
};
|
||||
trees.forEach(walk);
|
||||
return Array.from(set).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
||||
}
|
||||
|
||||
/** Recursively count processes */
|
||||
function countNodes(trees: ProcessNodeData[]): number {
|
||||
let c = 0;
|
||||
const walk = (n: ProcessNodeData) => { c++; n.children.forEach(walk); };
|
||||
trees.forEach(walk);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* ── flatten tree for Cytoscape elements ──────────────────────────── */
|
||||
function flattenTree(
|
||||
node: ProcessNodeData,
|
||||
parentId: string | null,
|
||||
nodes: any[],
|
||||
edges: any[],
|
||||
idSet: Set<string>,
|
||||
) {
|
||||
let id = `${node.hostname}_${node.pid}`;
|
||||
// deduplicate: if PID appears multiple times, append row_index
|
||||
if (idSet.has(id)) id = `${id}_${node.row_index}`;
|
||||
idSet.add(id);
|
||||
|
||||
const cmd = node.command_line || '';
|
||||
const isSuspicious = /powershell\s+-enc|certutil|mimikatz|psexec|mshta|cobalt|meterpreter/i.test(cmd);
|
||||
|
||||
nodes.push({
|
||||
data: {
|
||||
id,
|
||||
label: `${node.name || 'unknown'}\nPID ${node.pid}`,
|
||||
pid: node.pid,
|
||||
ppid: node.ppid,
|
||||
name: node.name,
|
||||
command_line: node.command_line,
|
||||
username: node.username,
|
||||
hostname: node.hostname,
|
||||
timestamp: node.timestamp,
|
||||
dataset_name: node.dataset_name,
|
||||
severity: isSuspicious ? 'high' : 'info',
|
||||
extra: node.extra,
|
||||
},
|
||||
});
|
||||
|
||||
if (parentId) {
|
||||
edges.push({
|
||||
data: { id: `e_${parentId}_${id}`, source: parentId, target: id },
|
||||
});
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
flattenTree(child, id, nodes, edges, idSet);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Cytoscape stylesheet ─────────────────────────────────────────── */
|
||||
const CY_STYLE: cytoscape.StylesheetStyle[] = [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
label: 'data(label)',
|
||||
'text-wrap': 'wrap' as any,
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'font-size': '11px',
|
||||
color: '#e2e8f0',
|
||||
'background-color': '#334155',
|
||||
'border-width': 2,
|
||||
'border-color': '#475569',
|
||||
width: 160,
|
||||
height: 50,
|
||||
shape: 'roundrectangle',
|
||||
'text-max-width': '140px',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[severity = "high"], node[severity = "critical"]',
|
||||
style: {
|
||||
'background-color': '#7f1d1d',
|
||||
'border-color': '#ef4444',
|
||||
'border-width': 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[severity = "medium"]',
|
||||
style: {
|
||||
'background-color': '#713f12',
|
||||
'border-color': '#eab308',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node:selected',
|
||||
style: {
|
||||
'border-color': '#60a5fa',
|
||||
'border-width': 3,
|
||||
'background-color': '#1e3a5f',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
width: 2,
|
||||
'line-color': '#475569',
|
||||
'target-arrow-color': '#475569',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
'arrow-scale': 0.8,
|
||||
},
|
||||
},
|
||||
{ selector: '.dimmed', style: { opacity: 0.15 } },
|
||||
{ selector: '.highlighted', style: { 'border-color': '#22d3ee', 'border-width': 3 } },
|
||||
];
|
||||
|
||||
/* ================================================================== */
|
||||
|
||||
export default function ProcessTree() {
|
||||
/* refs */
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const cyDivRef = useRef<HTMLDivElement>(null);
|
||||
const cyRef = useRef<Core | null>(null);
|
||||
|
||||
/* data */
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
const [datasetList, setDatasetList] = useState<DatasetSummary[]>([]);
|
||||
|
||||
/* selections */
|
||||
const [selectedHunt, setSelectedHunt] = useState('');
|
||||
const [selectedDataset, setSelectedDataset] = useState('');
|
||||
const [hostnames, setHostnames] = useState<string[]>([]);
|
||||
const [selectedHost, setSelectedHost] = useState('');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
/* ui */
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hostsLoading, setHostsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [totalProcs, setTotalProcs] = useState(0);
|
||||
const [selectedNode, setSelectedNode] = useState<Record<string, any> | null>(null);
|
||||
|
||||
/* ── load hunts + datasets on mount ─────────────────────────────── */
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const [h, d] = await Promise.all([
|
||||
hunts.list(0, 100),
|
||||
datasets.list(0, 200),
|
||||
]);
|
||||
setHuntList(h.hunts);
|
||||
setDatasetList(d.datasets);
|
||||
if (h.hunts.length > 0) setSelectedHunt(h.hunts[0].id);
|
||||
} catch {}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
/* ── when hunt changes, fetch all trees to extract hostnames ────── */
|
||||
useEffect(() => {
|
||||
if (!selectedHunt) return;
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
setHostsLoading(true);
|
||||
try {
|
||||
const res = await analysis.processTree({ hunt_id: selectedHunt });
|
||||
if (cancelled) return;
|
||||
const hosts = collectHostnames(res.trees);
|
||||
setHostnames(hosts);
|
||||
setTotalProcs(res.total_processes);
|
||||
// auto-pick first host so the user sees something reasonable
|
||||
if (hosts.length > 0) {
|
||||
setSelectedHost(hosts[0]);
|
||||
} else {
|
||||
setSelectedHost('');
|
||||
}
|
||||
} catch {
|
||||
if (!cancelled) setHostnames([]);
|
||||
}
|
||||
setHostsLoading(false);
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, [selectedHunt]);
|
||||
|
||||
/* filter datasets when hunt changes */
|
||||
const huntDatasets = selectedHunt
|
||||
? datasetList.filter(d => d.hunt_id === selectedHunt)
|
||||
: datasetList;
|
||||
|
||||
/* ── fetch tree (per host) ──────────────────────────────────────── */
|
||||
const fetchTree = useCallback(async (host?: string) => {
|
||||
const huntId = selectedDataset ? undefined : selectedHunt;
|
||||
const dsId = selectedDataset || undefined;
|
||||
if (!huntId && !dsId) return;
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSelectedNode(null);
|
||||
|
||||
try {
|
||||
const hostname = (host ?? selectedHost) || undefined;
|
||||
const res = await analysis.processTree({
|
||||
dataset_id: dsId,
|
||||
hunt_id: huntId,
|
||||
hostname,
|
||||
});
|
||||
|
||||
setTotalProcs(res.total_processes);
|
||||
|
||||
/* build Cytoscape elements */
|
||||
const nodes: any[] = [];
|
||||
const edges: any[] = [];
|
||||
const idSet = new Set<string>();
|
||||
for (const tree of res.trees) {
|
||||
flattenTree(tree, null, nodes, edges, idSet);
|
||||
}
|
||||
|
||||
/* init or update Cytoscape */
|
||||
if (cyRef.current) {
|
||||
cyRef.current.destroy();
|
||||
cyRef.current = null;
|
||||
}
|
||||
if (!cyDivRef.current) return;
|
||||
|
||||
/* choose layout based on whether there are actual parent→child edges */
|
||||
const hasEdges = edges.length > nodes.length * 0.1; // at least 10% connected
|
||||
const layoutConfig = hasEdges
|
||||
? {
|
||||
name: 'dagre',
|
||||
rankDir: 'TB',
|
||||
nodeSep: 40,
|
||||
rankSep: 70,
|
||||
padding: 40,
|
||||
}
|
||||
: {
|
||||
name: 'grid',
|
||||
rows: Math.max(1, Math.ceil(Math.sqrt(nodes.length))),
|
||||
cols: Math.max(1, Math.ceil(Math.sqrt(nodes.length))),
|
||||
padding: 30,
|
||||
avoidOverlap: true,
|
||||
};
|
||||
|
||||
const cy = cytoscape({
|
||||
container: cyDivRef.current,
|
||||
elements: [...nodes, ...edges],
|
||||
style: CY_STYLE as any,
|
||||
layout: layoutConfig as any,
|
||||
minZoom: 0.05,
|
||||
maxZoom: 5,
|
||||
wheelSensitivity: 0.3,
|
||||
});
|
||||
|
||||
/* tap handlers */
|
||||
cy.on('tap', 'node', (evt) => {
|
||||
const nd = (evt.target as NodeSingular).data();
|
||||
setSelectedNode(nd);
|
||||
});
|
||||
cy.on('tap', (evt) => {
|
||||
if (evt.target === cy) setSelectedNode(null);
|
||||
});
|
||||
|
||||
cyRef.current = cy;
|
||||
|
||||
/* fit with padding after layout settles */
|
||||
requestAnimationFrame(() => {
|
||||
cy.resize();
|
||||
cy.fit(undefined, 40);
|
||||
});
|
||||
} catch (e: any) {
|
||||
setError(e.message || 'Failed to load process tree');
|
||||
}
|
||||
setLoading(false);
|
||||
}, [selectedHunt, selectedDataset, selectedHost]);
|
||||
|
||||
/* re-fetch when host changes */
|
||||
useEffect(() => {
|
||||
if (selectedHost) fetchTree(selectedHost);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedHost, selectedDataset]);
|
||||
|
||||
/* ── keep Cytoscape sized correctly ─────────────────────────────── */
|
||||
useEffect(() => {
|
||||
const el = cyDivRef.current;
|
||||
if (!el) return;
|
||||
const ro = new ResizeObserver(() => cyRef.current?.resize());
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, []);
|
||||
|
||||
/* cleanup Cytoscape on unmount */
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (cyRef.current) {
|
||||
cyRef.current.destroy();
|
||||
cyRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
/* ── search highlight ───────────────────────────────────────────── */
|
||||
useEffect(() => {
|
||||
const cy = cyRef.current;
|
||||
if (!cy) return;
|
||||
cy.elements().removeClass('dimmed highlighted');
|
||||
if (!searchText.trim()) return;
|
||||
const q = searchText.toLowerCase();
|
||||
const matched = cy.nodes().filter(n => {
|
||||
const d = n.data();
|
||||
return (d.name || '').toLowerCase().includes(q)
|
||||
|| (d.pid || '').toLowerCase().includes(q)
|
||||
|| (d.command_line || '').toLowerCase().includes(q)
|
||||
|| (d.username || '').toLowerCase().includes(q);
|
||||
});
|
||||
if (matched.length === 0) return;
|
||||
cy.elements().addClass('dimmed');
|
||||
matched.removeClass('dimmed').addClass('highlighted');
|
||||
matched.connectedEdges().removeClass('dimmed');
|
||||
}, [searchText]);
|
||||
|
||||
/* controls */
|
||||
const zoomIn = () => { const cy = cyRef.current; if (cy) cy.zoom({ level: cy.zoom() * 1.3, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } }); };
|
||||
const zoomOut = () => { const cy = cyRef.current; if (cy) cy.zoom({ level: cy.zoom() / 1.3, renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 } }); };
|
||||
const fitAll = () => cyRef.current?.fit(undefined, 40);
|
||||
|
||||
const GRAPH_HEIGHT = 'calc(100vh - 230px)';
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Process Tree</Typography>
|
||||
|
||||
{/* ── Toolbar ──────────────────────────────────────────────── */}
|
||||
<Paper sx={{ p: 1.5, mb: 2 }}>
|
||||
<Stack direction="row" spacing={1.5} alignItems="center" flexWrap="wrap" useFlexGap>
|
||||
{/* Hunt */}
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select label="Hunt" value={selectedHunt}
|
||||
onChange={e => {
|
||||
setSelectedHunt(e.target.value);
|
||||
setSelectedDataset('');
|
||||
setSelectedHost('');
|
||||
setHostnames([]);
|
||||
}}>
|
||||
{huntList.map(h => (
|
||||
<MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Dataset (optional) */}
|
||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
||||
<InputLabel>Dataset (optional)</InputLabel>
|
||||
<Select label="Dataset (optional)" value={selectedDataset}
|
||||
onChange={e => setSelectedDataset(e.target.value)}>
|
||||
<MenuItem value="">All in hunt</MenuItem>
|
||||
{huntDatasets.map(d => (
|
||||
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Host dropdown */}
|
||||
<Autocomplete
|
||||
size="small"
|
||||
sx={{ width: 220 }}
|
||||
options={hostnames}
|
||||
value={selectedHost || null}
|
||||
loading={hostsLoading}
|
||||
onChange={(_e, v) => setSelectedHost(v || '')}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label="Host" placeholder={hostsLoading ? 'Loading hosts…' : 'Pick a host'} />
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Search */}
|
||||
<TextField size="small" label="Search process…" value={searchText}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
sx={{ width: 170 }}
|
||||
/>
|
||||
|
||||
<Tooltip title="Refresh"><IconButton onClick={() => fetchTree()}><RefreshIcon /></IconButton></Tooltip>
|
||||
<Tooltip title="Zoom In"><IconButton onClick={zoomIn}><ZoomInIcon /></IconButton></Tooltip>
|
||||
<Tooltip title="Zoom Out"><IconButton onClick={zoomOut}><ZoomOutIcon /></IconButton></Tooltip>
|
||||
<Tooltip title="Fit"><IconButton onClick={fitAll}><CenterFocusStrongIcon /></IconButton></Tooltip>
|
||||
|
||||
<Chip label={`${totalProcs.toLocaleString()} processes`} size="small" color="info" variant="outlined" />
|
||||
{hostnames.length > 0 && (
|
||||
<Chip label={`${hostnames.length} hosts`} size="small" variant="outlined" />
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
|
||||
{/* ── Graph + overlay Detail panel ─────────────────────────── */}
|
||||
<Box ref={containerRef} sx={{ position: 'relative', height: GRAPH_HEIGHT, minHeight: 400 }}>
|
||||
{/* Cytoscape canvas — dedicated div with NO React children */}
|
||||
<div
|
||||
ref={cyDivRef}
|
||||
style={{
|
||||
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
|
||||
background: '#0f172a', borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Overlays — sibling elements, not children of Cytoscape div */}
|
||||
{loading && (
|
||||
<Box sx={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', zIndex: 5 }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)}
|
||||
{!loading && !selectedHost && hostnames.length > 0 && (
|
||||
<Box sx={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', textAlign: 'center', zIndex: 5 }}>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Select a host from the dropdown to view its process tree
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
{hostnames.length} hosts available with {totalProcs.toLocaleString()} total processes
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Detail panel — overlays graph on the right, doesn't affect layout */}
|
||||
{selectedNode && (
|
||||
<Paper sx={{
|
||||
position: 'absolute', top: 8, right: 8, bottom: 8,
|
||||
width: 340, p: 2, overflow: 'auto', zIndex: 10,
|
||||
bgcolor: 'rgba(15,23,42,0.95)', backdropFilter: 'blur(8px)',
|
||||
border: '1px solid', borderColor: 'divider',
|
||||
}}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6">
|
||||
{selectedNode.name || 'Process'}
|
||||
</Typography>
|
||||
<IconButton size="small" onClick={() => setSelectedNode(null)}><CloseIcon fontSize="small" /></IconButton>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack spacing={0.5}>
|
||||
<DetailRow label="PID" value={selectedNode.pid} />
|
||||
<DetailRow label="PPID" value={selectedNode.ppid} />
|
||||
<DetailRow label="Host" value={selectedNode.hostname} />
|
||||
<DetailRow label="User" value={selectedNode.username} />
|
||||
<DetailRow label="Timestamp" value={selectedNode.timestamp} />
|
||||
<DetailRow label="Dataset" value={selectedNode.dataset_name} />
|
||||
</Stack>
|
||||
{selectedNode.command_line && (
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">Command Line</Typography>
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 0.5, fontFamily: 'monospace', fontSize: 12,
|
||||
wordBreak: 'break-all', bgcolor: 'background.default' }}>
|
||||
{selectedNode.command_line}
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
{selectedNode.extra && Object.keys(selectedNode.extra).length > 0 && (
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">Extra Fields</Typography>
|
||||
<Stack spacing={0.3} sx={{ mt: 0.5 }}>
|
||||
{Object.entries(selectedNode.extra as Record<string, string>).map(([k, v]) => (
|
||||
<DetailRow key={k} label={k} value={v} />
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
<Chip
|
||||
label={selectedNode.severity || 'info'}
|
||||
size="small" sx={{ mt: 1.5 }}
|
||||
color={
|
||||
selectedNode.severity === 'critical' || selectedNode.severity === 'high' ? 'error'
|
||||
: selectedNode.severity === 'medium' ? 'warning'
|
||||
: 'default'
|
||||
}
|
||||
/>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({ label, value }: { label: string; value?: string }) {
|
||||
if (!value) return null;
|
||||
return (
|
||||
<Typography variant="body2">
|
||||
<strong>{label}:</strong>{' '}
|
||||
<span style={{ color: '#94a3b8' }}>{value}</span>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
255
frontend/src/components/QueryBar.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* QueryBar — free-text search with field filters, time-range picker,
|
||||
* and result DataGrid. Works across all datasets in a hunt.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Stack, TextField, Button, IconButton,
|
||||
FormControl, InputLabel, Select, MenuItem, CircularProgress,
|
||||
Alert, Chip, Tooltip, Collapse,
|
||||
} from '@mui/material';
|
||||
import { DataGrid, type GridColDef } from '@mui/x-data-grid';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import {
|
||||
hunts, datasets, analysis,
|
||||
type HuntOut, type DatasetSummary, type SearchRowsResponse,
|
||||
} from '../api/client';
|
||||
|
||||
interface ActiveFilter { field: string; value: string }
|
||||
|
||||
export default function QueryBar() {
|
||||
const [huntList, setHuntList] = useState<HuntOut[]>([]);
|
||||
const [dsList, setDsList] = useState<DatasetSummary[]>([]);
|
||||
const [activeHunt, setActiveHunt] = useState('');
|
||||
const [activeDs, setActiveDs] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [filters, setFilters] = useState<ActiveFilter[]>([]);
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [timeStart, setTimeStart] = useState('');
|
||||
const [timeEnd, setTimeEnd] = useState('');
|
||||
const [results, setResults] = useState<SearchRowsResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [page, setPage] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(50);
|
||||
const [availableFields, setAvailableFields] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Load hunts + datasets
|
||||
useEffect(() => {
|
||||
hunts.list(0, 200).then(r => {
|
||||
setHuntList(r.hunts);
|
||||
if (r.hunts.length > 0) setActiveHunt(r.hunts[0].id);
|
||||
}).catch(() => {});
|
||||
datasets.list(0, 200).then(r => setDsList(r.datasets)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Load field names from field-stats
|
||||
useEffect(() => {
|
||||
if (!activeDs && !activeHunt) return;
|
||||
analysis.fieldStats({
|
||||
dataset_id: activeDs || undefined,
|
||||
hunt_id: activeHunt || undefined,
|
||||
top_n: 5,
|
||||
}).then(r => setAvailableFields(Object.keys(r.fields))).catch(() => {});
|
||||
}, [activeDs, activeHunt]);
|
||||
|
||||
const doSearch = useCallback(async (offset = 0) => {
|
||||
if (!activeDs && !activeHunt) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const filterMap: Record<string, string> = {};
|
||||
filters.forEach(f => { if (f.field && f.value) filterMap[f.field] = f.value; });
|
||||
const r = await analysis.searchRows({
|
||||
dataset_id: activeDs || undefined,
|
||||
hunt_id: activeHunt || undefined,
|
||||
query,
|
||||
filters: Object.keys(filterMap).length > 0 ? filterMap : undefined,
|
||||
time_start: timeStart || undefined,
|
||||
time_end: timeEnd || undefined,
|
||||
limit: pageSize,
|
||||
offset,
|
||||
});
|
||||
setResults(r);
|
||||
} catch (e: any) { setError(e.message); }
|
||||
setLoading(false);
|
||||
}, [activeDs, activeHunt, query, filters, timeStart, timeEnd, pageSize]);
|
||||
|
||||
const handleSearch = () => { setPage(0); doSearch(0); };
|
||||
const handlePageChange = (model: { page: number; pageSize: number }) => {
|
||||
setPage(model.page);
|
||||
setPageSize(model.pageSize);
|
||||
doSearch(model.page * model.pageSize);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') handleSearch();
|
||||
};
|
||||
|
||||
// Filter management
|
||||
const addFilter = () => setFilters(prev => [...prev, { field: '', value: '' }]);
|
||||
const removeFilter = (i: number) => setFilters(prev => prev.filter((_, idx) => idx !== i));
|
||||
const updateFilter = (i: number, key: 'field' | 'value', val: string) =>
|
||||
setFilters(prev => prev.map((f, idx) => idx === i ? { ...f, [key]: val } : f));
|
||||
|
||||
const clearAll = () => {
|
||||
setQuery('');
|
||||
setFilters([]);
|
||||
setTimeStart('');
|
||||
setTimeEnd('');
|
||||
setResults(null);
|
||||
};
|
||||
|
||||
// Build columns from result rows
|
||||
const columns: GridColDef[] = results && results.rows.length > 0
|
||||
? Object.keys(results.rows[0]).filter(k => k !== '__id').map(k => ({
|
||||
field: k, headerName: k, flex: 1, minWidth: 120,
|
||||
}))
|
||||
: [];
|
||||
|
||||
const gridRows = results?.rows.map((r, i) => ({ __id: `r-${page}-${i}`, ...r })) || [];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Search & Query</Typography>
|
||||
|
||||
{/* Source selectors */}
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select label="Hunt" value={activeHunt}
|
||||
onChange={e => { setActiveHunt(e.target.value); setActiveDs(''); }}>
|
||||
<MenuItem value="">— none —</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 220 }}>
|
||||
<InputLabel>Dataset</InputLabel>
|
||||
<Select label="Dataset" value={activeDs}
|
||||
onChange={e => setActiveDs(e.target.value)}>
|
||||
<MenuItem value="">— all datasets —</MenuItem>
|
||||
{dsList.map(d => <MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Search bar */}
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<TextField
|
||||
inputRef={inputRef}
|
||||
size="small" fullWidth
|
||||
placeholder="Free-text search across all fields…"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
InputProps={{
|
||||
startAdornment: <SearchIcon sx={{ color: 'text.secondary', mr: 0.5 }} />,
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Field filters">
|
||||
<IconButton size="small" onClick={() => setShowFilters(s => !s)}
|
||||
color={showFilters ? 'primary' : 'default'}>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Button variant="contained" size="small" onClick={handleSearch}
|
||||
disabled={loading || (!activeDs && !activeHunt)}>
|
||||
Search
|
||||
</Button>
|
||||
<Tooltip title="Clear all">
|
||||
<IconButton size="small" onClick={clearAll}><ClearIcon /></IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
|
||||
{/* Time range */}
|
||||
<Stack direction="row" spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
size="small" label="Time start" type="datetime-local"
|
||||
value={timeStart} onChange={e => setTimeStart(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }} sx={{ width: 220 }}
|
||||
/>
|
||||
<TextField
|
||||
size="small" label="Time end" type="datetime-local"
|
||||
value={timeEnd} onChange={e => setTimeEnd(e.target.value)}
|
||||
InputLabelProps={{ shrink: true }} sx={{ width: 220 }}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{/* Field filters */}
|
||||
<Collapse in={showFilters}>
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 0.5 }}>
|
||||
<Typography variant="caption" fontWeight={700}>Field Filters</Typography>
|
||||
<IconButton size="small" onClick={addFilter}><AddIcon fontSize="small" /></IconButton>
|
||||
</Stack>
|
||||
{filters.map((f, i) => (
|
||||
<Stack key={i} direction="row" spacing={1} alignItems="center" sx={{ mb: 0.5 }}>
|
||||
<FormControl size="small" sx={{ minWidth: 160 }}>
|
||||
<InputLabel>Field</InputLabel>
|
||||
<Select label="Field" value={f.field}
|
||||
onChange={e => updateFilter(i, 'field', e.target.value)}>
|
||||
{availableFields.map(af => (
|
||||
<MenuItem key={af} value={af}>{af}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField size="small" placeholder="Contains…" value={f.value}
|
||||
onChange={e => updateFilter(i, 'value', e.target.value)}
|
||||
sx={{ flex: 1 }} />
|
||||
<IconButton size="small" onClick={() => removeFilter(i)}>
|
||||
<DeleteOutlineIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
))}
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Paper>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
{loading && <CircularProgress sx={{ display: 'block', mx: 'auto', my: 4 }} />}
|
||||
|
||||
{/* Results */}
|
||||
{results && (
|
||||
<>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<Chip label={`${results.total.toLocaleString()} results`} size="small" color="primary" variant="outlined" />
|
||||
{query && <Chip label={`"${query}"`} size="small" onDelete={() => setQuery('')} />}
|
||||
{filters.filter(f => f.field && f.value).map((f, i) => (
|
||||
<Chip key={i} label={`${f.field}: ${f.value}`} size="small"
|
||||
onDelete={() => removeFilter(i)} />
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<Paper sx={{ height: 480 }}>
|
||||
<DataGrid
|
||||
rows={gridRows}
|
||||
columns={columns}
|
||||
getRowId={r => r.__id}
|
||||
rowCount={results.total}
|
||||
loading={loading}
|
||||
paginationMode="server"
|
||||
paginationModel={{ page, pageSize }}
|
||||
onPaginationModelChange={handlePageChange}
|
||||
pageSizeOptions={[25, 50, 100]}
|
||||
density="compact"
|
||||
sx={{
|
||||
border: 'none',
|
||||
'& .MuiDataGrid-cell': { fontSize: '0.8rem' },
|
||||
'& .MuiDataGrid-columnHeader': { fontWeight: 700 },
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
395
frontend/src/components/StorylineGraph.tsx
Normal file
@@ -0,0 +1,395 @@
|
||||
/**
|
||||
* StorylineGraph — CrowdStrike-style attack storyline visualization.
|
||||
*
|
||||
* Renders events as a directed graph using Cytoscape.js with cola layout.
|
||||
* Nodes are colour-coded by event type (process/network/file/registry).
|
||||
* Edges show spawned (parent→child) and temporal relationships.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import {
|
||||
Box, Paper, Typography, Stack, Alert, CircularProgress,
|
||||
FormControl, InputLabel, Select, MenuItem, Chip, TextField,
|
||||
IconButton, Tooltip, Divider, ToggleButton, ToggleButtonGroup,
|
||||
} from '@mui/material';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import ZoomInIcon from '@mui/icons-material/ZoomIn';
|
||||
import ZoomOutIcon from '@mui/icons-material/ZoomOut';
|
||||
import CenterFocusStrongIcon from '@mui/icons-material/CenterFocusStrong';
|
||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||
import TimelineIcon from '@mui/icons-material/Timeline';
|
||||
import cytoscape, { Core, NodeSingular } from 'cytoscape';
|
||||
// @ts-ignore
|
||||
import dagre from 'cytoscape-dagre';
|
||||
// @ts-ignore
|
||||
import cola from 'cytoscape-cola';
|
||||
import {
|
||||
analysis, hunts, datasets, type Hunt, type DatasetSummary,
|
||||
type StorylineResponse,
|
||||
} from '../api/client';
|
||||
|
||||
cytoscape.use(dagre);
|
||||
cytoscape.use(cola);
|
||||
|
||||
/* ── colour palette by event type ──────────────────────────────────── */
|
||||
const EVENT_COLORS: Record<string, string> = {
|
||||
process: '#3b82f6',
|
||||
network: '#8b5cf6',
|
||||
file: '#10b981',
|
||||
registry: '#f59e0b',
|
||||
other: '#6b7280',
|
||||
};
|
||||
|
||||
const SEVERITY_BG: Record<string, string> = {
|
||||
critical: '#7f1d1d',
|
||||
high: '#7f1d1d',
|
||||
medium: '#713f12',
|
||||
low: '#1e3a5f',
|
||||
info: '#1e293b',
|
||||
};
|
||||
|
||||
const SEVERITY_BORDER: Record<string, string> = {
|
||||
critical: '#ef4444',
|
||||
high: '#f97316',
|
||||
medium: '#eab308',
|
||||
low: '#3b82f6',
|
||||
info: '#475569',
|
||||
};
|
||||
|
||||
/* ── shapes per event type ─────────────────────────────────────────── */
|
||||
const EVENT_SHAPES: Record<string, string> = {
|
||||
process: 'roundrectangle',
|
||||
network: 'diamond',
|
||||
file: 'hexagon',
|
||||
registry: 'octagon',
|
||||
other: 'ellipse',
|
||||
};
|
||||
|
||||
/* ── Cytoscape stylesheet ─────────────────────────────────────────── */
|
||||
const CY_STYLE: cytoscape.StylesheetStyle[] = [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
label: 'data(label)',
|
||||
'text-wrap': 'wrap' as any,
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'font-size': '9px',
|
||||
color: '#e2e8f0',
|
||||
'background-color': '#334155',
|
||||
'border-width': 2,
|
||||
'border-color': '#475569',
|
||||
width: 130,
|
||||
height: 45,
|
||||
shape: 'roundrectangle',
|
||||
'text-max-width': '115px',
|
||||
},
|
||||
},
|
||||
/* per event type colours */
|
||||
...Object.entries(EVENT_COLORS).map(([type, color]) => ({
|
||||
selector: `node[event_type = "${type}"]`,
|
||||
style: {
|
||||
'border-color': color,
|
||||
shape: EVENT_SHAPES[type] as any,
|
||||
},
|
||||
})),
|
||||
/* per severity background */
|
||||
...Object.entries(SEVERITY_BG).map(([sev, bg]) => ({
|
||||
selector: `node[severity = "${sev}"]`,
|
||||
style: {
|
||||
'background-color': bg,
|
||||
'border-color': SEVERITY_BORDER[sev],
|
||||
'border-width': sev === 'critical' || sev === 'high' ? 3 : 2,
|
||||
},
|
||||
})),
|
||||
{
|
||||
selector: 'node:selected',
|
||||
style: {
|
||||
'border-color': '#60a5fa',
|
||||
'border-width': 3,
|
||||
'background-color': '#1e3a5f',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
width: 1.5,
|
||||
'line-color': '#475569',
|
||||
'target-arrow-color': '#475569',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
'arrow-scale': 0.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge[relationship = "spawned"]',
|
||||
style: {
|
||||
'line-color': '#3b82f6',
|
||||
'target-arrow-color': '#3b82f6',
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge[relationship = "temporal"]',
|
||||
style: {
|
||||
'line-color': '#334155',
|
||||
'line-style': 'dashed',
|
||||
width: 1,
|
||||
'target-arrow-shape': 'vee',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
type LayoutName = 'dagre' | 'cola';
|
||||
|
||||
export default function StorylineGraph() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const cyRef = useRef<Core | null>(null);
|
||||
|
||||
const [huntList, setHuntList] = useState<Hunt[]>([]);
|
||||
const [datasetList, setDatasetList] = useState<DatasetSummary[]>([]);
|
||||
const [selectedHunt, setSelectedHunt] = useState('');
|
||||
const [selectedDataset, setSelectedDataset] = useState('');
|
||||
const [hostFilter, setHostFilter] = useState('');
|
||||
const [layout, setLayout] = useState<LayoutName>('dagre');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [storyline, setStoryline] = useState<StorylineResponse | null>(null);
|
||||
const [selectedNode, setSelectedNode] = useState<Record<string, any> | null>(null);
|
||||
|
||||
/* load hunts + datasets */
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const [h, d] = await Promise.all([
|
||||
hunts.list(0, 100),
|
||||
datasets.list(0, 200),
|
||||
]);
|
||||
setHuntList(h.hunts);
|
||||
setDatasetList(d.datasets);
|
||||
if (h.hunts.length > 0) setSelectedHunt(h.hunts[0].id);
|
||||
} catch {}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const huntDatasets = selectedHunt
|
||||
? datasetList.filter(d => d.hunt_id === selectedHunt)
|
||||
: datasetList;
|
||||
|
||||
/* fetch storyline */
|
||||
const fetchStoryline = useCallback(async () => {
|
||||
if (!selectedHunt && !selectedDataset) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSelectedNode(null);
|
||||
|
||||
try {
|
||||
const res = await analysis.storyline({
|
||||
dataset_id: selectedDataset || undefined,
|
||||
hunt_id: selectedDataset ? undefined : selectedHunt,
|
||||
hostname: hostFilter || undefined,
|
||||
});
|
||||
setStoryline(res);
|
||||
renderGraph(res);
|
||||
} catch (e: any) {
|
||||
setError(e.message || 'Failed to load storyline');
|
||||
}
|
||||
setLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedHunt, selectedDataset, hostFilter, layout]);
|
||||
|
||||
/* render Cytoscape */
|
||||
const renderGraph = useCallback((data: StorylineResponse) => {
|
||||
if (cyRef.current) cyRef.current.destroy();
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const elements = [...data.nodes, ...data.edges];
|
||||
|
||||
const layoutConfig: any = layout === 'dagre'
|
||||
? { name: 'dagre', rankDir: 'LR', nodeSep: 25, rankSep: 50, padding: 30 }
|
||||
: { name: 'cola', nodeSpacing: 40, edgeLength: 120, animate: false, padding: 30, maxSimulationTime: 3000 };
|
||||
|
||||
const cy = cytoscape({
|
||||
container: containerRef.current,
|
||||
elements,
|
||||
style: CY_STYLE as any,
|
||||
layout: layoutConfig,
|
||||
minZoom: 0.05,
|
||||
maxZoom: 4,
|
||||
wheelSensitivity: 0.3,
|
||||
});
|
||||
|
||||
cy.on('tap', 'node', (evt) => {
|
||||
setSelectedNode((evt.target as NodeSingular).data());
|
||||
});
|
||||
cy.on('tap', (evt) => {
|
||||
if (evt.target === cy) setSelectedNode(null);
|
||||
});
|
||||
|
||||
cyRef.current = cy;
|
||||
}, [layout]);
|
||||
|
||||
/* refetch on param change */
|
||||
useEffect(() => {
|
||||
if (selectedHunt || selectedDataset) fetchStoryline();
|
||||
}, [selectedHunt, selectedDataset, fetchStoryline]);
|
||||
|
||||
/* re-render on layout change */
|
||||
useEffect(() => {
|
||||
if (storyline) renderGraph(storyline);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [layout]);
|
||||
|
||||
/* controls */
|
||||
const zoomIn = () => cyRef.current?.zoom(cyRef.current.zoom() * 1.3);
|
||||
const zoomOut = () => cyRef.current?.zoom(cyRef.current.zoom() / 1.3);
|
||||
const fitAll = () => cyRef.current?.fit(undefined, 40);
|
||||
|
||||
const summary = storyline?.summary;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Storyline Attack Graph</Typography>
|
||||
|
||||
{/* Controls */}
|
||||
<Paper sx={{ p: 1.5, mb: 2 }}>
|
||||
<Stack direction="row" spacing={1.5} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select label="Hunt" value={selectedHunt}
|
||||
onChange={e => { setSelectedHunt(e.target.value); setSelectedDataset(''); }}>
|
||||
{huntList.map(h => (
|
||||
<MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
||||
<InputLabel>Dataset (optional)</InputLabel>
|
||||
<Select label="Dataset (optional)" value={selectedDataset}
|
||||
onChange={e => setSelectedDataset(e.target.value)}>
|
||||
<MenuItem value="">All in hunt</MenuItem>
|
||||
{huntDatasets.map(d => (
|
||||
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<TextField size="small" label="Hostname" value={hostFilter}
|
||||
onChange={e => setHostFilter(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && fetchStoryline()}
|
||||
sx={{ width: 160 }}
|
||||
/>
|
||||
|
||||
<ToggleButtonGroup size="small" value={layout} exclusive
|
||||
onChange={(_, v) => v && setLayout(v)}>
|
||||
<ToggleButton value="dagre"><Tooltip title="Hierarchical"><AccountTreeIcon fontSize="small" /></Tooltip></ToggleButton>
|
||||
<ToggleButton value="cola"><Tooltip title="Force-directed"><TimelineIcon fontSize="small" /></Tooltip></ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<Tooltip title="Refresh"><IconButton onClick={fetchStoryline}><RefreshIcon /></IconButton></Tooltip>
|
||||
<Tooltip title="Zoom In"><IconButton onClick={zoomIn}><ZoomInIcon /></IconButton></Tooltip>
|
||||
<Tooltip title="Zoom Out"><IconButton onClick={zoomOut}><ZoomOutIcon /></IconButton></Tooltip>
|
||||
<Tooltip title="Fit"><IconButton onClick={fitAll}><CenterFocusStrongIcon /></IconButton></Tooltip>
|
||||
</Stack>
|
||||
|
||||
{/* Legend + stats */}
|
||||
{summary && (
|
||||
<Stack direction="row" spacing={1} sx={{ mt: 1 }} flexWrap="wrap" alignItems="center">
|
||||
<Chip label={`${summary.total_events} events`} size="small" variant="outlined" />
|
||||
<Chip label={`${summary.total_edges} edges`} size="small" variant="outlined" />
|
||||
<Chip label={`${summary.hosts?.length || 0} hosts`} size="small" variant="outlined" />
|
||||
<Divider orientation="vertical" flexItem />
|
||||
{Object.entries(EVENT_COLORS).map(([type, color]) => (
|
||||
<Chip
|
||||
key={type}
|
||||
label={`${type} (${summary.event_types?.[type] || 0})`}
|
||||
size="small"
|
||||
sx={{ borderColor: color, color }}
|
||||
variant="outlined"
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
|
||||
{/* Graph + Detail */}
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Paper
|
||||
ref={containerRef}
|
||||
sx={{
|
||||
flex: 1,
|
||||
height: 'calc(100vh - 280px)',
|
||||
minHeight: 400,
|
||||
bgcolor: '#0f172a',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{loading && (
|
||||
<Box sx={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)' }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{selectedNode && (
|
||||
<Paper sx={{ width: 340, p: 2, maxHeight: 'calc(100vh - 280px)', overflow: 'auto' }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{selectedNode.process_name || selectedNode.label || 'Event'}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={selectedNode.event_type}
|
||||
size="small"
|
||||
sx={{ borderColor: EVENT_COLORS[selectedNode.event_type] || '#6b7280',
|
||||
color: EVENT_COLORS[selectedNode.event_type] || '#6b7280', mb: 1 }}
|
||||
variant="outlined"
|
||||
/>
|
||||
<Chip
|
||||
label={selectedNode.severity}
|
||||
size="small"
|
||||
sx={{ ml: 0.5, mb: 1 }}
|
||||
color={
|
||||
selectedNode.severity === 'critical' || selectedNode.severity === 'high' ? 'error'
|
||||
: selectedNode.severity === 'medium' ? 'warning'
|
||||
: 'default'
|
||||
}
|
||||
/>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack spacing={0.5}>
|
||||
<DetailRow label="Hostname" value={selectedNode.hostname} />
|
||||
<DetailRow label="PID" value={selectedNode.pid} />
|
||||
<DetailRow label="PPID" value={selectedNode.ppid} />
|
||||
<DetailRow label="User" value={selectedNode.username} />
|
||||
<DetailRow label="Timestamp" value={selectedNode.timestamp} />
|
||||
<DetailRow label="Src IP" value={selectedNode.src_ip} />
|
||||
<DetailRow label="Dst IP" value={selectedNode.dst_ip} />
|
||||
<DetailRow label="Dst Port" value={selectedNode.dst_port} />
|
||||
<DetailRow label="File" value={selectedNode.file_path} />
|
||||
</Stack>
|
||||
{selectedNode.command_line && (
|
||||
<Box sx={{ mt: 1.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">Command Line</Typography>
|
||||
<Paper variant="outlined" sx={{ p: 1, mt: 0.5, fontFamily: 'monospace', fontSize: 12,
|
||||
wordBreak: 'break-all', bgcolor: 'background.default' }}>
|
||||
{selectedNode.command_line}
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function DetailRow({ label, value }: { label: string; value?: string }) {
|
||||
if (!value) return null;
|
||||
return (
|
||||
<Typography variant="body2">
|
||||
<strong>{label}:</strong>{' '}
|
||||
<span style={{ color: '#94a3b8' }}>{value}</span>
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
283
frontend/src/components/TimelineScrubber.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* TimelineScrubber — interactive temporal histogram with brush selection,
|
||||
* event-type stacking, and field-stats sidebar.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Box, Typography, Paper, Stack, FormControl, InputLabel, Select,
|
||||
MenuItem, CircularProgress, Alert, Chip, Tooltip, IconButton,
|
||||
ToggleButton, ToggleButtonGroup, Slider, List, ListItem,
|
||||
ListItemText, LinearProgress,
|
||||
} from '@mui/material';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import PauseIcon from '@mui/icons-material/Pause';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import BarChartIcon from '@mui/icons-material/BarChart';
|
||||
import StackedBarChartIcon from '@mui/icons-material/StackedBarChart';
|
||||
import {
|
||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RTooltip,
|
||||
ResponsiveContainer, Brush, BarChart, Bar, Legend,
|
||||
} from 'recharts';
|
||||
import {
|
||||
hunts, datasets, analysis,
|
||||
type HuntOut, type DatasetSummary, type TimelineBin,
|
||||
} from '../api/client';
|
||||
|
||||
const EVENT_COLORS: Record<string, string> = {
|
||||
process: '#3b82f6',
|
||||
network: '#8b5cf6',
|
||||
file: '#10b981',
|
||||
registry: '#f59e0b',
|
||||
authentication: '#ef4444',
|
||||
dns: '#06b6d4',
|
||||
other: '#6b7280',
|
||||
};
|
||||
|
||||
function shortTime(iso: string) {
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return iso.slice(0, 19);
|
||||
return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
|
||||
export default function TimelineScrubber() {
|
||||
const [huntList, setHuntList] = useState<HuntOut[]>([]);
|
||||
const [dsList, setDsList] = useState<DatasetSummary[]>([]);
|
||||
const [activeHunt, setActiveHunt] = useState<string>('');
|
||||
const [activeDs, setActiveDs] = useState<string>('');
|
||||
const [bins, setBins] = useState<TimelineBin[]>([]);
|
||||
const [fieldStats, setFieldStats] = useState<Record<string, { total: number; unique: number; top: { value: string; count: number; pct: number }[] }>>({});
|
||||
const [totalRows, setTotalRows] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [chartType, setChartType] = useState<'area' | 'bar'>('area');
|
||||
const [numBins, setNumBins] = useState(80);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [playIdx, setPlayIdx] = useState(0);
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
// Load hunts and datasets
|
||||
useEffect(() => {
|
||||
hunts.list(0, 200).then(r => {
|
||||
setHuntList(r.hunts);
|
||||
if (r.hunts.length > 0) setActiveHunt(r.hunts[0].id);
|
||||
}).catch(() => {});
|
||||
datasets.list(0, 200).then(r => {
|
||||
setDsList(r.datasets);
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Fetch timeline bins
|
||||
const fetchData = useCallback(async () => {
|
||||
if (!activeDs && !activeHunt) return;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const [tl, fs] = await Promise.all([
|
||||
analysis.timeline({ dataset_id: activeDs || undefined, hunt_id: activeHunt || undefined, bins: numBins }),
|
||||
analysis.fieldStats({ dataset_id: activeDs || undefined, hunt_id: activeHunt || undefined }),
|
||||
]);
|
||||
setBins(tl.bins);
|
||||
setTotalRows(tl.total);
|
||||
setFieldStats(fs.fields);
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
}
|
||||
setLoading(false);
|
||||
}, [activeDs, activeHunt, numBins]);
|
||||
|
||||
useEffect(() => { fetchData(); }, [fetchData]);
|
||||
|
||||
// Playback animation
|
||||
useEffect(() => {
|
||||
if (playing && bins.length > 0) {
|
||||
timerRef.current = setInterval(() => {
|
||||
setPlayIdx(prev => {
|
||||
if (prev >= bins.length - 1) {
|
||||
setPlaying(false);
|
||||
return 0;
|
||||
}
|
||||
return prev + 1;
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
return () => { if (timerRef.current) clearInterval(timerRef.current); };
|
||||
}, [playing, bins.length]);
|
||||
|
||||
// Collect all event types across bins
|
||||
const eventTypes = Array.from(new Set(bins.flatMap(b => Object.keys(b.types || {}))));
|
||||
|
||||
// Transform bins for recharts
|
||||
const chartData = bins.map((b, i) => ({
|
||||
name: shortTime(b.start),
|
||||
total: b.count,
|
||||
...b.types,
|
||||
_idx: i,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>Timeline Scrubber</Typography>
|
||||
|
||||
{/* Selectors */}
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
|
||||
<FormControl size="small" sx={{ minWidth: 180 }}>
|
||||
<InputLabel>Hunt</InputLabel>
|
||||
<Select
|
||||
label="Hunt" value={activeHunt}
|
||||
onChange={e => { setActiveHunt(e.target.value); setActiveDs(''); }}
|
||||
>
|
||||
<MenuItem value="">— none —</MenuItem>
|
||||
{huntList.map(h => <MenuItem key={h.id} value={h.id}>{h.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 220 }}>
|
||||
<InputLabel>Dataset</InputLabel>
|
||||
<Select
|
||||
label="Dataset" value={activeDs}
|
||||
onChange={e => setActiveDs(e.target.value)}
|
||||
>
|
||||
<MenuItem value="">— all datasets —</MenuItem>
|
||||
{dsList.map(d => <MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Box sx={{ width: 140, px: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary">Bins: {numBins}</Typography>
|
||||
<Slider size="small" min={20} max={200} step={10} value={numBins}
|
||||
onChange={(_, v) => setNumBins(v as number)} />
|
||||
</Box>
|
||||
|
||||
<ToggleButtonGroup size="small" exclusive value={chartType}
|
||||
onChange={(_, v) => { if (v) setChartType(v); }}>
|
||||
<ToggleButton value="area"><BarChartIcon fontSize="small" /></ToggleButton>
|
||||
<ToggleButton value="bar"><StackedBarChartIcon fontSize="small" /></ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<Chip label={`${totalRows.toLocaleString()} events`} size="small" color="primary" variant="outlined" />
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
{loading && <CircularProgress sx={{ display: 'block', mx: 'auto', my: 4 }} />}
|
||||
|
||||
{!loading && bins.length > 0 && (
|
||||
<Stack direction="row" spacing={2}>
|
||||
{/* Main chart */}
|
||||
<Paper sx={{ flex: 1, p: 2 }}>
|
||||
{/* Playback controls */}
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ mb: 1 }}>
|
||||
<Tooltip title={playing ? 'Pause' : 'Play animation'}>
|
||||
<IconButton size="small" onClick={() => setPlaying(p => !p)}>
|
||||
{playing ? <PauseIcon /> : <PlayArrowIcon />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Reset">
|
||||
<IconButton size="small" onClick={() => { setPlaying(false); setPlayIdx(0); }}>
|
||||
<RestartAltIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{playing && (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Bin {playIdx + 1} / {bins.length}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<ResponsiveContainer width="100%" height={320}>
|
||||
{chartType === 'area' ? (
|
||||
<AreaChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#333" />
|
||||
<XAxis dataKey="name" tick={{ fontSize: 10 }} interval="preserveStartEnd" />
|
||||
<YAxis tick={{ fontSize: 10 }} />
|
||||
<RTooltip
|
||||
contentStyle={{ background: '#1e1e1e', border: '1px solid #444', fontSize: 12 }}
|
||||
labelStyle={{ color: '#aaa' }}
|
||||
/>
|
||||
<Legend wrapperStyle={{ fontSize: 11 }} />
|
||||
{eventTypes.map(t => (
|
||||
<Area
|
||||
key={t} type="monotone" dataKey={t} stackId="1"
|
||||
fill={EVENT_COLORS[t] || EVENT_COLORS.other}
|
||||
stroke={EVENT_COLORS[t] || EVENT_COLORS.other}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
))}
|
||||
<Brush
|
||||
dataKey="name" height={28} stroke="#666"
|
||||
startIndex={0}
|
||||
endIndex={playing ? Math.min(playIdx, bins.length - 1) : undefined}
|
||||
fill="#1a1a1a"
|
||||
travellerWidth={8}
|
||||
/>
|
||||
</AreaChart>
|
||||
) : (
|
||||
<BarChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#333" />
|
||||
<XAxis dataKey="name" tick={{ fontSize: 10 }} interval="preserveStartEnd" />
|
||||
<YAxis tick={{ fontSize: 10 }} />
|
||||
<RTooltip
|
||||
contentStyle={{ background: '#1e1e1e', border: '1px solid #444', fontSize: 12 }}
|
||||
labelStyle={{ color: '#aaa' }}
|
||||
/>
|
||||
<Legend wrapperStyle={{ fontSize: 11 }} />
|
||||
{eventTypes.map(t => (
|
||||
<Bar
|
||||
key={t} dataKey={t} stackId="1"
|
||||
fill={EVENT_COLORS[t] || EVENT_COLORS.other}
|
||||
/>
|
||||
))}
|
||||
<Brush dataKey="name" height={28} stroke="#666" fill="#1a1a1a" />
|
||||
</BarChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
</Paper>
|
||||
|
||||
{/* Field stats sidebar */}
|
||||
<Paper sx={{ width: 320, p: 2, maxHeight: 440, overflow: 'auto' }}>
|
||||
<Typography variant="subtitle2" gutterBottom>Field Statistics</Typography>
|
||||
{Object.entries(fieldStats).slice(0, 12).map(([field, stat]) => (
|
||||
<Box key={field} sx={{ mb: 1.5 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="caption" fontWeight={700}>{field}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{stat.unique} unique
|
||||
</Typography>
|
||||
</Stack>
|
||||
<List dense disablePadding>
|
||||
{stat.top.slice(0, 5).map(v => (
|
||||
<ListItem key={v.value} disablePadding sx={{ py: 0 }}>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<Typography variant="caption" noWrap sx={{ maxWidth: 140 }}>
|
||||
{v.value || '(empty)'}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{v.count}
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
secondary={
|
||||
<LinearProgress
|
||||
variant="determinate" value={v.pct}
|
||||
sx={{ height: 3, borderRadius: 1, mt: 0.3 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
))}
|
||||
</Paper>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{!loading && bins.length === 0 && !error && (
|
||||
<Alert severity="info">Select a hunt or dataset to view the timeline.</Alert>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
2
frontend/src/declarations.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare module 'cytoscape-dagre';
|
||||
declare module 'cytoscape-cola';
|
||||