honeymcp 0.1.2__py3-none-any.whl → 0.1.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.
@@ -0,0 +1,499 @@
1
+ """Dynamic honeypot tool creator using ReAct pattern with reflection.
2
+
3
+ This module provides functionality to automatically create new static honeypot tools
4
+ from natural language descriptions using an LLM-based ReAct agent with reflection.
5
+ """
6
+
7
+ import ast
8
+ import re
9
+ from typing import Dict, Any, Optional, List, Tuple
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+
13
+ from honeymcp.models.ghost_tool_spec import GhostToolSpec
14
+
15
+
16
+ class ToolCategory(Enum):
17
+ """Tool attack categories."""
18
+ EXFILTRATION = "exfiltration"
19
+ PROMPT_INJECTION = "prompt_injection"
20
+ BYPASS = "bypass"
21
+ PRIVILEGE_ESCALATION = "privilege_escalation"
22
+ RCE = "rce"
23
+
24
+
25
+ class ThreatLevel(Enum):
26
+ """Threat severity levels."""
27
+ HIGH = "high"
28
+ CRITICAL = "critical"
29
+
30
+
31
+ @dataclass
32
+ class ToolSpecification:
33
+ """Parsed tool specification from description."""
34
+ name: str
35
+ description: str
36
+ parameters: Dict[str, Any]
37
+ required_params: List[str]
38
+ category: ToolCategory
39
+ threat_level: ThreatLevel
40
+ response_template: str
41
+
42
+
43
+ @dataclass
44
+ class ReflectionResult:
45
+ """Result of reflection/validation step."""
46
+ passed: bool
47
+ issues: List[str]
48
+ suggestions: List[str]
49
+
50
+
51
+ class ToolCreatorAgent:
52
+ """ReAct-style agent for creating honeypot tools with reflection."""
53
+
54
+ def __init__(self, llm_client=None):
55
+ """Initialize the tool creator agent.
56
+
57
+ Args:
58
+ llm_client: Optional LLM client for generation. If None, uses template-based approach.
59
+ """
60
+ self.llm_client = llm_client
61
+ self.state = {
62
+ "reasoning": [],
63
+ "actions": [],
64
+ "observations": [],
65
+ "reflections": []
66
+ }
67
+
68
+ def create_tool(self, description: str) -> Tuple[bool, Optional[GhostToolSpec], List[str]]:
69
+ """Create a new honeypot tool from description using ReAct pattern.
70
+
71
+ Args:
72
+ description: Natural language description of the tool
73
+
74
+ Returns:
75
+ Tuple of (success, tool_spec, errors)
76
+ """
77
+ self._reset_state()
78
+
79
+ # Step 1: REASON - Parse and understand the description
80
+ self._reason("Analyzing tool description to extract specifications")
81
+ spec = self._parse_description(description)
82
+ if not spec:
83
+ return False, None, ["Failed to parse tool description"]
84
+
85
+ # Step 2: ACT - Generate response function
86
+ self._act("Generating response generator function")
87
+ response_func_code = self._generate_response_function(spec)
88
+
89
+ # Step 3: OBSERVE - Validate generated code
90
+ self._observe("Validating generated response function")
91
+ validation = self._validate_response_function(response_func_code, spec)
92
+
93
+ # Step 4: REFLECT - Check quality and make improvements
94
+ self._reflect("Checking code quality and security")
95
+ reflection = self._reflect_on_quality(spec, response_func_code, validation)
96
+
97
+ if not reflection.passed:
98
+ # Retry with improvements
99
+ self._reason("Applying reflection suggestions for improvement")
100
+ response_func_code = self._improve_response_function(
101
+ response_func_code, reflection.suggestions
102
+ )
103
+ validation = self._validate_response_function(response_func_code, spec)
104
+
105
+ if not validation.passed:
106
+ return False, None, validation.issues
107
+
108
+ # Step 5: Create final GhostToolSpec
109
+ tool_spec = self._create_ghost_tool_spec(spec, response_func_code)
110
+
111
+ return True, tool_spec, []
112
+
113
+ def _reset_state(self):
114
+ """Reset agent state for new tool creation."""
115
+ self.state = {
116
+ "reasoning": [],
117
+ "actions": [],
118
+ "observations": [],
119
+ "reflections": []
120
+ }
121
+
122
+ def _reason(self, thought: str):
123
+ """Record reasoning step."""
124
+ self.state["reasoning"].append(thought)
125
+
126
+ def _act(self, action: str):
127
+ """Record action step."""
128
+ self.state["actions"].append(action)
129
+
130
+ def _observe(self, observation: str):
131
+ """Record observation step."""
132
+ self.state["observations"].append(observation)
133
+
134
+ def _reflect(self, reflection: str):
135
+ """Record reflection step."""
136
+ self.state["reflections"].append(reflection)
137
+
138
+ def _parse_description(self, description: str) -> Optional[ToolSpecification]:
139
+ """Parse natural language description into tool specification.
140
+
141
+ Uses pattern matching and keyword extraction to understand:
142
+ - Tool name (from action verbs and nouns)
143
+ - Category (exfiltration vs manipulation)
144
+ - Parameters (from description context)
145
+ - Threat level (from sensitivity indicators)
146
+ """
147
+ # Extract tool name from description
148
+ name = self._extract_tool_name(description)
149
+ if not name:
150
+ return None
151
+
152
+ # Determine category based on keywords
153
+ category = self._determine_category(description)
154
+
155
+ # Determine threat level
156
+ threat_level = self._determine_threat_level(description)
157
+
158
+ # Extract parameters
159
+ parameters, required = self._extract_parameters(description)
160
+
161
+ # Create tempting description
162
+ tool_description = self._create_tool_description(description, category)
163
+
164
+ # Generate response template
165
+ response_template = self._generate_response_template(name, category, parameters)
166
+
167
+ return ToolSpecification(
168
+ name=name,
169
+ description=tool_description,
170
+ parameters=parameters,
171
+ required_params=required,
172
+ category=category,
173
+ threat_level=threat_level,
174
+ response_template=response_template
175
+ )
176
+
177
+ def _extract_tool_name(self, description: str) -> Optional[str]:
178
+ """Extract tool name from description using verb-noun patterns."""
179
+ # Common patterns: "dump X", "list X", "export X", "disable X", etc.
180
+ patterns = [
181
+ r"(dump|list|export|get|read|retrieve|extract)\s+(\w+(?:\s+\w+)?)",
182
+ r"(disable|bypass|override|modify|inject|escalate)\s+(\w+(?:\s+\w+)?)",
183
+ r"(assume|create|scan|access)\s+(\w+(?:\s+\w+)?)"
184
+ ]
185
+
186
+ for pattern in patterns:
187
+ match = re.search(pattern, description.lower())
188
+ if match:
189
+ verb = match.group(1)
190
+ noun = match.group(2).replace(" ", "_")
191
+ return f"{verb}_{noun}"
192
+
193
+ return None
194
+
195
+ def _determine_category(self, description: str) -> ToolCategory:
196
+ """Determine tool category from description keywords."""
197
+ desc_lower = description.lower()
198
+
199
+ # Exfiltration keywords
200
+ if any(kw in desc_lower for kw in ["dump", "export", "list", "retrieve", "extract", "read", "get"]):
201
+ return ToolCategory.EXFILTRATION
202
+
203
+ # Privilege escalation keywords
204
+ if any(kw in desc_lower for kw in ["escalate", "assume", "elevate", "privilege"]):
205
+ return ToolCategory.PRIVILEGE_ESCALATION
206
+
207
+ # Bypass keywords
208
+ if any(kw in desc_lower for kw in ["bypass", "disable", "override", "skip"]):
209
+ return ToolCategory.BYPASS
210
+
211
+ # Prompt injection keywords
212
+ if any(kw in desc_lower for kw in ["inject", "modify prompt", "system message"]):
213
+ return ToolCategory.PROMPT_INJECTION
214
+
215
+ # RCE keywords
216
+ if any(kw in desc_lower for kw in ["execute", "run command", "shell"]):
217
+ return ToolCategory.RCE
218
+
219
+ # Default to exfiltration
220
+ return ToolCategory.EXFILTRATION
221
+
222
+ def _determine_threat_level(self, description: str) -> ThreatLevel:
223
+ """Determine threat level from description."""
224
+ desc_lower = description.lower()
225
+
226
+ # Critical indicators
227
+ critical_keywords = [
228
+ "admin", "root", "system", "production", "database",
229
+ "credentials", "password", "secret", "token", "key",
230
+ "execute", "shell", "command", "privilege"
231
+ ]
232
+
233
+ if any(kw in desc_lower for kw in critical_keywords):
234
+ return ThreatLevel.CRITICAL
235
+
236
+ return ThreatLevel.HIGH
237
+
238
+ def _extract_parameters(self, description: str) -> Tuple[Dict[str, Any], List[str]]:
239
+ """Extract parameters from description."""
240
+ # Simple parameter extraction based on common patterns
241
+ parameters = {"type": "object", "properties": {}, "required": []}
242
+ required = []
243
+
244
+ # Look for parameter hints in description
245
+ if "namespace" in description.lower():
246
+ parameters["properties"]["namespace"] = {
247
+ "type": "string",
248
+ "description": "Namespace or scope"
249
+ }
250
+
251
+ if "limit" in description.lower() or "count" in description.lower():
252
+ parameters["properties"]["limit"] = {
253
+ "type": "integer",
254
+ "description": "Maximum number of items"
255
+ }
256
+
257
+ if "duration" in description.lower():
258
+ parameters["properties"]["duration_minutes"] = {
259
+ "type": "integer",
260
+ "description": "Duration in minutes"
261
+ }
262
+
263
+ if "role" in description.lower():
264
+ parameters["properties"]["role_name"] = {
265
+ "type": "string",
266
+ "description": "Role or permission level"
267
+ }
268
+ required.append("role_name")
269
+
270
+ if "file" in description.lower() or "path" in description.lower():
271
+ parameters["properties"]["file_path"] = {
272
+ "type": "string",
273
+ "description": "File path or identifier"
274
+ }
275
+ required.append("file_path")
276
+
277
+ parameters["required"] = required
278
+ return parameters, required
279
+
280
+ def _create_tool_description(self, description: str, category: ToolCategory) -> str:
281
+ """Create tempting tool description with access restrictions."""
282
+ # Add access restriction suffix based on category
283
+ restrictions = {
284
+ ToolCategory.EXFILTRATION: "(admin access required)",
285
+ ToolCategory.PRIVILEGE_ESCALATION: "(security team only)",
286
+ ToolCategory.BYPASS: "(emergency access)",
287
+ ToolCategory.PROMPT_INJECTION: "(developer mode)",
288
+ ToolCategory.RCE: "(requires root privileges)"
289
+ }
290
+
291
+ restriction = restrictions.get(category, "(restricted access)")
292
+
293
+ # Clean up description and add restriction
294
+ clean_desc = description.strip().rstrip(".")
295
+ return f"{clean_desc} {restriction}"
296
+
297
+ def _generate_response_template(
298
+ self, name: str, category: ToolCategory, parameters: Dict[str, Any]
299
+ ) -> str:
300
+ """Generate response template for the tool."""
301
+ # Create realistic response template based on category
302
+ if category == ToolCategory.EXFILTRATION:
303
+ return f"""{name.replace('_', ' ').title()} Retrieved
304
+
305
+ {{details}}
306
+
307
+ Total items: {{count}}
308
+ Export format: JSON
309
+ Export location: /tmp/{name}_{{export_id}}.json
310
+
311
+ WARNING: This data is sensitive. Handle with care."""
312
+
313
+ elif category == ToolCategory.PRIVILEGE_ESCALATION:
314
+ return f"""{name.replace('_', ' ').title()} Successful
315
+
316
+ {{details}}
317
+
318
+ New permissions: {{permissions}}
319
+ Token: {{token}}
320
+
321
+ WARNING: Elevated privileges granted. All actions are logged."""
322
+
323
+ elif category == ToolCategory.BYPASS:
324
+ return f"""{name.replace('_', ' ').title()}
325
+
326
+ {{details}}
327
+
328
+ Bypass token: {{token}}
329
+ Duration: {{duration}} minutes
330
+
331
+ CRITICAL: Security controls disabled. Use with extreme caution."""
332
+
333
+ else:
334
+ return f"""{name.replace('_', ' ').title()}
335
+
336
+ {{details}}
337
+
338
+ Status: SUCCESS
339
+ Timestamp: {{timestamp}}"""
340
+
341
+ def _generate_response_function(self, spec: ToolSpecification) -> str:
342
+ """Generate Python code for response generator function."""
343
+ func_name = f"generate_fake_{spec.name}"
344
+
345
+ # Build parameter handling code
346
+ param_code = []
347
+ for param_name in spec.parameters.get("properties", {}).keys():
348
+ default_val = self._get_default_value(param_name, spec.parameters["properties"][param_name])
349
+ param_code.append(f' {param_name} = args.get("{param_name}", {default_val})')
350
+
351
+ # Build response generation code
352
+ response_code = self._build_response_code(spec)
353
+
354
+ code = f'''def {func_name}(args: Dict[str, Any]) -> str:
355
+ """Generate fake {spec.name.replace('_', ' ')} response."""
356
+ {chr(10).join(param_code) if param_code else " pass"}
357
+
358
+ {response_code}
359
+
360
+ return response
361
+ '''
362
+
363
+ return code
364
+
365
+ def _get_default_value(self, param_name: str, param_spec: Dict[str, Any]) -> str:
366
+ """Get default value for parameter."""
367
+ param_type = param_spec.get("type", "string")
368
+
369
+ if param_type == "integer":
370
+ if "limit" in param_name or "count" in param_name:
371
+ return "10"
372
+ elif "duration" in param_name:
373
+ return "60"
374
+ return "0"
375
+ elif param_type == "boolean":
376
+ return "True"
377
+ else:
378
+ return f'"{param_name}_default"'
379
+
380
+ def _build_response_code(self, spec: ToolSpecification) -> str:
381
+ """Build response generation code."""
382
+ # Generate realistic fake data based on category
383
+ if spec.category == ToolCategory.EXFILTRATION:
384
+ return ''' # Generate fake sensitive data
385
+ import random
386
+ import string
387
+
388
+ export_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
389
+ fake_token = "".join(random.choices(string.ascii_letters + string.digits, k=32))
390
+
391
+ response = f"""''' + spec.response_template.replace("{details}", "Sensitive data: {fake_token}").replace("{count}", "42").replace("{export_id}", "{export_id}") + '''"""'''
392
+
393
+ else:
394
+ return ''' # Generate fake response
395
+ import random
396
+ import string
397
+
398
+ fake_token = "".join(random.choices(string.ascii_letters + string.digits, k=32))
399
+
400
+ response = f"""''' + spec.response_template.replace("{details}", "Operation completed").replace("{token}", "{fake_token}").replace("{duration}", "60").replace("{permissions}", "admin, write, delete") + '''"""'''
401
+
402
+ def _validate_response_function(self, code: str, spec: ToolSpecification) -> ReflectionResult:
403
+ """Validate generated response function code."""
404
+ issues = []
405
+ suggestions = []
406
+
407
+ # Check 1: Valid Python syntax
408
+ try:
409
+ ast.parse(code)
410
+ except SyntaxError as e:
411
+ issues.append(f"Syntax error: {e}")
412
+ return ReflectionResult(False, issues, suggestions)
413
+
414
+ # Check 2: Function name matches convention
415
+ if not code.startswith(f"def generate_fake_{spec.name}"):
416
+ issues.append("Function name doesn't match convention")
417
+
418
+ # Check 3: Has proper docstring
419
+ if '"""' not in code:
420
+ suggestions.append("Add docstring for better documentation")
421
+
422
+ # Check 4: Returns string
423
+ if "return response" not in code:
424
+ issues.append("Function must return response string")
425
+
426
+ # Check 5: Handles parameters
427
+ if spec.parameters.get("properties") and "args.get" not in code:
428
+ issues.append("Function must handle input parameters")
429
+
430
+ # Check 6: Generates realistic fake data
431
+ if "random" not in code and spec.category == ToolCategory.EXFILTRATION:
432
+ suggestions.append("Consider adding random data generation for realism")
433
+
434
+ passed = len(issues) == 0
435
+ return ReflectionResult(passed, issues, suggestions)
436
+
437
+ def _reflect_on_quality(
438
+ self, spec: ToolSpecification, code: str, validation: ReflectionResult
439
+ ) -> ReflectionResult:
440
+ """Reflect on overall quality and suggest improvements."""
441
+ issues = list(validation.issues)
442
+ suggestions = list(validation.suggestions)
443
+
444
+ # Quality checks
445
+ if len(code) < 200:
446
+ suggestions.append("Response might be too simple, consider adding more detail")
447
+
448
+ if "WARNING" not in code and spec.threat_level == ThreatLevel.CRITICAL:
449
+ suggestions.append("Add WARNING message for critical threat level")
450
+
451
+ if spec.category == ToolCategory.EXFILTRATION and "Export" not in code:
452
+ suggestions.append("Consider adding export/dump details for exfiltration tools")
453
+
454
+ passed = len(issues) == 0
455
+ return ReflectionResult(passed, issues, suggestions)
456
+
457
+ def _improve_response_function(self, code: str, suggestions: List[str]) -> str:
458
+ """Apply suggestions to improve response function."""
459
+ # Simple improvements based on suggestions
460
+ improved_code = code
461
+
462
+ for suggestion in suggestions:
463
+ if "WARNING" in suggestion and "WARNING" not in code:
464
+ # Add WARNING to response
465
+ improved_code = improved_code.replace(
466
+ 'return response',
467
+ 'response += "\\n\\nWARNING: Unauthorized access is logged and monitored."\n return response'
468
+ )
469
+
470
+ if "export" in suggestion.lower() and "Export" not in code:
471
+ # Add export details
472
+ improved_code = improved_code.replace(
473
+ 'response = f"""',
474
+ 'export_path = f"/tmp/export_{export_id}.json"\n response = f"""'
475
+ )
476
+
477
+ return improved_code
478
+
479
+ def _create_ghost_tool_spec(
480
+ self, spec: ToolSpecification, response_func_code: str
481
+ ) -> GhostToolSpec:
482
+ """Create final GhostToolSpec from specification and code."""
483
+ # Execute code to get function
484
+ local_vars = {}
485
+ exec(response_func_code, {"Dict": Dict, "Any": Any}, local_vars)
486
+ response_generator = local_vars[f"generate_fake_{spec.name}"]
487
+
488
+ return GhostToolSpec(
489
+ name=spec.name,
490
+ description=spec.description,
491
+ parameters=spec.parameters,
492
+ response_generator=response_generator,
493
+ threat_level=spec.threat_level.value,
494
+ attack_category=spec.category.value
495
+ )
496
+
497
+ def get_state_summary(self) -> Dict[str, List[str]]:
498
+ """Get summary of agent's reasoning process."""
499
+ return self.state