agentic-threat-hunting-framework 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentic_threat_hunting_framework-0.2.0.dist-info → agentic_threat_hunting_framework-0.2.2.dist-info}/METADATA +1 -1
- agentic_threat_hunting_framework-0.2.2.dist-info/RECORD +23 -0
- athf/__version__.py +1 -1
- athf/commands/context.py +3 -3
- athf/commands/env.py +2 -1
- athf/commands/investigate.py +8 -8
- athf/commands/similar.py +4 -4
- athf/core/attack_matrix.py +19 -4
- athf/core/hunt_manager.py +2 -2
- athf/core/investigation_parser.py +3 -3
- agentic_threat_hunting_framework-0.2.0.dist-info/RECORD +0 -23
- {agentic_threat_hunting_framework-0.2.0.dist-info → agentic_threat_hunting_framework-0.2.2.dist-info}/WHEEL +0 -0
- {agentic_threat_hunting_framework-0.2.0.dist-info → agentic_threat_hunting_framework-0.2.2.dist-info}/entry_points.txt +0 -0
- {agentic_threat_hunting_framework-0.2.0.dist-info → agentic_threat_hunting_framework-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {agentic_threat_hunting_framework-0.2.0.dist-info → agentic_threat_hunting_framework-0.2.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-threat-hunting-framework
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Agentic Threat Hunting Framework - Memory and AI for threat hunters
|
|
5
5
|
Author-email: Sydney Marrone <athf@nebulock.io>
|
|
6
6
|
Maintainer-email: Sydney Marrone <athf@nebulock.io>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
agentic_threat_hunting_framework-0.2.2.dist-info/licenses/LICENSE,sha256=_KObErRfiKoolznt-DF0nJnr3U9Rdh7Z4Ba7G5qqckk,1071
|
|
2
|
+
athf/__init__.py,sha256=OrjZe8P97_BTEkscapnwSsqKSjwXNP9d8-HtGr19Ni0,241
|
|
3
|
+
athf/__version__.py,sha256=p9cAuZ-dTEMpo-qoeYkFo2166r8LvKpa5qHBZihGq3w,59
|
|
4
|
+
athf/cli.py,sha256=XLNRXEs9kHPH6utJ7_SnzLFcldbGAnACPMTe0xMOkhQ,4492
|
|
5
|
+
athf/commands/__init__.py,sha256=uDyr0bz-agpGO8fraXQl24wuQCxqbeCevZsJ2bDK29s,25
|
|
6
|
+
athf/commands/context.py,sha256=XpMtTf9Pq6NxMhawp6f1NYnTKYt1IuGx9CNDIY8K8Do,11956
|
|
7
|
+
athf/commands/env.py,sha256=AisRllJXbyCjK_2ii21qBBmCz9raxhBUemwM7BxqIYg,11859
|
|
8
|
+
athf/commands/hunt.py,sha256=2KORNWAqEvLY-Wc1q-a894g8kOpcqw_iJfnenKJeTDI,23019
|
|
9
|
+
athf/commands/init.py,sha256=L_29fvZF8SZ1BKh2D6NyDuacCC5JXOTezIxdBnnK88E,10941
|
|
10
|
+
athf/commands/investigate.py,sha256=mK_id5vjfN_ukqB_-fyia0FNa0pBmtn0Xv6CKHQI1Qo,24663
|
|
11
|
+
athf/commands/similar.py,sha256=ROoMs4NP1otCaXwM1XzpLWxmANknoeASlBT7zuMDqas,11793
|
|
12
|
+
athf/core/__init__.py,sha256=yG7C8ljx3UW4QZoYvDjUxsWHlbS8M-GLGB7Je7rRfqo,31
|
|
13
|
+
athf/core/attack_matrix.py,sha256=QZKKmxckQ6-U7lqVdGUJoj2jEAhP3Juvr3sqaNx2oTw,3238
|
|
14
|
+
athf/core/hunt_manager.py,sha256=6DC3wmreJ5IBiC7vi9xB9DP_WDXOetmGceFPTqjYVRU,11366
|
|
15
|
+
athf/core/hunt_parser.py,sha256=FUj0yyBIcZnaS9aItMImeBDhegQwpkewIwUMNXW_ZWU,5122
|
|
16
|
+
athf/core/investigation_parser.py,sha256=wbfjnq4gFgIc0a4bHIAnidVNPhbHDpIXWY1SGLk0Xls,6804
|
|
17
|
+
athf/core/template_engine.py,sha256=vNTVhlxIXZpxU7VmQyrqCSt6ORS0IVjAV54TOmUDMTE,5636
|
|
18
|
+
athf/utils/__init__.py,sha256=aEAPI1xnAsowOtc036cCb9ZOek5nrrfevu8PElhbNgk,30
|
|
19
|
+
agentic_threat_hunting_framework-0.2.2.dist-info/METADATA,sha256=zFr9-YmLEz0jqC0rmqEUmOmMhl-yMUr20U9IrqUrfcI,15472
|
|
20
|
+
agentic_threat_hunting_framework-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
agentic_threat_hunting_framework-0.2.2.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
|
|
22
|
+
agentic_threat_hunting_framework-0.2.2.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
|
|
23
|
+
agentic_threat_hunting_framework-0.2.2.dist-info/RECORD,,
|
athf/__version__.py
CHANGED
athf/commands/context.py
CHANGED
|
@@ -197,7 +197,7 @@ def _build_context(
|
|
|
197
197
|
|
|
198
198
|
def _read_and_optimize(file_path: Path) -> str:
|
|
199
199
|
"""Read file and optimize for token efficiency."""
|
|
200
|
-
content = file_path.read_text()
|
|
200
|
+
content = file_path.read_text(encoding='utf-8')
|
|
201
201
|
|
|
202
202
|
# First pass: Remove all control characters except tabs and newlines
|
|
203
203
|
# Control characters are U+0000 through U+001F (0-31), except tab (9), LF (10), CR (13)
|
|
@@ -234,7 +234,7 @@ def _find_hunts_by_tactic(tactic: str) -> List[Path]:
|
|
|
234
234
|
normalized_tactic = tactic.replace("-", " ").lower()
|
|
235
235
|
|
|
236
236
|
for hunt_file in hunts_dir.glob("H-*.md"):
|
|
237
|
-
content = hunt_file.read_text()
|
|
237
|
+
content = hunt_file.read_text(encoding='utf-8')
|
|
238
238
|
|
|
239
239
|
# Check YAML frontmatter for tactics field
|
|
240
240
|
if content.startswith("---"):
|
|
@@ -263,7 +263,7 @@ def _find_hunts_by_platform(platform: str) -> List[Path]:
|
|
|
263
263
|
normalized_platform = platform.lower()
|
|
264
264
|
|
|
265
265
|
for hunt_file in hunts_dir.glob("H-*.md"):
|
|
266
|
-
content = hunt_file.read_text()
|
|
266
|
+
content = hunt_file.read_text(encoding='utf-8')
|
|
267
267
|
|
|
268
268
|
# Check YAML frontmatter for platform field
|
|
269
269
|
if content.startswith("---"):
|
athf/commands/env.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import subprocess # nosec B404
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from typing import Union
|
|
6
7
|
|
|
7
8
|
import click
|
|
8
9
|
from rich.console import Console
|
|
@@ -280,7 +281,7 @@ def info() -> None:
|
|
|
280
281
|
|
|
281
282
|
# Get installed packages count
|
|
282
283
|
pip_path = python_path.parent / "pip"
|
|
283
|
-
package_count: int
|
|
284
|
+
package_count: Union[int, str]
|
|
284
285
|
try:
|
|
285
286
|
result = subprocess.run(
|
|
286
287
|
[str(pip_path), "list", "--format", "freeze"],
|
athf/commands/investigate.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
6
|
+
from typing import List, Optional, Tuple
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
import yaml
|
|
@@ -82,8 +82,8 @@ def new(
|
|
|
82
82
|
title: Optional[str],
|
|
83
83
|
investigation_type: Optional[str],
|
|
84
84
|
tags: Optional[str],
|
|
85
|
-
data_source:
|
|
86
|
-
related_hunt:
|
|
85
|
+
data_source: Tuple[str, ...],
|
|
86
|
+
related_hunt: Tuple[str, ...],
|
|
87
87
|
investigator: Optional[str],
|
|
88
88
|
non_interactive: bool,
|
|
89
89
|
) -> None:
|
|
@@ -200,9 +200,9 @@ def _render_investigation_template(
|
|
|
200
200
|
title: str,
|
|
201
201
|
investigator: str,
|
|
202
202
|
investigation_type: str,
|
|
203
|
-
tags:
|
|
204
|
-
data_sources:
|
|
205
|
-
related_hunts:
|
|
203
|
+
tags: List[str],
|
|
204
|
+
data_sources: List[str],
|
|
205
|
+
related_hunts: List[str],
|
|
206
206
|
) -> str:
|
|
207
207
|
"""Render investigation template with provided values.
|
|
208
208
|
|
|
@@ -569,8 +569,8 @@ def validate(investigation_id: str) -> None:
|
|
|
569
569
|
def promote(
|
|
570
570
|
investigation_id: str,
|
|
571
571
|
technique: Optional[str],
|
|
572
|
-
tactic:
|
|
573
|
-
platform:
|
|
572
|
+
tactic: Tuple[str, ...],
|
|
573
|
+
platform: Tuple[str, ...],
|
|
574
574
|
status: str,
|
|
575
575
|
non_interactive: bool,
|
|
576
576
|
) -> None:
|
athf/commands/similar.py
CHANGED
|
@@ -121,7 +121,7 @@ def _get_hunt_text(hunt_id: str) -> Optional[str]:
|
|
|
121
121
|
hunt_file = Path(f"hunts/{hunt_id}.md")
|
|
122
122
|
if not hunt_file.exists():
|
|
123
123
|
return None
|
|
124
|
-
return hunt_file.read_text()
|
|
124
|
+
return hunt_file.read_text(encoding='utf-8')
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
def _find_similar_hunts(
|
|
@@ -132,8 +132,8 @@ def _find_similar_hunts(
|
|
|
132
132
|
) -> List[Dict[str, Any]]:
|
|
133
133
|
"""Find similar hunts using TF-IDF similarity."""
|
|
134
134
|
try:
|
|
135
|
-
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
136
|
-
from sklearn.metrics.pairwise import cosine_similarity
|
|
135
|
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
136
|
+
from sklearn.metrics.pairwise import cosine_similarity
|
|
137
137
|
except ImportError:
|
|
138
138
|
console.print("[red]Error: scikit-learn not installed[/red]")
|
|
139
139
|
console.print("[dim]Install with: pip install scikit-learn[/dim]")
|
|
@@ -156,7 +156,7 @@ def _find_similar_hunts(
|
|
|
156
156
|
if exclude_hunt and hunt_id == exclude_hunt:
|
|
157
157
|
continue
|
|
158
158
|
|
|
159
|
-
content = hunt_file.read_text()
|
|
159
|
+
content = hunt_file.read_text(encoding='utf-8')
|
|
160
160
|
metadata = _extract_hunt_metadata(content)
|
|
161
161
|
|
|
162
162
|
# Extract searchable text (weighted semantic sections)
|
athf/core/attack_matrix.py
CHANGED
|
@@ -4,9 +4,20 @@ This module contains reference data for the MITRE ATT&CK Enterprise matrix,
|
|
|
4
4
|
including tactic ordering and technique counts.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from typing import Dict, List, TypedDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TacticInfo(TypedDict):
|
|
11
|
+
"""Type definition for tactic information."""
|
|
12
|
+
|
|
13
|
+
name: str
|
|
14
|
+
technique_count: int
|
|
15
|
+
order: int
|
|
16
|
+
|
|
17
|
+
|
|
7
18
|
# MITRE ATT&CK Enterprise Matrix v14 (January 2024)
|
|
8
19
|
# Approximate technique counts per tactic (includes sub-techniques)
|
|
9
|
-
ATTACK_TACTICS = {
|
|
20
|
+
ATTACK_TACTICS: Dict[str, TacticInfo] = {
|
|
10
21
|
"reconnaissance": {
|
|
11
22
|
"name": "Reconnaissance",
|
|
12
23
|
"technique_count": 10,
|
|
@@ -92,7 +103,9 @@ def get_tactic_display_name(tactic_key: str) -> str:
|
|
|
92
103
|
Returns:
|
|
93
104
|
Display name (e.g., "Credential Access")
|
|
94
105
|
"""
|
|
95
|
-
|
|
106
|
+
if tactic_key in ATTACK_TACTICS:
|
|
107
|
+
return ATTACK_TACTICS[tactic_key]["name"]
|
|
108
|
+
return tactic_key.replace("-", " ").title()
|
|
96
109
|
|
|
97
110
|
|
|
98
111
|
def get_tactic_technique_count(tactic_key: str) -> int:
|
|
@@ -104,10 +117,12 @@ def get_tactic_technique_count(tactic_key: str) -> int:
|
|
|
104
117
|
Returns:
|
|
105
118
|
Total technique count for the tactic
|
|
106
119
|
"""
|
|
107
|
-
|
|
120
|
+
if tactic_key in ATTACK_TACTICS:
|
|
121
|
+
return ATTACK_TACTICS[tactic_key]["technique_count"]
|
|
122
|
+
return 0
|
|
108
123
|
|
|
109
124
|
|
|
110
|
-
def get_sorted_tactics() ->
|
|
125
|
+
def get_sorted_tactics() -> List[str]:
|
|
111
126
|
"""Get all tactic keys sorted by ATT&CK matrix order.
|
|
112
127
|
|
|
113
128
|
Returns:
|
athf/core/hunt_manager.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
5
|
+
from typing import Any, Dict, List, Optional, Set
|
|
6
6
|
|
|
7
7
|
from athf.core.attack_matrix import ATTACK_TACTICS, TOTAL_TECHNIQUES, get_sorted_tactics
|
|
8
8
|
from athf.core.hunt_parser import parse_hunt_file
|
|
@@ -261,7 +261,7 @@ class HuntManager:
|
|
|
261
261
|
"total_techniques": ATTACK_TACTICS[tactic_key]["technique_count"],
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
all_unique_techniques:
|
|
264
|
+
all_unique_techniques: Set[str] = set()
|
|
265
265
|
|
|
266
266
|
for hunt in hunts:
|
|
267
267
|
hunt_id = hunt.get("hunt_id", "UNKNOWN")
|
|
@@ -8,7 +8,7 @@ Investigation parser is simpler than hunt parser:
|
|
|
8
8
|
|
|
9
9
|
import re
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any, Dict, List
|
|
11
|
+
from typing import Any, Dict, List, Tuple
|
|
12
12
|
|
|
13
13
|
import yaml
|
|
14
14
|
|
|
@@ -85,7 +85,7 @@ class InvestigationParser:
|
|
|
85
85
|
|
|
86
86
|
return content_without_fm.strip()
|
|
87
87
|
|
|
88
|
-
def validate(self) ->
|
|
88
|
+
def validate(self) -> Tuple[bool, List[str]]:
|
|
89
89
|
"""Validate investigation structure.
|
|
90
90
|
|
|
91
91
|
Lightweight validation - only checks minimal required fields.
|
|
@@ -140,7 +140,7 @@ def parse_investigation_file(file_path: Path) -> Dict[str, Any]:
|
|
|
140
140
|
return parser.parse()
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
def validate_investigation_file(file_path: Path) ->
|
|
143
|
+
def validate_investigation_file(file_path: Path) -> Tuple[bool, List[str]]:
|
|
144
144
|
"""Convenience function to validate an investigation file.
|
|
145
145
|
|
|
146
146
|
Args:
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
agentic_threat_hunting_framework-0.2.0.dist-info/licenses/LICENSE,sha256=_KObErRfiKoolznt-DF0nJnr3U9Rdh7Z4Ba7G5qqckk,1071
|
|
2
|
-
athf/__init__.py,sha256=OrjZe8P97_BTEkscapnwSsqKSjwXNP9d8-HtGr19Ni0,241
|
|
3
|
-
athf/__version__.py,sha256=qT3kQNuJrw6IXvX9OpVNABZ8axShk7vcwAcufNBLlls,59
|
|
4
|
-
athf/cli.py,sha256=XLNRXEs9kHPH6utJ7_SnzLFcldbGAnACPMTe0xMOkhQ,4492
|
|
5
|
-
athf/commands/__init__.py,sha256=uDyr0bz-agpGO8fraXQl24wuQCxqbeCevZsJ2bDK29s,25
|
|
6
|
-
athf/commands/context.py,sha256=nWETwEqPMTxxkUdsfVwH-K3Td41_EKQkxutdPbbIwos,11908
|
|
7
|
-
athf/commands/env.py,sha256=Y1UZXn5sStpkRYMJ0ZMjr_ox3ve4ZuhqGGJPBo6Ytko,11828
|
|
8
|
-
athf/commands/hunt.py,sha256=2KORNWAqEvLY-Wc1q-a894g8kOpcqw_iJfnenKJeTDI,23019
|
|
9
|
-
athf/commands/init.py,sha256=L_29fvZF8SZ1BKh2D6NyDuacCC5JXOTezIxdBnnK88E,10941
|
|
10
|
-
athf/commands/investigate.py,sha256=WjwPtafs9bOSu09RC1QW4CVFYJjdn2C96wRa9M_o2PI,24650
|
|
11
|
-
athf/commands/similar.py,sha256=d8AArbknc08qlyGw8kTzF35q9Dk-qBXN4SMP5n0z4-I,11793
|
|
12
|
-
athf/core/__init__.py,sha256=yG7C8ljx3UW4QZoYvDjUxsWHlbS8M-GLGB7Je7rRfqo,31
|
|
13
|
-
athf/core/attack_matrix.py,sha256=Tp-519BLjjov8NAQ84iRvIv7STegLBtF09E5vf7jO9s,2958
|
|
14
|
-
athf/core/hunt_manager.py,sha256=5fxGXbtRGfUR8B0E2jb62peSQhwISmim71SZPRrJRr0,11361
|
|
15
|
-
athf/core/hunt_parser.py,sha256=FUj0yyBIcZnaS9aItMImeBDhegQwpkewIwUMNXW_ZWU,5122
|
|
16
|
-
athf/core/investigation_parser.py,sha256=tZnUqrFGLMUif9rayu7hgb6sKBWIvui46siUdDokAAA,6797
|
|
17
|
-
athf/core/template_engine.py,sha256=vNTVhlxIXZpxU7VmQyrqCSt6ORS0IVjAV54TOmUDMTE,5636
|
|
18
|
-
athf/utils/__init__.py,sha256=aEAPI1xnAsowOtc036cCb9ZOek5nrrfevu8PElhbNgk,30
|
|
19
|
-
agentic_threat_hunting_framework-0.2.0.dist-info/METADATA,sha256=4XD3KtzPLvRcA4a4lfjmRhLAuA5AAkQBF1IdXFM7ZvQ,15472
|
|
20
|
-
agentic_threat_hunting_framework-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
-
agentic_threat_hunting_framework-0.2.0.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
|
|
22
|
-
agentic_threat_hunting_framework-0.2.0.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
|
|
23
|
-
agentic_threat_hunting_framework-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|