agentic-threat-hunting-framework 0.3.1__tar.gz → 0.5.0__tar.gz
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.3.1/agentic_threat_hunting_framework.egg-info → agentic_threat_hunting_framework-0.5.0}/PKG-INFO +4 -1
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0/agentic_threat_hunting_framework.egg-info}/PKG-INFO +4 -1
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/agentic_threat_hunting_framework.egg-info/SOURCES.txt +10 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/agentic_threat_hunting_framework.egg-info/requires.txt +4 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/__version__.py +1 -1
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/cli.py +25 -10
- agentic_threat_hunting_framework-0.5.0/athf/commands/__init__.py +26 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/agent.py +43 -1
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/hunt.py +63 -12
- agentic_threat_hunting_framework-0.5.0/athf/commands/splunk.py +323 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/clickhouse_connection.py +396 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/metrics_tracker.py +518 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/query_executor.py +169 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/query_parser.py +203 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/query_suggester.py +235 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/query_validator.py +240 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/session_manager.py +764 -0
- agentic_threat_hunting_framework-0.5.0/athf/core/splunk_client.py +360 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/template_engine.py +7 -1
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/CHANGELOG.md +29 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/CLI_REFERENCE.md +518 -12
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/getting-started.md +47 -3
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/level4-agentic-workflows.md +9 -1
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/maturity-model.md +56 -14
- agentic_threat_hunting_framework-0.5.0/athf/plugin_system.py +48 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/pyproject.toml +19 -1
- agentic_threat_hunting_framework-0.3.1/athf/commands/__init__.py +0 -5
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/LICENSE +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/MANIFEST.in +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/README.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/USING_ATHF.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/agentic_threat_hunting_framework.egg-info/dependency_links.txt +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/agentic_threat_hunting_framework.egg-info/entry_points.txt +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/agentic_threat_hunting_framework.egg-info/top_level.txt +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/ATHF_level_3.png +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf-cli-workflow.gif +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf-level0.gif +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf-level1.gif +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf-level2.gif +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf-level3.gif +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf_fivelevels.png +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf_lock.png +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf_logo.png +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/assets/athf_manual_v_ai.png +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/__init__.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/agents/__init__.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/agents/base.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/agents/llm/__init__.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/agents/llm/hunt_researcher.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/agents/llm/hypothesis_generator.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/context.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/env.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/init.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/investigate.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/research.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/commands/similar.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/__init__.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/attack_matrix.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/hunt_manager.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/hunt_parser.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/investigation_parser.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/research_manager.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/core/web_search.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/__init__.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/INSTALL.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/README.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/environment.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/lock-pattern.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/docs/why-athf.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/hunts/FORMAT_GUIDELINES.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/hunts/H-0001.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/hunts/H-0002.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/hunts/H-0003.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/hunts/README.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/integrations/MCP_CATALOG.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/integrations/README.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/integrations/quickstart/splunk.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/knowledge/hunting-knowledge.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/prompts/README.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/prompts/ai-workflow.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/prompts/basic-prompts.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/data/templates/HUNT_LOCK.md +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/utils/__init__.py +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/setup.cfg +0 -0
- {agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-threat-hunting-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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>
|
|
@@ -33,6 +33,7 @@ Requires-Dist: click>=8.0.0
|
|
|
33
33
|
Requires-Dist: pyyaml>=6.0
|
|
34
34
|
Requires-Dist: rich>=10.0.0
|
|
35
35
|
Requires-Dist: jinja2>=3.0.0
|
|
36
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
36
37
|
Requires-Dist: importlib_resources>=5.0.0; python_version < "3.9"
|
|
37
38
|
Provides-Extra: dev
|
|
38
39
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
@@ -49,6 +50,8 @@ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
|
|
|
49
50
|
Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
|
|
50
51
|
Provides-Extra: similarity
|
|
51
52
|
Requires-Dist: scikit-learn>=1.0.0; extra == "similarity"
|
|
53
|
+
Provides-Extra: splunk
|
|
54
|
+
Requires-Dist: requests>=2.25.0; extra == "splunk"
|
|
52
55
|
Dynamic: license-file
|
|
53
56
|
|
|
54
57
|
# Agentic Threat Hunting Framework (ATHF)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-threat-hunting-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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>
|
|
@@ -33,6 +33,7 @@ Requires-Dist: click>=8.0.0
|
|
|
33
33
|
Requires-Dist: pyyaml>=6.0
|
|
34
34
|
Requires-Dist: rich>=10.0.0
|
|
35
35
|
Requires-Dist: jinja2>=3.0.0
|
|
36
|
+
Requires-Dist: python-dotenv>=0.19.0
|
|
36
37
|
Requires-Dist: importlib_resources>=5.0.0; python_version < "3.9"
|
|
37
38
|
Provides-Extra: dev
|
|
38
39
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
@@ -49,6 +50,8 @@ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
|
|
|
49
50
|
Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
|
|
50
51
|
Provides-Extra: similarity
|
|
51
52
|
Requires-Dist: scikit-learn>=1.0.0; extra == "similarity"
|
|
53
|
+
Provides-Extra: splunk
|
|
54
|
+
Requires-Dist: requests>=2.25.0; extra == "splunk"
|
|
52
55
|
Dynamic: license-file
|
|
53
56
|
|
|
54
57
|
# Agentic Threat Hunting Framework (ATHF)
|
|
@@ -23,6 +23,7 @@ assets/athf_manual_v_ai.png
|
|
|
23
23
|
athf/__init__.py
|
|
24
24
|
athf/__version__.py
|
|
25
25
|
athf/cli.py
|
|
26
|
+
athf/plugin_system.py
|
|
26
27
|
athf/agents/__init__.py
|
|
27
28
|
athf/agents/base.py
|
|
28
29
|
athf/agents/llm/__init__.py
|
|
@@ -37,12 +38,21 @@ athf/commands/init.py
|
|
|
37
38
|
athf/commands/investigate.py
|
|
38
39
|
athf/commands/research.py
|
|
39
40
|
athf/commands/similar.py
|
|
41
|
+
athf/commands/splunk.py
|
|
40
42
|
athf/core/__init__.py
|
|
41
43
|
athf/core/attack_matrix.py
|
|
44
|
+
athf/core/clickhouse_connection.py
|
|
42
45
|
athf/core/hunt_manager.py
|
|
43
46
|
athf/core/hunt_parser.py
|
|
44
47
|
athf/core/investigation_parser.py
|
|
48
|
+
athf/core/metrics_tracker.py
|
|
49
|
+
athf/core/query_executor.py
|
|
50
|
+
athf/core/query_parser.py
|
|
51
|
+
athf/core/query_suggester.py
|
|
52
|
+
athf/core/query_validator.py
|
|
45
53
|
athf/core/research_manager.py
|
|
54
|
+
athf/core/session_manager.py
|
|
55
|
+
athf/core/splunk_client.py
|
|
46
56
|
athf/core/template_engine.py
|
|
47
57
|
athf/core/web_search.py
|
|
48
58
|
athf/data/__init__.py
|
|
@@ -2,6 +2,7 @@ click>=8.0.0
|
|
|
2
2
|
pyyaml>=6.0
|
|
3
3
|
rich>=10.0.0
|
|
4
4
|
jinja2>=3.0.0
|
|
5
|
+
python-dotenv>=0.19.0
|
|
5
6
|
|
|
6
7
|
[:python_version < "3.9"]
|
|
7
8
|
importlib_resources>=5.0.0
|
|
@@ -23,3 +24,6 @@ mkdocs-material>=9.0.0
|
|
|
23
24
|
|
|
24
25
|
[similarity]
|
|
25
26
|
scikit-learn>=1.0.0
|
|
27
|
+
|
|
28
|
+
[splunk]
|
|
29
|
+
requests>=2.25.0
|
{agentic_threat_hunting_framework-0.3.1 → agentic_threat_hunting_framework-0.5.0}/athf/cli.py
RENAMED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
import random
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
|
+
from dotenv import load_dotenv
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# Load .env file from current directory (if it exists)
|
|
10
|
+
load_dotenv()
|
|
11
|
+
|
|
12
|
+
from athf.__version__ import __version__ # noqa: E402
|
|
13
|
+
from athf.commands import context, env, hunt, init, investigate, research, similar, splunk # noqa: E402
|
|
14
|
+
from athf.commands.agent import agent # noqa: E402
|
|
11
15
|
|
|
12
16
|
console = Console()
|
|
13
17
|
|
|
@@ -78,19 +82,30 @@ def cli() -> None:
|
|
|
78
82
|
|
|
79
83
|
|
|
80
84
|
# Register command groups
|
|
81
|
-
cli.add_command(init
|
|
82
|
-
cli.add_command(hunt
|
|
83
|
-
cli.add_command(investigate
|
|
84
|
-
cli.add_command(research
|
|
85
|
+
cli.add_command(init)
|
|
86
|
+
cli.add_command(hunt)
|
|
87
|
+
cli.add_command(investigate)
|
|
88
|
+
cli.add_command(research)
|
|
85
89
|
|
|
86
90
|
# Phase 1 commands (env, context, similar)
|
|
87
|
-
cli.add_command(env
|
|
88
|
-
cli.add_command(context
|
|
89
|
-
cli.add_command(similar
|
|
91
|
+
cli.add_command(env)
|
|
92
|
+
cli.add_command(context)
|
|
93
|
+
cli.add_command(similar)
|
|
90
94
|
|
|
91
95
|
# Agent commands
|
|
92
96
|
cli.add_command(agent)
|
|
93
97
|
|
|
98
|
+
# Integration commands (optional, requires additional dependencies)
|
|
99
|
+
if splunk is not None:
|
|
100
|
+
cli.add_command(splunk)
|
|
101
|
+
|
|
102
|
+
# Load and register plugins
|
|
103
|
+
from athf.plugin_system import PluginRegistry
|
|
104
|
+
|
|
105
|
+
PluginRegistry.load_plugins()
|
|
106
|
+
for name, cmd in PluginRegistry._commands.items():
|
|
107
|
+
cli.add_command(cmd, name=name)
|
|
108
|
+
|
|
94
109
|
|
|
95
110
|
@cli.command(hidden=True)
|
|
96
111
|
def wisdom() -> None:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""ATHF CLI commands (base commands only)."""
|
|
2
|
+
|
|
3
|
+
from athf.commands.context import context
|
|
4
|
+
from athf.commands.env import env
|
|
5
|
+
from athf.commands.hunt import hunt
|
|
6
|
+
from athf.commands.init import init
|
|
7
|
+
from athf.commands.investigate import investigate
|
|
8
|
+
from athf.commands.research import research
|
|
9
|
+
from athf.commands.similar import similar
|
|
10
|
+
|
|
11
|
+
# Optional: Splunk integration (requires requests package)
|
|
12
|
+
try:
|
|
13
|
+
from athf.commands.splunk import splunk
|
|
14
|
+
except ImportError:
|
|
15
|
+
splunk = None # type: ignore[assignment]
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"init",
|
|
19
|
+
"hunt",
|
|
20
|
+
"investigate",
|
|
21
|
+
"research",
|
|
22
|
+
"context",
|
|
23
|
+
"similar",
|
|
24
|
+
"env",
|
|
25
|
+
"splunk",
|
|
26
|
+
]
|
|
@@ -125,6 +125,7 @@ def info(agent_name: str) -> None:
|
|
|
125
125
|
|
|
126
126
|
console.print("\n[bold]Usage:[/bold]")
|
|
127
127
|
console.print(' athf agent run hypothesis-generator --threat-intel "APT29 targeting SaaS"')
|
|
128
|
+
console.print(' athf agent run hypothesis-generator --threat-intel "..." --research R-0001')
|
|
128
129
|
console.print()
|
|
129
130
|
|
|
130
131
|
elif agent_name == "hunt-researcher":
|
|
@@ -170,6 +171,7 @@ def info(agent_name: str) -> None:
|
|
|
170
171
|
@agent.command()
|
|
171
172
|
@click.argument("agent_name")
|
|
172
173
|
@click.option("--threat-intel", help="Threat intelligence context (for hypothesis-generator)")
|
|
174
|
+
@click.option("--research", help="Research document ID (e.g., R-0001) to load context from")
|
|
173
175
|
@click.option("--topic", help="Research topic (for hunt-researcher)")
|
|
174
176
|
@click.option("--technique", help="MITRE ATT&CK technique (for hunt-researcher)")
|
|
175
177
|
@click.option(
|
|
@@ -191,6 +193,7 @@ def info(agent_name: str) -> None:
|
|
|
191
193
|
def run( # noqa: C901
|
|
192
194
|
agent_name: str,
|
|
193
195
|
threat_intel: Optional[str],
|
|
196
|
+
research: Optional[str],
|
|
194
197
|
topic: Optional[str],
|
|
195
198
|
technique: Optional[str],
|
|
196
199
|
depth: str,
|
|
@@ -208,6 +211,7 @@ def run( # noqa: C901
|
|
|
208
211
|
# Hypothesis Generator
|
|
209
212
|
athf agent run hypothesis-generator --threat-intel "APT29 targeting SaaS applications"
|
|
210
213
|
athf agent run hypothesis-generator --threat-intel "Insider threat data exfiltration" --tactic collection
|
|
214
|
+
athf agent run hypothesis-generator --threat-intel "Credential dumping" --research R-0001
|
|
211
215
|
|
|
212
216
|
# Hunt Researcher
|
|
213
217
|
athf agent run hunt-researcher --topic "LSASS dumping"
|
|
@@ -232,6 +236,32 @@ def run( # noqa: C901
|
|
|
232
236
|
# Try to load past hunts and environment data if available
|
|
233
237
|
past_hunts: List[dict[str, Any]] = []
|
|
234
238
|
environment = {}
|
|
239
|
+
research_context = None
|
|
240
|
+
|
|
241
|
+
# Load research document if provided
|
|
242
|
+
if research:
|
|
243
|
+
try:
|
|
244
|
+
from pathlib import Path
|
|
245
|
+
|
|
246
|
+
from athf.core.research_manager import ResearchManager
|
|
247
|
+
|
|
248
|
+
research_mgr = ResearchManager(Path.cwd())
|
|
249
|
+
research_doc = research_mgr.get_research(research)
|
|
250
|
+
|
|
251
|
+
if research_doc:
|
|
252
|
+
# Extract relevant research context
|
|
253
|
+
research_context = {
|
|
254
|
+
"research_id": research_doc.get("metadata", {}).get("research_id"),
|
|
255
|
+
"topic": research_doc.get("metadata", {}).get("topic"),
|
|
256
|
+
"recommended_hypothesis": research_doc.get("synthesis", {}).get("recommended_hypothesis"),
|
|
257
|
+
"gaps": research_doc.get("synthesis", {}).get("gaps_identified", []),
|
|
258
|
+
"key_findings": research_doc.get("synthesis", {}).get("key_findings", []),
|
|
259
|
+
}
|
|
260
|
+
console.print(f"[green]✓ Loaded research context from {research}[/green]\n")
|
|
261
|
+
else:
|
|
262
|
+
console.print(f"[yellow]⚠ Research document {research} not found[/yellow]\n")
|
|
263
|
+
except Exception as e:
|
|
264
|
+
console.print(f"[yellow]⚠ Could not load research document: {e}[/yellow]\n")
|
|
235
265
|
|
|
236
266
|
# Try to load environment.md if it exists
|
|
237
267
|
try:
|
|
@@ -252,10 +282,22 @@ def run( # noqa: C901
|
|
|
252
282
|
"platforms": ["Windows", "macOS", "Linux"],
|
|
253
283
|
}
|
|
254
284
|
|
|
285
|
+
# If research context is provided, append it to threat intel
|
|
286
|
+
threat_intel_with_research = threat_intel
|
|
287
|
+
if research_context:
|
|
288
|
+
threat_intel_with_research = (
|
|
289
|
+
f"{threat_intel}\n\n"
|
|
290
|
+
f"Research Context from {research_context['research_id']}:\n"
|
|
291
|
+
f"- Topic: {research_context['topic']}\n"
|
|
292
|
+
f"- Recommended Hypothesis: {research_context.get('recommended_hypothesis', 'N/A')}\n"
|
|
293
|
+
)
|
|
294
|
+
if research_context.get("gaps"):
|
|
295
|
+
threat_intel_with_research += f"- Gaps: {', '.join(research_context['gaps'][:3])}\n"
|
|
296
|
+
|
|
255
297
|
# Execute agent
|
|
256
298
|
hypothesis_result = hypothesis_agent.execute(
|
|
257
299
|
HypothesisGenerationInput(
|
|
258
|
-
threat_intel=
|
|
300
|
+
threat_intel=threat_intel_with_research,
|
|
259
301
|
past_hunts=past_hunts,
|
|
260
302
|
environment=environment,
|
|
261
303
|
)
|
|
@@ -40,6 +40,9 @@ Examples:
|
|
|
40
40
|
# Non-interactive with all options
|
|
41
41
|
athf hunt new --technique T1003.001 --title "LSASS Dumping" --non-interactive
|
|
42
42
|
|
|
43
|
+
# Link research document to hunt
|
|
44
|
+
athf hunt new --research R-0001 --title "Hunt Title" --non-interactive
|
|
45
|
+
|
|
43
46
|
# List hunts with filters
|
|
44
47
|
athf hunt list --status completed --tactic credential-access
|
|
45
48
|
|
|
@@ -52,6 +55,9 @@ Examples:
|
|
|
52
55
|
# Show coverage gaps
|
|
53
56
|
athf hunt coverage
|
|
54
57
|
|
|
58
|
+
# Filter coverage by tactic
|
|
59
|
+
athf hunt coverage --tactic credential-access
|
|
60
|
+
|
|
55
61
|
# Validate hunt structure
|
|
56
62
|
athf hunt validate H-0042
|
|
57
63
|
|
|
@@ -96,6 +102,7 @@ def hunt() -> None:
|
|
|
96
102
|
@click.option("--location", help="Location/scope (for ABLE framework)")
|
|
97
103
|
@click.option("--evidence", help="Evidence description (for ABLE framework)")
|
|
98
104
|
@click.option("--hunter", help="Hunter name", default="AI Assistant")
|
|
105
|
+
@click.option("--research", help="Research document ID (e.g., R-0001) this hunt is based on")
|
|
99
106
|
def new(
|
|
100
107
|
technique: Optional[str],
|
|
101
108
|
title: Optional[str],
|
|
@@ -110,6 +117,7 @@ def new(
|
|
|
110
117
|
location: Optional[str],
|
|
111
118
|
evidence: Optional[str],
|
|
112
119
|
hunter: Optional[str],
|
|
120
|
+
research: Optional[str],
|
|
113
121
|
) -> None:
|
|
114
122
|
"""Create a new hunt hypothesis with LOCK structure.
|
|
115
123
|
|
|
@@ -119,6 +127,7 @@ def new(
|
|
|
119
127
|
• YAML frontmatter with metadata
|
|
120
128
|
• LOCK pattern sections (Learn, Observe, Check, Keep)
|
|
121
129
|
• MITRE ATT&CK mapping
|
|
130
|
+
• Optional link to research document
|
|
122
131
|
|
|
123
132
|
\b
|
|
124
133
|
Interactive mode (default):
|
|
@@ -131,6 +140,11 @@ def new(
|
|
|
131
140
|
Example: athf hunt new --technique T1003.001 --title "LSASS Dumping" \\
|
|
132
141
|
--tactic credential-access --platform Windows --non-interactive
|
|
133
142
|
|
|
143
|
+
\b
|
|
144
|
+
With research document:
|
|
145
|
+
Link a pre-hunt research document to the hunt.
|
|
146
|
+
Example: athf hunt new --research R-0001 --title "Hunt Title" --non-interactive
|
|
147
|
+
|
|
134
148
|
\b
|
|
135
149
|
After creation:
|
|
136
150
|
1. Edit hunts/H-XXXX.md to flesh out your hypothesis
|
|
@@ -155,6 +169,13 @@ def new(
|
|
|
155
169
|
|
|
156
170
|
console.print(f"[bold]Hunt ID:[/bold] {hunt_id}")
|
|
157
171
|
|
|
172
|
+
# Validate research document if provided
|
|
173
|
+
if research:
|
|
174
|
+
research_file = Path("research") / f"{research}.md"
|
|
175
|
+
if not research_file.exists():
|
|
176
|
+
console.print(f"[yellow]Warning: Research document {research} not found at {research_file}[/yellow]")
|
|
177
|
+
console.print("[yellow]Hunt will still be created, but research link may be broken.[/yellow]\n")
|
|
178
|
+
|
|
158
179
|
# Gather hunt details
|
|
159
180
|
if non_interactive:
|
|
160
181
|
if not title:
|
|
@@ -209,6 +230,7 @@ def new(
|
|
|
209
230
|
behavior=behavior,
|
|
210
231
|
location=location,
|
|
211
232
|
evidence=evidence,
|
|
233
|
+
spawned_from=research,
|
|
212
234
|
)
|
|
213
235
|
|
|
214
236
|
# Write hunt file
|
|
@@ -529,8 +551,9 @@ def _render_progress_bar(covered: int, total: int, width: int = 20) -> str:
|
|
|
529
551
|
|
|
530
552
|
|
|
531
553
|
@hunt.command()
|
|
554
|
+
@click.option("--tactic", help="Filter by specific tactic (or 'all' for all tactics)")
|
|
532
555
|
@click.option("--detailed", is_flag=True, help="Show detailed technique coverage with hunt references")
|
|
533
|
-
def coverage(detailed: bool) -> None:
|
|
556
|
+
def coverage(tactic: Optional[str], detailed: bool) -> None:
|
|
534
557
|
"""Show MITRE ATT&CK technique coverage across hunts.
|
|
535
558
|
|
|
536
559
|
\b
|
|
@@ -542,11 +565,17 @@ def coverage(detailed: bool) -> None:
|
|
|
542
565
|
|
|
543
566
|
\b
|
|
544
567
|
Examples:
|
|
545
|
-
# Show coverage overview
|
|
568
|
+
# Show coverage overview for all tactics
|
|
546
569
|
athf hunt coverage
|
|
547
570
|
|
|
548
|
-
# Show
|
|
549
|
-
athf hunt coverage --
|
|
571
|
+
# Show all tactics explicitly
|
|
572
|
+
athf hunt coverage --tactic all
|
|
573
|
+
|
|
574
|
+
# Show coverage for a specific tactic
|
|
575
|
+
athf hunt coverage --tactic credential-access
|
|
576
|
+
|
|
577
|
+
# Show detailed technique mapping for execution tactic
|
|
578
|
+
athf hunt coverage --tactic execution --detailed
|
|
550
579
|
|
|
551
580
|
\b
|
|
552
581
|
Note on technique counts:
|
|
@@ -578,12 +607,31 @@ def coverage(detailed: bool) -> None:
|
|
|
578
607
|
summary = coverage["summary"]
|
|
579
608
|
by_tactic = coverage["by_tactic"]
|
|
580
609
|
|
|
610
|
+
# Determine which tactics to display
|
|
611
|
+
tactics_to_display = []
|
|
612
|
+
if tactic and tactic.lower() != "all":
|
|
613
|
+
# Validate tactic exists
|
|
614
|
+
if tactic not in ATTACK_TACTICS:
|
|
615
|
+
console.print(f"[red]Error: Unknown tactic '{tactic}'[/red]")
|
|
616
|
+
console.print("\n[bold]Valid tactics:[/bold]")
|
|
617
|
+
for tactic_key in get_sorted_tactics():
|
|
618
|
+
console.print(f" • {tactic_key}")
|
|
619
|
+
return
|
|
620
|
+
tactics_to_display = [tactic]
|
|
621
|
+
else:
|
|
622
|
+
# Show all tactics
|
|
623
|
+
tactics_to_display = get_sorted_tactics()
|
|
624
|
+
|
|
581
625
|
# Display title
|
|
582
|
-
|
|
626
|
+
if tactic and tactic.lower() != "all":
|
|
627
|
+
tactic_display_name = ATTACK_TACTICS[tactic]["name"]
|
|
628
|
+
console.print(f"\n[bold]MITRE ATT&CK Coverage - {tactic_display_name}[/bold]")
|
|
629
|
+
else:
|
|
630
|
+
console.print("\n[bold]MITRE ATT&CK Coverage[/bold]")
|
|
583
631
|
console.print("─" * 60 + "\n")
|
|
584
632
|
|
|
585
|
-
# Display
|
|
586
|
-
for tactic_key in
|
|
633
|
+
# Display selected tactics in ATT&CK order with hunt counts
|
|
634
|
+
for tactic_key in tactics_to_display:
|
|
587
635
|
data = by_tactic.get(tactic_key, {})
|
|
588
636
|
tactic_name = ATTACK_TACTICS[tactic_key]["name"]
|
|
589
637
|
|
|
@@ -596,16 +644,19 @@ def coverage(detailed: bool) -> None:
|
|
|
596
644
|
else:
|
|
597
645
|
console.print(f"{tactic_name:<24} [dim]no coverage[/dim]")
|
|
598
646
|
|
|
599
|
-
# Display overall coverage
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
647
|
+
# Display overall coverage only if showing all tactics
|
|
648
|
+
if not tactic or tactic.lower() == "all":
|
|
649
|
+
console.print(
|
|
650
|
+
f"\n[bold]Overall: {summary['unique_techniques']}/{summary['total_techniques']} techniques ({summary['overall_coverage_pct']:.0f}%)[/bold]\n"
|
|
651
|
+
)
|
|
652
|
+
else:
|
|
653
|
+
console.print()
|
|
603
654
|
|
|
604
655
|
# Display detailed technique coverage if requested
|
|
605
656
|
if detailed:
|
|
606
657
|
console.print("\n[bold cyan]🔍 Detailed Technique Coverage[/bold cyan]\n")
|
|
607
658
|
|
|
608
|
-
for tactic_key in
|
|
659
|
+
for tactic_key in tactics_to_display:
|
|
609
660
|
data = by_tactic.get(tactic_key, {})
|
|
610
661
|
if data.get("hunt_count", 0) == 0:
|
|
611
662
|
continue # Skip tactics with no hunts in detailed view
|