mcpower-proxy 0.0.72__tar.gz → 0.0.82__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.
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/PKG-INFO +3 -2
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/pyproject.toml +23 -3
- mcpower_proxy-0.0.82/src/ide_tools/common/__init__.py +5 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/__init__.py +5 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/init.py +130 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/output.py +63 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/prompt_submit.py +136 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/read_file.py +170 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/shell_execution.py +289 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/shell_parser_bashlex.py +394 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/types.py +34 -0
- mcpower_proxy-0.0.82/src/ide_tools/common/hooks/utils.py +286 -0
- mcpower_proxy-0.0.82/src/ide_tools/cursor/constants.py +77 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/ide_tools/cursor/router.py +10 -3
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/ide_tools/router.py +4 -1
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/mcpower_proxy.egg-info/PKG-INFO +3 -2
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/mcpower_proxy.egg-info/SOURCES.txt +12 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/mcpower_proxy.egg-info/requires.txt +2 -1
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/decision_handler.py +2 -4
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/logs/audit_trail.py +5 -4
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/redaction/gitleaks_rules.py +98 -2
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/redaction/pii_rules.py +0 -48
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/classes.py +0 -1
- mcpower_proxy-0.0.82/src/modules/utils/platform.py +23 -0
- mcpower_proxy-0.0.82/src/modules/utils/string.py +17 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/wrapper/__version__.py +1 -1
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/wrapper/middleware.py +23 -10
- mcpower_proxy-0.0.72/src/ide_tools/cursor/constants.py +0 -58
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/LICENSE +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/README.md +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/setup.cfg +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/LICENSE +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/ide_tools/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/ide_tools/cursor/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/ide_tools/cursor/format.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/main.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/mcpower_proxy.egg-info/dependency_links.txt +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/mcpower_proxy.egg-info/entry_points.txt +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/mcpower_proxy.egg-info/top_level.txt +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/apis/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/apis/security_policy.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/logs/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/logs/logger.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/redaction/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/redaction/constants.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/redaction/redactor.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/confirmation.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/simple_dialog.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/constants.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/mac_dialogs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/tk_dialogs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/windows_custom_dialog.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/windows_dialogs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/windows_structs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/yad_dialogs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/ui/xdialog/zenity_dialogs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/cli.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/config.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/copy.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/ids.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/json.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/modules/utils/mcp_configs.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/wrapper/__init__.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/wrapper/schema.py +0 -0
- {mcpower_proxy-0.0.72 → mcpower_proxy-0.0.82}/src/wrapper/server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpower-proxy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.82
|
|
4
4
|
Summary: MCPower Security proxy
|
|
5
5
|
Author-email: MCPower Security <support@mcpower.tech>
|
|
6
6
|
License: Apache License
|
|
@@ -216,7 +216,8 @@ Requires-Dist: watchdog>=3.0.0
|
|
|
216
216
|
Requires-Dist: jsonc-parser>=1.1.5
|
|
217
217
|
Requires-Dist: jsonpath-ng>=1.7.0
|
|
218
218
|
Requires-Dist: pydantic>=2.8.0
|
|
219
|
-
Requires-Dist: mcpower-shared==0.1.
|
|
219
|
+
Requires-Dist: mcpower-shared==0.1.5
|
|
220
|
+
Requires-Dist: bashlex>=0.18
|
|
220
221
|
Dynamic: license-file
|
|
221
222
|
|
|
222
223
|
# MCPower Proxy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mcpower-proxy"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.82"
|
|
4
4
|
description = "MCPower Security proxy"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = "~=3.11.0"
|
|
@@ -16,7 +16,8 @@ dependencies = [
|
|
|
16
16
|
"jsonc-parser>=1.1.5",
|
|
17
17
|
"jsonpath-ng>=1.7.0",
|
|
18
18
|
"pydantic>=2.8.0",
|
|
19
|
-
"mcpower-shared==0.1.
|
|
19
|
+
"mcpower-shared==0.1.5",
|
|
20
|
+
"bashlex>=0.18",
|
|
20
21
|
]
|
|
21
22
|
|
|
22
23
|
[project.license]
|
|
@@ -31,9 +32,28 @@ build-backend = "setuptools.build_meta"
|
|
|
31
32
|
|
|
32
33
|
[tool.setuptools]
|
|
33
34
|
package-dir = {"" = "src"}
|
|
34
|
-
packages = [
|
|
35
|
+
packages = [
|
|
36
|
+
"modules",
|
|
37
|
+
"modules.apis",
|
|
38
|
+
"modules.logs",
|
|
39
|
+
"modules.redaction",
|
|
40
|
+
"modules.ui",
|
|
41
|
+
"modules.ui.xdialog",
|
|
42
|
+
"modules.utils",
|
|
43
|
+
"wrapper",
|
|
44
|
+
"ide_tools",
|
|
45
|
+
"ide_tools.common",
|
|
46
|
+
"ide_tools.common.hooks",
|
|
47
|
+
"ide_tools.cursor"
|
|
48
|
+
]
|
|
35
49
|
py-modules = ["main"]
|
|
36
50
|
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"pytest>=8.0.0",
|
|
54
|
+
"pytest-anyio>=0.0.0",
|
|
55
|
+
]
|
|
56
|
+
|
|
37
57
|
[tool.uv]
|
|
38
58
|
|
|
39
59
|
[tool.pytest.ini_options]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared initialization logic for IDE tools
|
|
3
|
+
|
|
4
|
+
Registers IDE hooks with the security API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
|
|
10
|
+
from mcpower_shared.mcp_types import InitRequest, EnvironmentContext, ServerRef, ToolRef
|
|
11
|
+
from modules.apis.security_policy import SecurityPolicyClient
|
|
12
|
+
from modules.logs.audit_trail import AuditTrailLogger
|
|
13
|
+
from modules.logs.logger import MCPLogger
|
|
14
|
+
from modules.utils.ids import get_session_id, read_app_uid, get_project_mcpower_dir
|
|
15
|
+
from modules.utils.json import safe_json_dumps
|
|
16
|
+
from wrapper.__version__ import __version__
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def output_init_result(success: bool, message: str):
|
|
20
|
+
"""
|
|
21
|
+
Output init result to stdout
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
success: True if initialization succeeded
|
|
25
|
+
message: Status message
|
|
26
|
+
"""
|
|
27
|
+
result = {
|
|
28
|
+
"success": success,
|
|
29
|
+
"message": message
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
print(safe_json_dumps(result), flush=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def handle_init(
|
|
36
|
+
logger: MCPLogger,
|
|
37
|
+
audit_logger: AuditTrailLogger,
|
|
38
|
+
event_id: str,
|
|
39
|
+
prompt_id: str,
|
|
40
|
+
cwd: Optional[str],
|
|
41
|
+
server_name: str,
|
|
42
|
+
client_name: str,
|
|
43
|
+
hooks: Dict[str, Dict[str, str]]
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Generic init handler - registers hooks with security API
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
logger: Logger instance
|
|
50
|
+
audit_logger: Audit logger instance
|
|
51
|
+
event_id: Event identifier
|
|
52
|
+
prompt_id: Prompt identifier
|
|
53
|
+
cwd: Current working directory
|
|
54
|
+
server_name: IDE-specific server name (e.g. "cursor_tools_mcp")
|
|
55
|
+
client_name: IDE-specific client name (e.g. "cursor", "claude-code")
|
|
56
|
+
hooks: Dict of hook definitions with {name, description, version}
|
|
57
|
+
|
|
58
|
+
Outputs result and exits with appropriate code.
|
|
59
|
+
"""
|
|
60
|
+
session_id = get_session_id()
|
|
61
|
+
|
|
62
|
+
logger.info(f"Init handler started (client={client_name}, event_id={event_id}, prompt_id={prompt_id}, cwd={cwd})")
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
app_uid = read_app_uid(logger, get_project_mcpower_dir(cwd))
|
|
66
|
+
audit_logger.set_app_uid(app_uid)
|
|
67
|
+
|
|
68
|
+
audit_logger.log_event(
|
|
69
|
+
"mcpower_start",
|
|
70
|
+
{
|
|
71
|
+
"wrapper_version": __version__,
|
|
72
|
+
"wrapped_server_name": server_name,
|
|
73
|
+
"client": client_name
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
tools = [
|
|
79
|
+
ToolRef(
|
|
80
|
+
name=hook_info["name"],
|
|
81
|
+
description=f"Description:\n{hook_info['description']}\n\n"
|
|
82
|
+
f"inputSchema:\n{hook_info['parameters']}",
|
|
83
|
+
version=hook_info["version"]
|
|
84
|
+
)
|
|
85
|
+
for hook_info in hooks.values()
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
init_request = InitRequest(
|
|
89
|
+
environment=EnvironmentContext(
|
|
90
|
+
session_id=session_id,
|
|
91
|
+
workspace={
|
|
92
|
+
"roots": [cwd] if cwd else [],
|
|
93
|
+
"current_files": []
|
|
94
|
+
},
|
|
95
|
+
client=client_name,
|
|
96
|
+
client_version=__version__,
|
|
97
|
+
selection_hash=""
|
|
98
|
+
),
|
|
99
|
+
server=ServerRef(
|
|
100
|
+
name=server_name,
|
|
101
|
+
transport="stdio",
|
|
102
|
+
version="1.0.0",
|
|
103
|
+
context="ide"
|
|
104
|
+
),
|
|
105
|
+
tools=tools
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async with SecurityPolicyClient(
|
|
109
|
+
session_id=session_id,
|
|
110
|
+
logger=logger,
|
|
111
|
+
audit_logger=audit_logger,
|
|
112
|
+
app_id=app_uid
|
|
113
|
+
) as client:
|
|
114
|
+
await client.init_tools(init_request, event_id=event_id)
|
|
115
|
+
|
|
116
|
+
logger.info(f"Hooks registered successfully for {client_name}")
|
|
117
|
+
|
|
118
|
+
# Success - output result and exit
|
|
119
|
+
output_init_result(True, f"{client_name.title()} hooks registered successfully")
|
|
120
|
+
sys.exit(0)
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"API initialization failed: {e}")
|
|
124
|
+
output_init_result(False, f"Error: {str(e)}")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"Unexpected error in init handler: {e}", exc_info=True)
|
|
129
|
+
output_init_result(False, f"Initialization failed: {str(e)}")
|
|
130
|
+
sys.exit(1)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IDE-agnostic output handling
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from modules.logs.logger import MCPLogger
|
|
9
|
+
from .types import OutputFormat
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def output_result(
|
|
13
|
+
logger: MCPLogger,
|
|
14
|
+
output_format: OutputFormat,
|
|
15
|
+
hook_type: str,
|
|
16
|
+
allowed: bool,
|
|
17
|
+
user_message: Optional[str] = None,
|
|
18
|
+
agent_message: Optional[str] = None
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Output hook result in IDE-specific format and exit with appropriate code
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
logger: Logger instance
|
|
25
|
+
output_format: IDE-specific output configuration
|
|
26
|
+
hook_type: "permission" or "continue"
|
|
27
|
+
allowed: True for allow/continue, False for deny/block
|
|
28
|
+
user_message: Optional message for user
|
|
29
|
+
agent_message: Optional message for agent/logs
|
|
30
|
+
"""
|
|
31
|
+
# Format output using IDE-specific formatter
|
|
32
|
+
formatted_output = output_format.formatter(hook_type, allowed, user_message, agent_message)
|
|
33
|
+
|
|
34
|
+
logger.info(f"Hook output ({hook_type}, allowed={allowed}): {formatted_output}")
|
|
35
|
+
print(formatted_output, flush=True)
|
|
36
|
+
|
|
37
|
+
# Exit with appropriate code
|
|
38
|
+
exit_code = output_format.allow_exit_code if allowed else output_format.deny_exit_code
|
|
39
|
+
sys.exit(exit_code)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def output_error(
|
|
43
|
+
logger: MCPLogger,
|
|
44
|
+
output_format: OutputFormat,
|
|
45
|
+
hook_type: str,
|
|
46
|
+
error_message: str
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Output error and exit with error code
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
logger: Logger instance
|
|
53
|
+
output_format: IDE-specific output configuration
|
|
54
|
+
hook_type: "permission" or "continue"
|
|
55
|
+
error_message: Error message
|
|
56
|
+
"""
|
|
57
|
+
logger.error(f"Hook error: {error_message}")
|
|
58
|
+
|
|
59
|
+
# Output as deny/block with error message
|
|
60
|
+
formatted_output = output_format.formatter(hook_type, False, error_message, error_message)
|
|
61
|
+
print(formatted_output, flush=True)
|
|
62
|
+
|
|
63
|
+
sys.exit(output_format.error_exit_code)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared logic for UserPromptSubmit hook
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
from modules.logs.audit_trail import AuditTrailLogger
|
|
8
|
+
from modules.logs.logger import MCPLogger
|
|
9
|
+
from modules.redaction import redact
|
|
10
|
+
from modules.utils.ids import get_session_id, read_app_uid, get_project_mcpower_dir
|
|
11
|
+
from modules.utils.string import truncate_at
|
|
12
|
+
from .output import output_result, output_error
|
|
13
|
+
from .types import HookConfig
|
|
14
|
+
from .utils import create_validator, extract_redaction_patterns, process_attachments_for_redaction, inspect_and_enforce
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def handle_prompt_submit(
|
|
18
|
+
logger: MCPLogger,
|
|
19
|
+
audit_logger: AuditTrailLogger,
|
|
20
|
+
stdin_input: str,
|
|
21
|
+
prompt_id: str,
|
|
22
|
+
event_id: str,
|
|
23
|
+
cwd: Optional[str],
|
|
24
|
+
config: HookConfig,
|
|
25
|
+
tool_name: str
|
|
26
|
+
) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Shared handler for prompt submission hooks
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
logger: Logger instance
|
|
32
|
+
audit_logger: Audit logger instance
|
|
33
|
+
stdin_input: Raw JSON input
|
|
34
|
+
prompt_id: Prompt/conversation ID
|
|
35
|
+
event_id: Event/generation ID
|
|
36
|
+
cwd: Current working directory
|
|
37
|
+
config: IDE-specific hook configuration
|
|
38
|
+
tool_name: IDE-specific tool name (e.g., "beforeSubmitPrompt", "UserPromptSubmit")
|
|
39
|
+
"""
|
|
40
|
+
session_id = get_session_id()
|
|
41
|
+
logger.info(
|
|
42
|
+
f"Prompt submit handler started (client={config.client_name}, prompt_id={prompt_id}, "
|
|
43
|
+
f"event_id={event_id}, cwd={cwd})")
|
|
44
|
+
|
|
45
|
+
app_uid = read_app_uid(logger, get_project_mcpower_dir(cwd))
|
|
46
|
+
audit_logger.set_app_uid(app_uid)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
try:
|
|
50
|
+
validator = create_validator(
|
|
51
|
+
required_fields={"prompt": str},
|
|
52
|
+
optional_fields={"attachments": list}
|
|
53
|
+
)
|
|
54
|
+
input_data = validator(stdin_input)
|
|
55
|
+
prompt = input_data["prompt"]
|
|
56
|
+
attachments = input_data.get("attachments", [])
|
|
57
|
+
except ValueError as e:
|
|
58
|
+
logger.error(f"Input validation error: {e}")
|
|
59
|
+
output_error(logger, config.output_format, "continue", str(e))
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
redacted_prompt = redact(prompt)
|
|
63
|
+
|
|
64
|
+
audit_logger.log_event(
|
|
65
|
+
"prompt_submission",
|
|
66
|
+
{
|
|
67
|
+
"server": config.server_name,
|
|
68
|
+
"tool": tool_name,
|
|
69
|
+
"params": {"prompt": truncate_at(redacted_prompt, 100), "attachments_count": len(attachments)}
|
|
70
|
+
},
|
|
71
|
+
event_id=event_id,
|
|
72
|
+
prompt_id=prompt_id
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
prompt_patterns = extract_redaction_patterns(redacted_prompt)
|
|
76
|
+
|
|
77
|
+
# Check for redactions in file attachments
|
|
78
|
+
files_with_redactions = process_attachments_for_redaction(
|
|
79
|
+
attachments,
|
|
80
|
+
logger
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
has_any_redactions = bool(prompt_patterns) or len(files_with_redactions) > 0
|
|
84
|
+
|
|
85
|
+
content_data: Dict[str, Any] = {
|
|
86
|
+
"prompt": redacted_prompt,
|
|
87
|
+
"is_redacted": has_any_redactions,
|
|
88
|
+
"redacted_files": files_with_redactions,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Call security API and enforce decision
|
|
92
|
+
try:
|
|
93
|
+
decision = await inspect_and_enforce(
|
|
94
|
+
is_request=True,
|
|
95
|
+
session_id=session_id,
|
|
96
|
+
logger=logger,
|
|
97
|
+
audit_logger=audit_logger,
|
|
98
|
+
app_uid=app_uid,
|
|
99
|
+
event_id=event_id,
|
|
100
|
+
server_name=config.server_name,
|
|
101
|
+
tool_name=tool_name,
|
|
102
|
+
content_data=content_data,
|
|
103
|
+
prompt_id=prompt_id,
|
|
104
|
+
cwd=cwd,
|
|
105
|
+
client_name=config.client_name
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
audit_logger.log_event(
|
|
109
|
+
"prompt_submission_forwarded",
|
|
110
|
+
{
|
|
111
|
+
"server": config.server_name,
|
|
112
|
+
"tool": tool_name,
|
|
113
|
+
"params": {"redactions_found": has_any_redactions}
|
|
114
|
+
},
|
|
115
|
+
event_id=event_id,
|
|
116
|
+
prompt_id=prompt_id
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
reasons = decision.get("reasons", [])
|
|
120
|
+
agent_message = "Prompt submission approved: {0}".format("; ".join(
|
|
121
|
+
reasons)) if reasons else "Prompt submission approved by security policy"
|
|
122
|
+
output_result(logger, config.output_format, "continue", True,
|
|
123
|
+
"Prompt approved", agent_message)
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
# Decision enforcement failed - block
|
|
127
|
+
error_msg = str(e)
|
|
128
|
+
user_message = "Prompt blocked by security policy"
|
|
129
|
+
if "User blocked" in error_msg or "User denied" in error_msg:
|
|
130
|
+
user_message = "Prompt blocked by user"
|
|
131
|
+
|
|
132
|
+
output_result(logger, config.output_format, "continue", False, user_message, error_msg)
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Unexpected error in prompt submit handler: {e}", exc_info=True)
|
|
136
|
+
output_error(logger, config.output_format, "continue", f"Unexpected error: {str(e)}")
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared logic for beforeReadFile/PreReadFile hook
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from modules.logs.audit_trail import AuditTrailLogger
|
|
8
|
+
from modules.logs.logger import MCPLogger
|
|
9
|
+
from modules.redaction import redact
|
|
10
|
+
from modules.utils.ids import get_session_id, read_app_uid, get_project_mcpower_dir
|
|
11
|
+
from .output import output_result, output_error
|
|
12
|
+
from .types import HookConfig
|
|
13
|
+
from .utils import create_validator, process_attachments_for_redaction, inspect_and_enforce
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def handle_read_file(
|
|
17
|
+
logger: MCPLogger,
|
|
18
|
+
audit_logger: AuditTrailLogger,
|
|
19
|
+
stdin_input: str,
|
|
20
|
+
prompt_id: str,
|
|
21
|
+
event_id: str,
|
|
22
|
+
cwd: Optional[str],
|
|
23
|
+
config: HookConfig,
|
|
24
|
+
tool_name: str
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Shared handler for file read hooks
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
logger: Logger instance
|
|
31
|
+
audit_logger: Audit logger instance
|
|
32
|
+
stdin_input: Raw JSON input
|
|
33
|
+
prompt_id: Prompt/conversation ID
|
|
34
|
+
event_id: Event/generation ID
|
|
35
|
+
cwd: Current working directory
|
|
36
|
+
config: IDE-specific hook configuration
|
|
37
|
+
tool_name: IDE-specific tool name (e.g., "beforeReadFile", "PreToolUse")
|
|
38
|
+
"""
|
|
39
|
+
session_id = get_session_id()
|
|
40
|
+
logger.info(
|
|
41
|
+
f"Read file handler started (client={config.client_name}, prompt_id={prompt_id}, event_id={event_id}, cwd={cwd})")
|
|
42
|
+
|
|
43
|
+
app_uid = read_app_uid(logger, get_project_mcpower_dir(cwd))
|
|
44
|
+
audit_logger.set_app_uid(app_uid)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
try:
|
|
48
|
+
validator = create_validator(
|
|
49
|
+
required_fields={"file_path": str, "content": str},
|
|
50
|
+
optional_fields={"attachments": list}
|
|
51
|
+
)
|
|
52
|
+
input_data = validator(stdin_input)
|
|
53
|
+
file_path = input_data["file_path"]
|
|
54
|
+
provided_content = input_data["content"]
|
|
55
|
+
attachments = input_data.get("attachments", [])
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
logger.error(f"Input validation error: {e}")
|
|
58
|
+
output_error(logger, config.output_format, "permission", str(e))
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
audit_logger.log_event(
|
|
62
|
+
"agent_request",
|
|
63
|
+
{
|
|
64
|
+
"server": config.server_name,
|
|
65
|
+
"tool": tool_name,
|
|
66
|
+
"params": {"file_path": file_path, "attachments_count": len(attachments)}
|
|
67
|
+
},
|
|
68
|
+
event_id=event_id,
|
|
69
|
+
prompt_id=prompt_id
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Check content length - skip API if too large
|
|
73
|
+
if len(provided_content) > config.max_content_length:
|
|
74
|
+
logger.info(f"Content length ({len(provided_content)} chars) exceeds max ({config.max_content_length}) - "
|
|
75
|
+
f"skipping API call")
|
|
76
|
+
|
|
77
|
+
audit_logger.log_event(
|
|
78
|
+
"agent_request_forwarded",
|
|
79
|
+
{
|
|
80
|
+
"server": config.server_name,
|
|
81
|
+
"tool": tool_name,
|
|
82
|
+
"params": {
|
|
83
|
+
"file_path": file_path,
|
|
84
|
+
"content_length": len(provided_content),
|
|
85
|
+
"content_too_large": True
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
event_id=event_id,
|
|
89
|
+
prompt_id=prompt_id
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
output_result(logger, config.output_format, "permission", True)
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
# Redact the main content
|
|
96
|
+
redacted_content = redact(provided_content)
|
|
97
|
+
|
|
98
|
+
# Process attachments for redaction status
|
|
99
|
+
files_with_redactions = process_attachments_for_redaction(attachments, logger)
|
|
100
|
+
files_with_redactions_paths = {f["file_path"] for f in files_with_redactions}
|
|
101
|
+
|
|
102
|
+
# Build attachments info with redaction status
|
|
103
|
+
attachments_info = []
|
|
104
|
+
for attachment in attachments:
|
|
105
|
+
att_path = attachment.get("file_path") or attachment.get("filePath")
|
|
106
|
+
if att_path:
|
|
107
|
+
attachments_info.append({
|
|
108
|
+
"file_path": att_path,
|
|
109
|
+
"has_redactions": att_path in files_with_redactions_paths
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
logger.info(f"Processed file and {len(attachments)} attachment(s), found redactions in "
|
|
113
|
+
f"{len(files_with_redactions)} attachment(s)")
|
|
114
|
+
|
|
115
|
+
# Build content_data with file_path, redacted content, and attachments
|
|
116
|
+
content_data = {
|
|
117
|
+
"file_path": file_path,
|
|
118
|
+
"content": redacted_content,
|
|
119
|
+
"attachments": attachments_info
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Call security API and enforce decision
|
|
123
|
+
try:
|
|
124
|
+
decision = await inspect_and_enforce(
|
|
125
|
+
is_request=True,
|
|
126
|
+
session_id=session_id,
|
|
127
|
+
logger=logger,
|
|
128
|
+
audit_logger=audit_logger,
|
|
129
|
+
app_uid=app_uid,
|
|
130
|
+
event_id=event_id,
|
|
131
|
+
server_name=config.server_name,
|
|
132
|
+
tool_name=tool_name,
|
|
133
|
+
content_data=content_data,
|
|
134
|
+
prompt_id=prompt_id,
|
|
135
|
+
cwd=cwd,
|
|
136
|
+
current_files=[file_path],
|
|
137
|
+
client_name=config.client_name
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
audit_logger.log_event(
|
|
141
|
+
"agent_request_forwarded",
|
|
142
|
+
{
|
|
143
|
+
"server": config.server_name,
|
|
144
|
+
"tool": tool_name,
|
|
145
|
+
"params": {
|
|
146
|
+
"file_path": file_path,
|
|
147
|
+
"content_length": len(provided_content),
|
|
148
|
+
"attachments_with_redactions": len(files_with_redactions)}
|
|
149
|
+
},
|
|
150
|
+
event_id=event_id,
|
|
151
|
+
prompt_id=prompt_id
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
reasons = decision.get("reasons", [])
|
|
155
|
+
agent_message = "File read approved: " + "; ".join(
|
|
156
|
+
reasons) if reasons else "File read approved by security policy"
|
|
157
|
+
output_result(logger, config.output_format, "permission", True, "File read approved", agent_message)
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
# Decision enforcement failed - block
|
|
161
|
+
error_msg = str(e)
|
|
162
|
+
user_message = "File read blocked by security policy"
|
|
163
|
+
if "User blocked" in error_msg or "User denied" in error_msg:
|
|
164
|
+
user_message = "File read blocked by user"
|
|
165
|
+
|
|
166
|
+
output_result(logger, config.output_format, "permission", False, user_message, error_msg)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(f"Unexpected error in read file handler: {e}", exc_info=True)
|
|
170
|
+
output_error(logger, config.output_format, "permission", f"Unexpected error: {str(e)}")
|