iflow-mcp_developermode-korea_reversecore-mcp 1.0.0__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.
Files changed (79) hide show
  1. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/METADATA +543 -0
  2. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/RECORD +79 -0
  3. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/top_level.txt +1 -0
  7. reversecore_mcp/__init__.py +9 -0
  8. reversecore_mcp/core/__init__.py +78 -0
  9. reversecore_mcp/core/audit.py +101 -0
  10. reversecore_mcp/core/binary_cache.py +138 -0
  11. reversecore_mcp/core/command_spec.py +357 -0
  12. reversecore_mcp/core/config.py +432 -0
  13. reversecore_mcp/core/container.py +288 -0
  14. reversecore_mcp/core/decorators.py +152 -0
  15. reversecore_mcp/core/error_formatting.py +93 -0
  16. reversecore_mcp/core/error_handling.py +142 -0
  17. reversecore_mcp/core/evidence.py +229 -0
  18. reversecore_mcp/core/exceptions.py +296 -0
  19. reversecore_mcp/core/execution.py +240 -0
  20. reversecore_mcp/core/ghidra.py +642 -0
  21. reversecore_mcp/core/ghidra_helper.py +481 -0
  22. reversecore_mcp/core/ghidra_manager.py +234 -0
  23. reversecore_mcp/core/json_utils.py +131 -0
  24. reversecore_mcp/core/loader.py +73 -0
  25. reversecore_mcp/core/logging_config.py +206 -0
  26. reversecore_mcp/core/memory.py +721 -0
  27. reversecore_mcp/core/metrics.py +198 -0
  28. reversecore_mcp/core/mitre_mapper.py +365 -0
  29. reversecore_mcp/core/plugin.py +45 -0
  30. reversecore_mcp/core/r2_helpers.py +404 -0
  31. reversecore_mcp/core/r2_pool.py +403 -0
  32. reversecore_mcp/core/report_generator.py +268 -0
  33. reversecore_mcp/core/resilience.py +252 -0
  34. reversecore_mcp/core/resource_manager.py +169 -0
  35. reversecore_mcp/core/result.py +132 -0
  36. reversecore_mcp/core/security.py +213 -0
  37. reversecore_mcp/core/validators.py +238 -0
  38. reversecore_mcp/dashboard/__init__.py +221 -0
  39. reversecore_mcp/prompts/__init__.py +56 -0
  40. reversecore_mcp/prompts/common.py +24 -0
  41. reversecore_mcp/prompts/game.py +280 -0
  42. reversecore_mcp/prompts/malware.py +1219 -0
  43. reversecore_mcp/prompts/report.py +150 -0
  44. reversecore_mcp/prompts/security.py +136 -0
  45. reversecore_mcp/resources.py +329 -0
  46. reversecore_mcp/server.py +727 -0
  47. reversecore_mcp/tools/__init__.py +49 -0
  48. reversecore_mcp/tools/analysis/__init__.py +74 -0
  49. reversecore_mcp/tools/analysis/capa_tools.py +215 -0
  50. reversecore_mcp/tools/analysis/die_tools.py +180 -0
  51. reversecore_mcp/tools/analysis/diff_tools.py +643 -0
  52. reversecore_mcp/tools/analysis/lief_tools.py +272 -0
  53. reversecore_mcp/tools/analysis/signature_tools.py +591 -0
  54. reversecore_mcp/tools/analysis/static_analysis.py +479 -0
  55. reversecore_mcp/tools/common/__init__.py +58 -0
  56. reversecore_mcp/tools/common/file_operations.py +352 -0
  57. reversecore_mcp/tools/common/memory_tools.py +516 -0
  58. reversecore_mcp/tools/common/patch_explainer.py +230 -0
  59. reversecore_mcp/tools/common/server_tools.py +115 -0
  60. reversecore_mcp/tools/ghidra/__init__.py +19 -0
  61. reversecore_mcp/tools/ghidra/decompilation.py +975 -0
  62. reversecore_mcp/tools/ghidra/ghidra_tools.py +1052 -0
  63. reversecore_mcp/tools/malware/__init__.py +61 -0
  64. reversecore_mcp/tools/malware/adaptive_vaccine.py +579 -0
  65. reversecore_mcp/tools/malware/dormant_detector.py +756 -0
  66. reversecore_mcp/tools/malware/ioc_tools.py +228 -0
  67. reversecore_mcp/tools/malware/vulnerability_hunter.py +519 -0
  68. reversecore_mcp/tools/malware/yara_tools.py +214 -0
  69. reversecore_mcp/tools/patch_explainer.py +19 -0
  70. reversecore_mcp/tools/radare2/__init__.py +13 -0
  71. reversecore_mcp/tools/radare2/r2_analysis.py +972 -0
  72. reversecore_mcp/tools/radare2/r2_session.py +376 -0
  73. reversecore_mcp/tools/radare2/radare2_mcp_tools.py +1183 -0
  74. reversecore_mcp/tools/report/__init__.py +4 -0
  75. reversecore_mcp/tools/report/email.py +82 -0
  76. reversecore_mcp/tools/report/report_mcp_tools.py +344 -0
  77. reversecore_mcp/tools/report/report_tools.py +1076 -0
  78. reversecore_mcp/tools/report/session.py +194 -0
  79. reversecore_mcp/tools/report_tools.py +11 -0
