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,432 @@
1
+ """Configuration management using pydantic-settings.
2
+
3
+ This module provides type-safe configuration loading from environment variables
4
+ with automatic validation. Code can call ``get_config()`` to access the cached
5
+ singleton, and tests can use ``reset_config()`` for dependency injection.
6
+
7
+ Environment Variables:
8
+ REVERSECORE_WORKSPACE: Path to workspace directory (default: current directory)
9
+ REVERSECORE_READ_DIRS: Comma-separated list of read-only directories
10
+ LOG_LEVEL: Logging level (default: INFO)
11
+ LOG_FILE: Path to log file (default: /tmp/reversecore/app.log)
12
+ LOG_FORMAT: Log format - "human" or "json" (default: human)
13
+ STRUCTURED_ERRORS: Enable structured error responses (default: false)
14
+ RATE_LIMIT: Rate limit per minute (default: 60)
15
+ LIEF_MAX_FILE_SIZE: Max file size for LIEF parsing (default: 1GB)
16
+ MCP_TRANSPORT: Transport mode - "stdio" or "http" (default: stdio)
17
+ DEFAULT_TOOL_TIMEOUT: Default timeout in seconds (default: 120)
18
+ R2_POOL_SIZE: Radare2 connection pool size (default: 3)
19
+ R2_POOL_TIMEOUT: Radare2 pool connection timeout (default: 30)
20
+ GHIDRA_MAX_PROJECTS: Max Ghidra projects to cache for multi-malware analysis (default: 3)
21
+ REVERSECORE_STRICT_PATHS: Strict path validation mode (default: false)
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from enum import Enum
28
+ from pathlib import Path
29
+
30
+ from pydantic import Field, field_validator, model_validator
31
+ from pydantic_settings import BaseSettings, SettingsConfigDict
32
+
33
+
34
+ class LogFormat(str, Enum):
35
+ """Supported log formats."""
36
+
37
+ HUMAN = "human"
38
+ JSON = "json"
39
+
40
+
41
+ class TransportMode(str, Enum):
42
+ """Supported MCP transport modes."""
43
+
44
+ STDIO = "stdio"
45
+ HTTP = "http"
46
+
47
+
48
+ class Settings(BaseSettings):
49
+ """Application settings with environment variable support.
50
+
51
+ All settings are loaded from environment variables with automatic
52
+ type conversion and validation.
53
+ """
54
+
55
+ model_config = SettingsConfigDict(
56
+ env_prefix="REVERSECORE_",
57
+ env_file=".env",
58
+ env_file_encoding="utf-8",
59
+ extra="ignore",
60
+ case_sensitive=False,
61
+ )
62
+
63
+ # Workspace configuration
64
+ workspace: Path = Field(
65
+ default_factory=Path.cwd,
66
+ description="Path to workspace directory for file operations",
67
+ )
68
+ read_dirs: str = Field(
69
+ default="",
70
+ alias="REVERSECORE_READ_DIRS",
71
+ description="Comma-separated list of read-only directories",
72
+ )
73
+ strict_paths: bool = Field(
74
+ default=False,
75
+ description="Enable strict path validation (raise errors for missing paths)",
76
+ )
77
+
78
+ # Logging configuration
79
+ log_level: str = Field(
80
+ default="INFO",
81
+ alias="LOG_LEVEL",
82
+ description="Logging level: DEBUG, INFO, WARNING, ERROR, CRITICAL",
83
+ )
84
+ log_file: Path = Field(
85
+ default=Path("/tmp/reversecore/app.log"),
86
+ alias="LOG_FILE",
87
+ description="Path to log file",
88
+ )
89
+ log_format: LogFormat = Field(
90
+ default=LogFormat.HUMAN,
91
+ alias="LOG_FORMAT",
92
+ description="Log format: 'human' for readable, 'json' for structured",
93
+ )
94
+
95
+ # Error handling
96
+ structured_errors: bool = Field(
97
+ default=False,
98
+ description="Enable structured error responses with error codes",
99
+ )
100
+
101
+ # Rate limiting
102
+ rate_limit: int = Field(
103
+ default=60,
104
+ ge=1,
105
+ le=1000,
106
+ description="Rate limit (requests per minute)",
107
+ )
108
+
109
+ # File size limits
110
+ max_output_size: int = Field(
111
+ default=10_000_000,
112
+ ge=1000,
113
+ description="Maximum output size for tools (bytes)",
114
+ )
115
+ lief_max_file_size: int = Field(
116
+ default=1_000_000_000,
117
+ ge=1_000_000,
118
+ description="Maximum file size for LIEF parsing (bytes)",
119
+ )
120
+
121
+ file_retention_minutes: int = Field(
122
+ default=1440, # 24 hours
123
+ ge=60,
124
+ description="Retention period for temporary files (minutes)",
125
+ )
126
+
127
+ # Transport configuration
128
+ mcp_transport: TransportMode = Field(
129
+ default=TransportMode.STDIO,
130
+ alias="MCP_TRANSPORT",
131
+ description="MCP transport mode: 'stdio' or 'http'",
132
+ )
133
+
134
+ # Timeout configuration
135
+ default_tool_timeout: int = Field(
136
+ default=120,
137
+ ge=10,
138
+ le=3600,
139
+ alias="DEFAULT_TOOL_TIMEOUT",
140
+ description="Default timeout for tool execution (seconds)",
141
+ )
142
+
143
+ # R2 Pool configuration
144
+ r2_pool_size: int = Field(
145
+ default=3,
146
+ ge=1,
147
+ le=20,
148
+ description="Number of radare2 connections in pool",
149
+ )
150
+ r2_pool_timeout: int = Field(
151
+ default=30,
152
+ ge=5,
153
+ le=300,
154
+ description="Timeout for acquiring radare2 connection from pool",
155
+ )
156
+
157
+ # Ghidra configuration
158
+ ghidra_max_projects: int = Field(
159
+ default=3,
160
+ ge=1,
161
+ le=10,
162
+ description="Maximum number of Ghidra projects to cache (higher = more RAM)",
163
+ )
164
+
165
+ # Emulation configuration
166
+ max_emulation_instructions: int = Field(
167
+ default=1000,
168
+ ge=1,
169
+ le=1_000_000,
170
+ description="Maximum instructions for emulation safety limit",
171
+ )
172
+
173
+ # AI Memory configuration
174
+ memory_db_path: Path = Field(
175
+ default=Path.home() / ".reversecore_mcp" / "memory.db",
176
+ alias="MEMORY_DB_PATH",
177
+ description="Path to AI memory SQLite database",
178
+ )
179
+
180
+ @field_validator("log_level")
181
+ @classmethod
182
+ def validate_log_level(cls, v: str) -> str:
183
+ """Normalize and validate log level."""
184
+ v = v.upper()
185
+ valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
186
+ if v not in valid_levels:
187
+ raise ValueError(f"Invalid log level: {v}. Must be one of {valid_levels}")
188
+ return v
189
+
190
+ @field_validator("workspace", mode="before")
191
+ @classmethod
192
+ def expand_workspace_path(cls, v: str | Path | None) -> Path:
193
+ """Expand and resolve workspace path."""
194
+ if v is None or v == "":
195
+ return Path.cwd()
196
+ path = Path(v).expanduser().resolve()
197
+ return path
198
+
199
+ @field_validator("log_file", mode="before")
200
+ @classmethod
201
+ def expand_log_file_path(cls, v: str | Path) -> Path:
202
+ """Expand and resolve log file path."""
203
+ return Path(v).expanduser().resolve()
204
+
205
+ @model_validator(mode="after")
206
+ def validate_workspace_exists(self) -> "Settings":
207
+ """Validate workspace directory exists."""
208
+ if self.strict_paths:
209
+ if not self.workspace.exists():
210
+ raise ValueError(f"Workspace directory does not exist: {self.workspace}")
211
+ if not self.workspace.is_dir():
212
+ raise ValueError(f"Workspace path is not a directory: {self.workspace}")
213
+ return self
214
+
215
+ @property
216
+ def read_only_dirs(self) -> tuple[Path, ...]:
217
+ """Parse and return read-only directories."""
218
+ if not self.read_dirs:
219
+ return tuple()
220
+ parts = [s.strip() for s in self.read_dirs.split(",") if s.strip()]
221
+ dirs = []
222
+ for part in parts:
223
+ path = Path(part).expanduser().resolve()
224
+ if path.exists() and path.is_dir():
225
+ dirs.append(path)
226
+ return tuple(dirs)
227
+
228
+
229
+ # =============================================================================
230
+ # Legacy Config Compatibility Layer
231
+ # =============================================================================
232
+ # The following provides backward compatibility with the existing codebase
233
+ # that uses the Config dataclass interface.
234
+
235
+
236
+ class Config:
237
+ """Wrapper class for backward compatibility with existing code.
238
+
239
+ This class wraps the pydantic Settings model to maintain the same
240
+ interface as the previous dataclass-based Config.
241
+ """
242
+
243
+ def __init__(
244
+ self,
245
+ settings: Settings | None = None,
246
+ *,
247
+ workspace: Path | str | None = None,
248
+ read_only_dirs: tuple[Path, ...] | None = None,
249
+ log_level: str | None = None,
250
+ log_file: Path | str | None = None,
251
+ log_format: str | None = None,
252
+ structured_errors: bool | None = None,
253
+ rate_limit: int | None = None,
254
+ lief_max_file_size: int | None = None,
255
+ mcp_transport: str | None = None,
256
+ default_tool_timeout: int | None = None,
257
+ ):
258
+ """Initialize Config with optional Settings instance or individual values.
259
+
260
+ For backward compatibility, individual values can be passed directly.
261
+ """
262
+ if settings is not None:
263
+ self._settings = settings
264
+ else:
265
+ # Build settings from individual values if provided
266
+ env_overrides = {}
267
+ if workspace is not None:
268
+ env_overrides["workspace"] = Path(workspace)
269
+ if log_level is not None:
270
+ env_overrides["log_level"] = log_level
271
+ if log_file is not None:
272
+ env_overrides["log_file"] = Path(log_file)
273
+ if log_format is not None:
274
+ env_overrides["log_format"] = LogFormat(log_format.lower())
275
+ if structured_errors is not None:
276
+ env_overrides["structured_errors"] = structured_errors
277
+ if rate_limit is not None:
278
+ env_overrides["rate_limit"] = rate_limit
279
+ if lief_max_file_size is not None:
280
+ env_overrides["lief_max_file_size"] = lief_max_file_size
281
+ if mcp_transport is not None:
282
+ env_overrides["mcp_transport"] = TransportMode(mcp_transport.lower())
283
+ if default_tool_timeout is not None:
284
+ env_overrides["default_tool_timeout"] = default_tool_timeout
285
+
286
+ if env_overrides:
287
+ self._settings = Settings(**env_overrides)
288
+ else:
289
+ self._settings = Settings()
290
+
291
+ # Store read_only_dirs if explicitly provided (for test overrides)
292
+ self._read_only_dirs_override = read_only_dirs
293
+
294
+ @property
295
+ def workspace(self) -> Path:
296
+ return self._settings.workspace
297
+
298
+ @property
299
+ def read_only_dirs(self) -> tuple[Path, ...]:
300
+ if self._read_only_dirs_override is not None:
301
+ return self._read_only_dirs_override
302
+ return self._settings.read_only_dirs
303
+
304
+ @property
305
+ def log_level(self) -> str:
306
+ return self._settings.log_level
307
+
308
+ @property
309
+ def log_file(self) -> Path:
310
+ return self._settings.log_file
311
+
312
+ @property
313
+ def log_format(self) -> str:
314
+ return self._settings.log_format.value
315
+
316
+ @property
317
+ def structured_errors(self) -> bool:
318
+ return self._settings.structured_errors
319
+
320
+ @property
321
+ def rate_limit(self) -> int:
322
+ return self._settings.rate_limit
323
+
324
+ @property
325
+ def lief_max_file_size(self) -> int:
326
+ return self._settings.lief_max_file_size
327
+
328
+ @property
329
+ def max_output_size(self) -> int:
330
+ return self._settings.max_output_size
331
+
332
+ @property
333
+ def mcp_transport(self) -> str:
334
+ return self._settings.mcp_transport.value
335
+
336
+ @property
337
+ def default_tool_timeout(self) -> int:
338
+ return self._settings.default_tool_timeout
339
+
340
+ @property
341
+ def r2_pool_size(self) -> int:
342
+ return self._settings.r2_pool_size
343
+
344
+ @property
345
+ def r2_pool_timeout(self) -> int:
346
+ return self._settings.r2_pool_timeout
347
+
348
+ @property
349
+ def ghidra_max_projects(self) -> int:
350
+ return self._settings.ghidra_max_projects
351
+
352
+ @property
353
+ def max_emulation_instructions(self) -> int:
354
+ return self._settings.max_emulation_instructions
355
+
356
+ @property
357
+ def file_retention_minutes(self) -> int:
358
+ return self._settings.file_retention_minutes
359
+
360
+ @property
361
+ def file_retention_minutes(self) -> int:
362
+ return self._settings.file_retention_minutes
363
+
364
+ @classmethod
365
+ def from_env(cls) -> "Config":
366
+ """Build a Config instance from environment variables."""
367
+ return cls(Settings())
368
+
369
+ def validate_paths(self, strict: bool = True) -> None:
370
+ """Validate that configured directories exist and are directories."""
371
+ logger = logging.getLogger(__name__)
372
+
373
+ if not self.workspace.exists():
374
+ msg = f"Workspace directory does not exist: {self.workspace}"
375
+ if strict:
376
+ raise ValueError(msg)
377
+ logger.warning(msg)
378
+ elif not self.workspace.is_dir():
379
+ msg = f"Workspace path is not a directory: {self.workspace}"
380
+ if strict:
381
+ raise ValueError(msg)
382
+ logger.warning(msg)
383
+
384
+ for read_dir in self.read_only_dirs:
385
+ if not read_dir.exists():
386
+ msg = f"Read directory does not exist: {read_dir}"
387
+ if strict:
388
+ raise ValueError(msg)
389
+ logger.warning(msg)
390
+ elif not read_dir.is_dir():
391
+ msg = f"Read directory path is not a directory: {read_dir}"
392
+ if strict:
393
+ raise ValueError(msg)
394
+ logger.warning(msg)
395
+
396
+
397
+ # =============================================================================
398
+ # Module-level config management
399
+ # =============================================================================
400
+
401
+ _CONFIG: Config | None = None
402
+
403
+
404
+ def get_config() -> Config:
405
+ """Return the cached Config instance, loading it on first access."""
406
+ global _CONFIG
407
+ if _CONFIG is None:
408
+ _CONFIG = Config.from_env()
409
+ return _CONFIG
410
+
411
+
412
+ def get_settings() -> Settings:
413
+ """Return the underlying pydantic Settings instance."""
414
+ return get_config()._settings
415
+
416
+
417
+ def reset_config() -> Config:
418
+ """Reload configuration from the current environment (primarily for tests)."""
419
+ global _CONFIG
420
+ _CONFIG = Config.from_env()
421
+ try:
422
+ from reversecore_mcp.core import security
423
+
424
+ security.refresh_workspace_config()
425
+ except Exception:
426
+ pass
427
+ return _CONFIG
428
+
429
+
430
+ def reload_settings() -> Config:
431
+ """Backward-compatible alias for legacy test helpers."""
432
+ return reset_config()