agentic-threat-hunting-framework 0.2.1__py3-none-any.whl → 0.2.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-threat-hunting-framework
3
- Version: 0.2.1
3
+ Version: 0.2.3
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.3.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=WvOf0OuttAsEk_h4QDtdfqYI4CulDg2UCtq_5r5iJAA,12686
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=PFsg8Ecg94NCpuFZpApo82lyORkgK5IfOIih-7-XsmM,11580
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.3.dist-info/METADATA,sha256=I3x8s2Rff1A7BjYz-lfy_M6I_qw0-nDBC2Ypc0DcxTA,15472
20
+ agentic_threat_hunting_framework-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ agentic_threat_hunting_framework-0.2.3.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
22
+ agentic_threat_hunting_framework-0.2.3.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
23
+ agentic_threat_hunting_framework-0.2.3.dist-info/RECORD,,
athf/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for ATHF."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
athf/commands/context.py CHANGED
@@ -22,6 +22,9 @@ Examples:
22
22
  # Export context for macOS platform hunts
23
23
  athf context --platform macos
24
24
 
25
+ # Combine filters: persistence hunts on Linux
26
+ athf context --tactic persistence --platform linux
27
+
25
28
  # Export full repository context (large output)
26
29
  athf context --full
27
30
 
@@ -86,18 +89,20 @@ def context(
86
89
  • With context: 1 command, ~1,000 tokens
87
90
  • Savings: ~2,000 tokens per hunt (~$0.03 per hunt)
88
91
  """
89
- # Validate mutually exclusive options
90
- exclusive_options = sum([bool(hunt), bool(tactic), bool(platform), full])
91
- if exclusive_options == 0:
92
- console.print("[red]Error: Must specify one of: --hunt, --tactic, --platform, or --full[/red]")
92
+ # Validate that at least one filter is provided
93
+ has_filter = any([hunt, tactic, platform, full])
94
+ if not has_filter:
95
+ console.print("[red]Error: Must specify at least one of: --hunt, --tactic, --platform, or --full[/red]")
93
96
  console.print("\n[dim]Examples:[/dim]")
94
97
  console.print(" athf context --hunt H-0013")
95
98
  console.print(" athf context --tactic credential-access")
96
99
  console.print(" athf context --platform macos")
100
+ console.print(" athf context --tactic persistence --platform linux")
97
101
  raise click.Abort()
98
102
 
99
- if exclusive_options > 1:
100
- console.print("[red]Error: Only one filter option allowed at a time[/red]")
103
+ # --full flag is mutually exclusive with other filters
104
+ if full and (hunt or tactic or platform):
105
+ console.print("[red]Error: --full cannot be combined with other filters[/red]")
101
106
  raise click.Abort()
102
107
 
103
108
  # Build context bundle
@@ -158,17 +163,26 @@ def _build_context(
158
163
  if index_path.exists():
159
164
  context["hunt_index"] = _read_and_optimize(index_path)
160
165
 
161
- # Load hunts based on filter
162
- if hunt:
163
- hunt_files = [Path(f"hunts/{hunt}.md")]
164
- elif tactic:
165
- hunt_files = _find_hunts_by_tactic(tactic)
166
- elif platform:
167
- hunt_files = _find_hunts_by_platform(platform)
168
- elif full:
166
+ # Load hunts based on filters (can be combined)
167
+ if full:
168
+ # Full export: include all hunts
169
169
  hunt_files = list(Path("hunts").glob("H-*.md"))
170
+ elif hunt:
171
+ # Specific hunt: only load that one
172
+ hunt_files = [Path(f"hunts/{hunt}.md")]
170
173
  else:
171
- hunt_files = []
174
+ # Combine tactic and platform filters
175
+ if tactic and platform:
176
+ # Both filters: find hunts matching both criteria
177
+ tactic_hunts = set(_find_hunts_by_tactic(tactic))
178
+ platform_hunts = set(_find_hunts_by_platform(platform))
179
+ hunt_files = list(tactic_hunts & platform_hunts) # Intersection
180
+ elif tactic:
181
+ hunt_files = _find_hunts_by_tactic(tactic)
182
+ elif platform:
183
+ hunt_files = _find_hunts_by_platform(platform)
184
+ else:
185
+ hunt_files = []
172
186
 
173
187
  # Load hunt content
174
188
  for hunt_file in hunt_files:
@@ -197,7 +211,7 @@ def _build_context(
197
211
 
198
212
  def _read_and_optimize(file_path: Path) -> str:
199
213
  """Read file and optimize for token efficiency."""
200
- content = file_path.read_text()
214
+ content = file_path.read_text(encoding='utf-8')
201
215
 
202
216
  # First pass: Remove all control characters except tabs and newlines
203
217
  # Control characters are U+0000 through U+001F (0-31), except tab (9), LF (10), CR (13)
@@ -234,7 +248,7 @@ def _find_hunts_by_tactic(tactic: str) -> List[Path]:
234
248
  normalized_tactic = tactic.replace("-", " ").lower()
235
249
 
236
250
  for hunt_file in hunts_dir.glob("H-*.md"):
237
- content = hunt_file.read_text()
251
+ content = hunt_file.read_text(encoding='utf-8')
238
252
 
239
253
  # Check YAML frontmatter for tactics field
240
254
  if content.startswith("---"):
@@ -263,7 +277,7 @@ def _find_hunts_by_platform(platform: str) -> List[Path]:
263
277
  normalized_platform = platform.lower()
264
278
 
265
279
  for hunt_file in hunts_dir.glob("H-*.md"):
266
- content = hunt_file.read_text()
280
+ content = hunt_file.read_text(encoding='utf-8')
267
281
 
268
282
  # Check YAML frontmatter for platform field
269
283
  if content.startswith("---"):
@@ -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: tuple[str, ...],
86
- related_hunt: tuple[str, ...],
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: list[str],
204
- data_sources: list[str],
205
- related_hunts: list[str],
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: tuple[str, ...],
573
- platform: tuple[str, ...],
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(
@@ -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)
@@ -4,7 +4,7 @@ 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, TypedDict
7
+ from typing import Dict, List, TypedDict
8
8
 
9
9
 
10
10
  class TacticInfo(TypedDict):
@@ -122,7 +122,7 @@ def get_tactic_technique_count(tactic_key: str) -> int:
122
122
  return 0
123
123
 
124
124
 
125
- def get_sorted_tactics() -> list[str]:
125
+ def get_sorted_tactics() -> List[str]:
126
126
  """Get all tactic keys sorted by ATT&CK matrix order.
127
127
 
128
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
@@ -154,7 +154,14 @@ class HuntManager:
154
154
  results = []
155
155
  query_lower = query.lower()
156
156
 
157
+ # Exclude documentation files
158
+ exclude_files = {"README.md", "FORMAT_GUIDELINES.md"}
159
+
157
160
  for hunt_file in self.hunts_dir.glob("*.md"):
161
+ # Skip documentation files
162
+ if hunt_file.name in exclude_files:
163
+ continue
164
+
158
165
  try:
159
166
  with open(hunt_file, "r", encoding="utf-8") as f:
160
167
  content = f.read()
@@ -261,7 +268,7 @@ class HuntManager:
261
268
  "total_techniques": ATTACK_TACTICS[tactic_key]["technique_count"],
262
269
  }