@@ -0,0 +1,229 @@
1
+ """
2
+ Evidence-based analysis types and confidence levels.
3
+
4
+ This module provides data structures for tracking the evidence level
5
+ and confidence of analysis findings, preventing hallucination and
6
+ over-inference in automated reports.
7
+ """
8
+
9
+ from enum import Enum
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime
12
+ from typing import Any, Optional
13
+
14
+
15
+ class EvidenceLevel(str, Enum):
16
+ """Evidence level classification for analysis findings.
17
+
18
+ Based on professional SOC/IR standards:
19
+ - OBSERVED: Directly observed through dynamic analysis or tracing
20
+ - INFERRED: Logically inferred from static analysis (high confidence)
21
+ - POSSIBLE: Hypothesized based on patterns (requires verification)
22
+ """
23
+ OBSERVED = "observed" # Directly observed (dynamic analysis, logs, traces)
24
+ INFERRED = "inferred" # Logically inferred from static analysis
25
+ POSSIBLE = "possible" # Possible but needs verification
26
+
27
+ @property
28
+ def symbol(self) -> str:
29
+ """Return a symbol for display."""
30
+ return {
31
+ "observed": "🔍",
32
+ "inferred": "🔎",
33
+ "possible": "❓",
34
+ }[self.value]
35
+
36
+ @property
37
+ def confidence_score(self) -> float:
38
+ """Return a confidence score (0.0-1.0)."""
39
+ return {
40
+ "observed": 1.0,
41
+ "inferred": 0.7,
42
+ "possible": 0.4,
43
+ }[self.value]
44
+
45
+
46
+ class MITREConfidence(str, Enum):
47
+ """MITRE ATT&CK mapping confidence levels."""
48
+ CONFIRMED = "confirmed" # Multiple evidence sources
49
+ HIGH = "high" # Strong single evidence
50
+ MEDIUM = "medium" # Inferred from API/patterns
51
+ LOW = "low" # Possible based on behavior
52
+
53
+
54
+ @dataclass
55
+ class Evidence:
56
+ """Evidence record for a finding."""
57
+ source: str # e.g., "strings", "imports", "decompile", "sandbox"
58
+ location: str # e.g., "0x401000", "section:.rsrc", "log:procmon"
59
+ description: str # What was found
60
+ raw_data: Optional[str] = None # Raw evidence (hex, string, etc.)
61
+ timestamp: datetime = field(default_factory=datetime.now)
62
+
63
+ def to_dict(self) -> dict[str, Any]:
64
+ return {
65
+ "source": self.source,
66
+ "location": self.location,
67
+ "description": self.description,
68
+ "raw_data": self.raw_data,
69
+ "timestamp": self.timestamp.isoformat(),
70
+ }
71
+
72
+
73
+ @dataclass
74
+ class Finding:
75
+ """An analysis finding with evidence tracking."""
76
+ title: str
77
+ description: str
78
+ level: EvidenceLevel
79
+ category: str # e.g., "persistence", "encryption", "network"
80
+ evidence: list[Evidence] = field(default_factory=list)
81
+ mitre_techniques: list[str] = field(default_factory=list)
82
+
83
+ def add_evidence(self, source: str, location: str, description: str,
84
+ raw_data: Optional[str] = None) -> None:
85
+ """Add evidence to this finding."""
86
+ self.evidence.append(Evidence(
87
+ source=source,
88
+ location=location,
89
+ description=description,
90
+ raw_data=raw_data,
91
+ ))
92
+
93
+ @property
94
+ def confidence(self) -> float:
95
+ """Calculate overall confidence based on evidence level and count."""
96
+ base = self.level.confidence_score
97
+ # More evidence = higher confidence (up to 20% boost)
98
+ evidence_boost = min(len(self.evidence) * 0.05, 0.2)
99
+ return min(base + evidence_boost, 1.0)
100
+
101
+ def to_dict(self) -> dict[str, Any]:
102
+ return {
103
+ "title": self.title,
104
+ "description": self.description,
105
+ "level": self.level.value,
106
+ "level_symbol": self.level.symbol,
107
+ "category": self.category,
108
+ "confidence": round(self.confidence, 2),
109
+ "evidence_count": len(self.evidence),
110
+ "evidence": [e.to_dict() for e in self.evidence],
111
+ "mitre_techniques": self.mitre_techniques,
112
+ }
113
+
114
+ def format_markdown(self) -> str:
115
+ """Format finding as markdown with evidence."""
116
+ lines = [
117
+ f"### {self.level.symbol} [{self.level.value.upper()}] {self.title}",
118
+ f"",
119
+ f"**Confidence**: {self.confidence:.0%}",
120
+ f"**Category**: {self.category}",
121
+ f"",
122
+ self.description,
123
+ f"",
124
+ ]
125
+
126
+ if self.evidence:
127
+ lines.append("**Evidence:**")
128
+ for i, ev in enumerate(self.evidence, 1):
129
+ lines.append(f" {i}. `{ev.source}` @ `{ev.location}`: {ev.description}")
130
+ if ev.raw_data:
131
+ lines.append(f" ```")
132
+ lines.append(f" {ev.raw_data[:200]}{'...' if len(ev.raw_data) > 200 else ''}")
133
+ lines.append(f" ```")
134
+
135
+ if self.mitre_techniques:
136
+ lines.append(f"")
137
+ lines.append(f"**MITRE ATT&CK**: {', '.join(self.mitre_techniques)}")
138
+
139
+ return "\n".join(lines)
140
+
141
+
142
+ @dataclass
143
+ class MITRETechnique:
144
+ """MITRE ATT&CK technique with confidence tracking."""
145
+ technique_id: str # e.g., "T1055"
146
+ technique_name: str
147
+ tactic: str
148
+ confidence: MITREConfidence
149
+ evidence: list[Evidence] = field(default_factory=list)
150
+
151
+ def to_dict(self) -> dict[str, Any]:
152
+ return {
153
+ "technique_id": self.technique_id,
154
+ "technique_name": self.technique_name,
155
+ "tactic": self.tactic,
156
+ "confidence": self.confidence.value,
157
+ "evidence_count": len(self.evidence),
158
+ }
159
+
160
+ def format_markdown_row(self) -> str:
161
+ """Format as markdown table row."""
162
+ conf_symbol = {
163
+ "confirmed": "✅",
164
+ "high": "🟢",
165
+ "medium": "🟡",
166
+ "low": "🔴",
167
+ }[self.confidence.value]
168
+
169
+ return f"| {self.technique_id} | {self.technique_name} | {self.tactic} | {conf_symbol} {self.confidence.value} |"
170
+
171
+
172
+ @dataclass
173
+ class AnalysisMetadata:
174
+ """Unified metadata for analysis session (single source of truth)."""
175
+ session_id: str
176
+ sample_name: str
177
+ sample_hash: str # SHA256
178
+ start_time: datetime
179
+ end_time: Optional[datetime] = None
180
+ analyst: str = "Reversecore MCP"
181
+ tools_used: list[str] = field(default_factory=list)
182
+
183
+ @property
184
+ def duration_seconds(self) -> float:
185
+ """Calculate analysis duration in seconds."""
186
+ if self.end_time:
187
+ return (self.end_time - self.start_time).total_seconds()
188
+ return (datetime.now() - self.start_time).total_seconds()
189
+
190
+ @property
191
+ def duration_formatted(self) -> str:
192
+ """Return human-readable duration."""
193
+ seconds = self.duration_seconds
194
+ if seconds < 60:
195
+ return f"{seconds:.1f} seconds"
196
+ elif seconds < 3600:
197
+ return f"{seconds / 60:.1f} minutes"
198
+ else:
199
+ return f"{seconds / 3600:.1f} hours"
200
+
201
+ def to_dict(self) -> dict[str, Any]:
202
+ return {
203
+ "session_id": self.session_id,
204
+ "sample_name": self.sample_name,
205
+ "sample_hash": self.sample_hash,
206
+ "start_time": self.start_time.isoformat(),
207
+ "end_time": self.end_time.isoformat() if self.end_time else None,
208
+ "duration": self.duration_formatted,
209
+ "duration_seconds": round(self.duration_seconds, 2),
210
+ "analyst": self.analyst,
211
+ "tools_used": self.tools_used,
212
+ }
213
+
214
+
215
+ # Helper functions for quick finding creation
216
+ def observed_finding(title: str, description: str, category: str, **kwargs) -> Finding:
217
+ """Create an OBSERVED level finding."""
218
+ return Finding(title=title, description=description,
219
+ level=EvidenceLevel.OBSERVED, category=category, **kwargs)
220
+
221
+ def inferred_finding(title: str, description: str, category: str, **kwargs) -> Finding:
222
+ """Create an INFERRED level finding."""
223
+ return Finding(title=title, description=description,
224
+ level=EvidenceLevel.INFERRED, category=category, **kwargs)
225
+
226
+ def possible_finding(title: str, description: str, category: str, **kwargs) -> Finding:
227
+ """Create a POSSIBLE level finding."""
228
+ return Finding(title=title, description=description,
229
+ level=EvidenceLevel.POSSIBLE, category=category, **kwargs)
@@ -0,0 +1,296 @@
1
+ """
2
+ Custom exception classes for Reversecore_MCP.
3
+
4
+ All exceptions inherit from ReversecoreError to allow for centralized
5
+ exception handling at the MCP server level.
6
+ """
7
+
8
+
9
+ class ReversecoreError(Exception):
10
+ """Base exception for all Reversecore_MCP errors."""
11
+
12
+ error_code: str = "RCMCP-E000"
13
+ error_type: str = "UNKNOWN_ERROR"
14
+
15
+ def __init__(
16
+ self,
17
+ message: str,
18
+ error_code: str | None = None,
19
+ error_type: str | None = None,
20
+ ):
21
+ self.message = message
22
+ if error_code:
23
+ self.error_code = error_code
24
+ if error_type:
25
+ self.error_type = error_type
26
+ super().__init__(message)
27
+
28
+
29
+ class ToolNotFoundError(ReversecoreError):
30
+ """Raised when a required CLI tool is not found in the system."""
31
+
32
+ error_code = "RCMCP-E003"
33
+ error_type = "TOOL_ERROR"
34
+
35
+ def __init__(self, tool_name: str):
36
+ self.tool_name = tool_name
37
+ message = f"Tool '{tool_name}' not found. Please install it."
38
+ super().__init__(message, self.error_code, self.error_type)
39
+
40
+
41
+ class ExecutionTimeoutError(ReversecoreError):
42
+ """Raised when a subprocess execution exceeds the timeout limit."""
43
+
44
+ error_code = "RCMCP-E002"
45
+ error_type = "TIMEOUT_ERROR"
46
+
47
+ def __init__(self, timeout_seconds: int):
48
+ self.timeout_seconds = timeout_seconds
49
+ message = f"Operation timed out after {timeout_seconds} seconds."
50
+ super().__init__(message, self.error_code, self.error_type)
51
+
52
+
53
+ class OutputLimitExceededError(ReversecoreError):
54
+ """Raised when subprocess output exceeds the maximum allowed size."""
55
+
56
+ error_code = "RCMCP-E004"
57
+ error_type = "OUTPUT_ERROR"
58
+
59
+ def __init__(self, max_size: int, actual_size: int):
60
+ self.max_size = max_size
61
+ self.actual_size = actual_size
62
+ message = (
63
+ f"Output limit exceeded: {actual_size} bytes (max: {max_size} bytes). "
64
+ "Output has been truncated."
65
+ )
66
+ super().__init__(message, self.error_code, self.error_type)
67
+
68
+
69
+ class ValidationError(ReversecoreError):
70
+ """Raised when input validation fails."""
71
+
72
+ error_code = "RCMCP-E001"
73
+ error_type = "VALIDATION_ERROR"
74
+
75
+ def __init__(self, message: str, details: dict | None = None):
76
+ self.details = details or {}
77
+ super().__init__(message, self.error_code, self.error_type)
78
+
79
+
80
+ class ToolExecutionError(ReversecoreError):
81
+ """Raised when a tool execution fails."""
82
+
83
+ error_code = "RCMCP-E005"
84
+ error_type = "EXECUTION_ERROR"
85
+
86
+ def __init__(self, message: str):
87
+ super().__init__(message, self.error_code, self.error_type)
88
+
89
+
90
+ # ============================================================================
91
+ # Binary Analysis Exceptions
92
+ # ============================================================================
93
+
94
+
95
+ class BinaryAnalysisError(ReversecoreError):
96
+ """Base exception for binary analysis operations."""
97
+
98
+ error_code = "RCMCP-E100"
99
+ error_type = "BINARY_ANALYSIS_ERROR"
100
+
101
+ def __init__(
102
+ self,
103
+ message: str,
104
+ binary_path: str | None = None,
105
+ tool_name: str | None = None,
106
+ ):
107
+ self.binary_path = binary_path
108
+ self.tool_name = tool_name
109
+ super().__init__(message, self.error_code, self.error_type)
110
+
111
+
112
+ class DecompilationError(BinaryAnalysisError):
113
+ """Raised when decompilation of a function fails."""
114
+
115
+ error_code = "RCMCP-E101"
116
+ error_type = "DECOMPILATION_ERROR"
117
+
118
+ def __init__(
119
+ self,
120
+ message: str,
121
+ function_address: str | None = None,
122
+ binary_path: str | None = None,
123
+ tool_name: str = "ghidra",
124
+ ):
125
+ self.function_address = function_address
126
+ detailed_message = message
127
+ if function_address:
128
+ detailed_message = f"Failed to decompile function at {function_address}: {message}"
129
+ super().__init__(detailed_message, binary_path, tool_name)
130
+
131
+
132
+ class DisassemblyError(BinaryAnalysisError):
133
+ """Raised when disassembly of code fails."""
134
+
135
+ error_code = "RCMCP-E102"
136
+ error_type = "DISASSEMBLY_ERROR"
137
+
138
+ def __init__(
139
+ self,
140
+ message: str,
141
+ address: str | None = None,
142
+ binary_path: str | None = None,
143
+ ):
144
+ self.address = address
145
+ detailed_message = message
146
+ if address:
147
+ detailed_message = f"Failed to disassemble at {address}: {message}"
148
+ super().__init__(detailed_message, binary_path, tool_name="radare2")
149
+
150
+
151
+ class StructureRecoveryError(BinaryAnalysisError):
152
+ """Raised when structure recovery fails."""
153
+
154
+ error_code = "RCMCP-E103"
155
+ error_type = "STRUCTURE_RECOVERY_ERROR"
156
+
157
+ def __init__(
158
+ self,
159
+ message: str,
160
+ function_address: str | None = None,
161
+ binary_path: str | None = None,
162
+ ):
163
+ self.function_address = function_address
164
+ super().__init__(message, binary_path, tool_name="ghidra")
165
+
166
+
167
+ class SignatureGenerationError(BinaryAnalysisError):
168
+ """Raised when YARA signature generation fails."""
169
+
170
+ error_code = "RCMCP-E104"
171
+ error_type = "SIGNATURE_GENERATION_ERROR"
172
+
173
+ def __init__(
174
+ self,
175
+ message: str,
176
+ address: str | None = None,
177
+ binary_path: str | None = None,
178
+ ):
179
+ self.address = address
180
+ super().__init__(message, binary_path, tool_name="radare2")
181
+
182
+
183
+ class EmulationError(BinaryAnalysisError):
184
+ """Raised when code emulation fails."""
185
+
186
+ error_code = "RCMCP-E105"
187
+ error_type = "EMULATION_ERROR"
188
+
189
+ def __init__(
190
+ self,
191
+ message: str,
192
+ start_address: str | None = None,
193
+ binary_path: str | None = None,
194
+ ):
195
+ self.start_address = start_address
196
+ super().__init__(message, binary_path, tool_name="radare2")
197
+
198
+
199
+ # ============================================================================
200
+ # Tool-Specific Exceptions
201
+ # ============================================================================
202
+
203
+
204
+ class ToolTimeoutError(ReversecoreError):
205
+ """Raised when a specific tool exceeds its timeout."""
206
+
207
+ error_code = "RCMCP-E200"
208
+ error_type = "TOOL_TIMEOUT_ERROR"
209
+
210
+ def __init__(
211
+ self,
212
+ tool_name: str,
213
+ timeout_seconds: int,
214
+ operation: str | None = None,
215
+ ):
216
+ self.tool_name = tool_name
217
+ self.timeout_seconds = timeout_seconds
218
+ self.operation = operation
219
+ if operation:
220
+ message = f"{tool_name} timed out after {timeout_seconds}s during {operation}"
221
+ else:
222
+ message = f"{tool_name} timed out after {timeout_seconds} seconds"
223
+ super().__init__(message, self.error_code, self.error_type)
224
+
225
+
226
+ class GhidraConnectionError(ReversecoreError):
227
+ """Raised when connection to Ghidra server fails."""
228
+
229
+ error_code = "RCMCP-E201"
230
+ error_type = "GHIDRA_CONNECTION_ERROR"
231
+
232
+ def __init__(self, message: str, host: str | None = None, port: int | None = None):
233
+ self.host = host
234
+ self.port = port
235
+ detailed_message = message
236
+ if host and port:
237
+ detailed_message = f"Failed to connect to Ghidra at {host}:{port}: {message}"
238
+ super().__init__(detailed_message, self.error_code, self.error_type)
239
+
240
+
241
+ class Radare2Error(ReversecoreError):
242
+ """Raised when radare2 operation fails."""
243
+
244
+ error_code = "RCMCP-E202"
245
+ error_type = "RADARE2_ERROR"
246
+
247
+ def __init__(
248
+ self,
249
+ message: str,
250
+ command: str | None = None,
251
+ binary_path: str | None = None,
252
+ ):
253
+ self.command = command
254
+ self.binary_path = binary_path
255
+ detailed_message = message
256
+ if command:
257
+ detailed_message = f"radare2 command '{command}' failed: {message}"
258
+ super().__init__(detailed_message, self.error_code, self.error_type)
259
+
260
+
261
+ # ============================================================================
262
+ # Workspace & Security Exceptions
263
+ # ============================================================================
264
+
265
+
266
+ class WorkspaceError(ReversecoreError):
267
+ """Raised when workspace operation fails."""
268
+
269
+ error_code = "RCMCP-E300"
270
+ error_type = "WORKSPACE_ERROR"
271
+
272
+ def __init__(self, message: str, path: str | None = None):
273
+ self.path = path
274
+ super().__init__(message, self.error_code, self.error_type)
275
+
276
+
277
+ class SecurityViolationError(ReversecoreError):
278
+ """Raised when a security policy is violated."""
279
+
280
+ error_code = "RCMCP-E301"
281
+ error_type = "SECURITY_VIOLATION"
282
+
283
+ def __init__(self, message: str, attempted_path: str | None = None):
284
+ self.attempted_path = attempted_path
285
+ super().__init__(message, self.error_code, self.error_type)
286
+
287
+
288
+ class PathTraversalError(SecurityViolationError):
289
+ """Raised when path traversal attack is detected."""
290
+
291
+ error_code = "RCMCP-E302"
292
+ error_type = "PATH_TRAVERSAL"
293
+
294
+ def __init__(self, attempted_path: str):
295
+ message = f"Path traversal detected: {attempted_path}"
296
+ super().__init__(message, attempted_path)