263
270
 
264
- all_unique_techniques: set[str] = set()
271
+ all_unique_techniques: Set[str] = set()
265
272
 
266
273
  for hunt in hunts:
267
274
  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) -> tuple[bool, List[str]]:
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) -> tuple[bool, List[str]]:
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.1.dist-info/licenses/LICENSE,sha256=_KObErRfiKoolznt-DF0nJnr3U9Rdh7Z4Ba7G5qqckk,1071
2
- athf/__init__.py,sha256=OrjZe8P97_BTEkscapnwSsqKSjwXNP9d8-HtGr19Ni0,241
3
- athf/__version__.py,sha256=JMSRfysx6Kj8rOPSF3-d9x_3-QAfDP47yKOeqdgkH1M,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=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=WjwPtafs9bOSu09RC1QW4CVFYJjdn2C96wRa9M_o2PI,24650
11
- athf/commands/similar.py,sha256=sgCNwI1Ru0lbavNm_3fjOdcJjeRPVD827vGdMF2LMBM,11761
12
- athf/core/__init__.py,sha256=yG7C8ljx3UW4QZoYvDjUxsWHlbS8M-GLGB7Je7rRfqo,31
13
- athf/core/attack_matrix.py,sha256=ayr9StkOskgFTsNum8MwTuCd4Z7Rhy5NNCNx1ncLYKs,3232
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.1.dist-info/METADATA,sha256=WMcuK9M13SlUPyRtud9frS8t4_7lFeRXiwevmicJMgk,15472
20
- agentic_threat_hunting_framework-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- agentic_threat_hunting_framework-0.2.1.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
22
- agentic_threat_hunting_framework-0.2.1.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
23
- agentic_threat_hunting_framework-0.2.1.dist-info/RECORD,,