amd-gaia 0.15.0__py3-none-any.whl → 0.15.1__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.
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +835 -835
- gaia/agents/chat/app.py +1058 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2036 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1974 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +173 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5632
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/llm/__init__.py +9 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3236 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +120 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +163 -163
- gaia/talk/app.py +289 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.15.0.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -723
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
gaia/security.py
CHANGED
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""
|
|
4
|
-
Security utilities for GAIA.
|
|
5
|
-
Handles path validation, user prompting, and persistent allow-lists.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import logging
|
|
10
|
-
import os
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import List, Optional, Set
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class PathValidator:
|
|
18
|
-
"""
|
|
19
|
-
Validates file paths against an allowed list, with user prompting for exceptions.
|
|
20
|
-
Persists allowed paths to ~/.gaia/cache/allowed_paths.json.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
def __init__(self, allowed_paths: Optional[List[str]] = None):
|
|
24
|
-
"""
|
|
25
|
-
Initialize PathValidator.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
allowed_paths: Initial list of allowed paths. Defaults to [CWD].
|
|
29
|
-
"""
|
|
30
|
-
self.allowed_paths: Set[Path] = set()
|
|
31
|
-
|
|
32
|
-
# Add default allowed paths
|
|
33
|
-
if allowed_paths:
|
|
34
|
-
for p in allowed_paths:
|
|
35
|
-
self.allowed_paths.add(Path(p).resolve())
|
|
36
|
-
else:
|
|
37
|
-
self.allowed_paths.add(Path.cwd().resolve())
|
|
38
|
-
|
|
39
|
-
# Setup cache directory
|
|
40
|
-
self.cache_dir = Path.home() / ".gaia" / "cache"
|
|
41
|
-
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
-
self.config_file = self.cache_dir / "allowed_paths.json"
|
|
43
|
-
|
|
44
|
-
# Load persisted paths
|
|
45
|
-
self._load_persisted_paths()
|
|
46
|
-
|
|
47
|
-
def _load_persisted_paths(self):
|
|
48
|
-
"""Load allowed paths from cache file."""
|
|
49
|
-
if self.config_file.exists():
|
|
50
|
-
try:
|
|
51
|
-
with open(self.config_file, "r", encoding="utf-8") as f:
|
|
52
|
-
data = json.load(f)
|
|
53
|
-
for p in data.get("paths", []):
|
|
54
|
-
try:
|
|
55
|
-
path_obj = Path(p).resolve()
|
|
56
|
-
if path_obj.exists():
|
|
57
|
-
self.allowed_paths.add(path_obj)
|
|
58
|
-
except Exception as e:
|
|
59
|
-
logger.warning(f"Invalid path in cache {p}: {e}")
|
|
60
|
-
except Exception as e:
|
|
61
|
-
logger.error(
|
|
62
|
-
f"Failed to load allowed paths from {self.config_file}: {e}"
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def _save_persisted_path(self, path: Path):
|
|
66
|
-
"""Save a new allowed path to cache file."""
|
|
67
|
-
try:
|
|
68
|
-
data = {"paths": []}
|
|
69
|
-
if self.config_file.exists():
|
|
70
|
-
try:
|
|
71
|
-
with open(self.config_file, "r", encoding="utf-8") as f:
|
|
72
|
-
data = json.load(f)
|
|
73
|
-
except Exception:
|
|
74
|
-
pass # Start fresh if corrupt
|
|
75
|
-
|
|
76
|
-
str_path = str(path)
|
|
77
|
-
if str_path not in data["paths"]:
|
|
78
|
-
data["paths"].append(str_path)
|
|
79
|
-
|
|
80
|
-
with open(self.config_file, "w", encoding="utf-8") as f:
|
|
81
|
-
json.dump(data, f, indent=2)
|
|
82
|
-
|
|
83
|
-
logger.info(f"Persisted new allowed path: {path}")
|
|
84
|
-
except Exception as e:
|
|
85
|
-
logger.error(f"Failed to save allowed path to {self.config_file}: {e}")
|
|
86
|
-
|
|
87
|
-
def add_allowed_path(self, path: str) -> None:
|
|
88
|
-
"""
|
|
89
|
-
Add a path to the allowed paths set.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
path: Path to add to allowed paths
|
|
93
|
-
"""
|
|
94
|
-
self.allowed_paths.add(Path(path).resolve())
|
|
95
|
-
logger.debug(f"Added allowed path: {path}")
|
|
96
|
-
|
|
97
|
-
def is_path_allowed(self, path: str, prompt_user: bool = True) -> bool:
|
|
98
|
-
"""
|
|
99
|
-
Check if a path is allowed. If not, optionally prompt the user.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
path: Path to check
|
|
103
|
-
prompt_user: Whether to ask user for permission if path is not allowed
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
True if allowed, False otherwise
|
|
107
|
-
"""
|
|
108
|
-
try:
|
|
109
|
-
# Resolve path using os.path.realpath to follow symlinks
|
|
110
|
-
# This prevents TOCTOU attacks by resolving at check time
|
|
111
|
-
real_path = Path(os.path.realpath(path)).resolve()
|
|
112
|
-
|
|
113
|
-
# Check if real path is within any allowed directory
|
|
114
|
-
for allowed_path in self.allowed_paths:
|
|
115
|
-
try:
|
|
116
|
-
# is_relative_to requires Python 3.9+, use alternative for compatibility
|
|
117
|
-
real_path.relative_to(allowed_path)
|
|
118
|
-
return True
|
|
119
|
-
except ValueError:
|
|
120
|
-
continue
|
|
121
|
-
|
|
122
|
-
# If we get here, path is not allowed. Prompt user?
|
|
123
|
-
if prompt_user:
|
|
124
|
-
return self._prompt_user_for_access(real_path)
|
|
125
|
-
|
|
126
|
-
return False
|
|
127
|
-
|
|
128
|
-
except Exception as e:
|
|
129
|
-
logger.error(f"Error validating path {path}: {e}")
|
|
130
|
-
return False
|
|
131
|
-
|
|
132
|
-
def _prompt_user_for_access(self, path: Path) -> bool:
|
|
133
|
-
"""Prompt user to allow access to a path."""
|
|
134
|
-
print(
|
|
135
|
-
"\n⚠️ SECURITY WARNING: Agent is attempting to access a path outside allowed directories."
|
|
136
|
-
)
|
|
137
|
-
print(f" Path: {path}")
|
|
138
|
-
print(f" Allowed: {[str(p) for p in self.allowed_paths]}")
|
|
139
|
-
|
|
140
|
-
while True:
|
|
141
|
-
response = (
|
|
142
|
-
input("Allow this access? [y]es / [n]o / [a]lways: ").lower().strip()
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
if response in ["y", "yes"]:
|
|
146
|
-
# Allow for this session only (add to memory but don't persist)
|
|
147
|
-
# We add the specific file or directory to allowed paths
|
|
148
|
-
self.allowed_paths.add(path)
|
|
149
|
-
logger.info(f"User temporarily allowed access to: {path}")
|
|
150
|
-
return True
|
|
151
|
-
|
|
152
|
-
elif response in ["a", "always"]:
|
|
153
|
-
# Allow and persist
|
|
154
|
-
self.allowed_paths.add(path)
|
|
155
|
-
self._save_persisted_path(path)
|
|
156
|
-
logger.info(f"User permanently allowed access to: {path}")
|
|
157
|
-
return True
|
|
158
|
-
|
|
159
|
-
elif response in ["n", "no"]:
|
|
160
|
-
logger.warning(f"User denied access to: {path}")
|
|
161
|
-
return False
|
|
162
|
-
|
|
163
|
-
print("Please answer 'y', 'n', or 'a'.")
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
Security utilities for GAIA.
|
|
5
|
+
Handles path validation, user prompting, and persistent allow-lists.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional, Set
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PathValidator:
|
|
18
|
+
"""
|
|
19
|
+
Validates file paths against an allowed list, with user prompting for exceptions.
|
|
20
|
+
Persists allowed paths to ~/.gaia/cache/allowed_paths.json.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, allowed_paths: Optional[List[str]] = None):
|
|
24
|
+
"""
|
|
25
|
+
Initialize PathValidator.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
allowed_paths: Initial list of allowed paths. Defaults to [CWD].
|
|
29
|
+
"""
|
|
30
|
+
self.allowed_paths: Set[Path] = set()
|
|
31
|
+
|
|
32
|
+
# Add default allowed paths
|
|
33
|
+
if allowed_paths:
|
|
34
|
+
for p in allowed_paths:
|
|
35
|
+
self.allowed_paths.add(Path(p).resolve())
|
|
36
|
+
else:
|
|
37
|
+
self.allowed_paths.add(Path.cwd().resolve())
|
|
38
|
+
|
|
39
|
+
# Setup cache directory
|
|
40
|
+
self.cache_dir = Path.home() / ".gaia" / "cache"
|
|
41
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
self.config_file = self.cache_dir / "allowed_paths.json"
|
|
43
|
+
|
|
44
|
+
# Load persisted paths
|
|
45
|
+
self._load_persisted_paths()
|
|
46
|
+
|
|
47
|
+
def _load_persisted_paths(self):
|
|
48
|
+
"""Load allowed paths from cache file."""
|
|
49
|
+
if self.config_file.exists():
|
|
50
|
+
try:
|
|
51
|
+
with open(self.config_file, "r", encoding="utf-8") as f:
|
|
52
|
+
data = json.load(f)
|
|
53
|
+
for p in data.get("paths", []):
|
|
54
|
+
try:
|
|
55
|
+
path_obj = Path(p).resolve()
|
|
56
|
+
if path_obj.exists():
|
|
57
|
+
self.allowed_paths.add(path_obj)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.warning(f"Invalid path in cache {p}: {e}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error(
|
|
62
|
+
f"Failed to load allowed paths from {self.config_file}: {e}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _save_persisted_path(self, path: Path):
|
|
66
|
+
"""Save a new allowed path to cache file."""
|
|
67
|
+
try:
|
|
68
|
+
data = {"paths": []}
|
|
69
|
+
if self.config_file.exists():
|
|
70
|
+
try:
|
|
71
|
+
with open(self.config_file, "r", encoding="utf-8") as f:
|
|
72
|
+
data = json.load(f)
|
|
73
|
+
except Exception:
|
|
74
|
+
pass # Start fresh if corrupt
|
|
75
|
+
|
|
76
|
+
str_path = str(path)
|
|
77
|
+
if str_path not in data["paths"]:
|
|
78
|
+
data["paths"].append(str_path)
|
|
79
|
+
|
|
80
|
+
with open(self.config_file, "w", encoding="utf-8") as f:
|
|
81
|
+
json.dump(data, f, indent=2)
|
|
82
|
+
|
|
83
|
+
logger.info(f"Persisted new allowed path: {path}")
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"Failed to save allowed path to {self.config_file}: {e}")
|
|
86
|
+
|
|
87
|
+
def add_allowed_path(self, path: str) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Add a path to the allowed paths set.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
path: Path to add to allowed paths
|
|
93
|
+
"""
|
|
94
|
+
self.allowed_paths.add(Path(path).resolve())
|
|
95
|
+
logger.debug(f"Added allowed path: {path}")
|
|
96
|
+
|
|
97
|
+
def is_path_allowed(self, path: str, prompt_user: bool = True) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Check if a path is allowed. If not, optionally prompt the user.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
path: Path to check
|
|
103
|
+
prompt_user: Whether to ask user for permission if path is not allowed
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if allowed, False otherwise
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
# Resolve path using os.path.realpath to follow symlinks
|
|
110
|
+
# This prevents TOCTOU attacks by resolving at check time
|
|
111
|
+
real_path = Path(os.path.realpath(path)).resolve()
|
|
112
|
+
|
|
113
|
+
# Check if real path is within any allowed directory
|
|
114
|
+
for allowed_path in self.allowed_paths:
|
|
115
|
+
try:
|
|
116
|
+
# is_relative_to requires Python 3.9+, use alternative for compatibility
|
|
117
|
+
real_path.relative_to(allowed_path)
|
|
118
|
+
return True
|
|
119
|
+
except ValueError:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# If we get here, path is not allowed. Prompt user?
|
|
123
|
+
if prompt_user:
|
|
124
|
+
return self._prompt_user_for_access(real_path)
|
|
125
|
+
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Error validating path {path}: {e}")
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def _prompt_user_for_access(self, path: Path) -> bool:
|
|
133
|
+
"""Prompt user to allow access to a path."""
|
|
134
|
+
print(
|
|
135
|
+
"\n⚠️ SECURITY WARNING: Agent is attempting to access a path outside allowed directories."
|
|
136
|
+
)
|
|
137
|
+
print(f" Path: {path}")
|
|
138
|
+
print(f" Allowed: {[str(p) for p in self.allowed_paths]}")
|
|
139
|
+
|
|
140
|
+
while True:
|
|
141
|
+
response = (
|
|
142
|
+
input("Allow this access? [y]es / [n]o / [a]lways: ").lower().strip()
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if response in ["y", "yes"]:
|
|
146
|
+
# Allow for this session only (add to memory but don't persist)
|
|
147
|
+
# We add the specific file or directory to allowed paths
|
|
148
|
+
self.allowed_paths.add(path)
|
|
149
|
+
logger.info(f"User temporarily allowed access to: {path}")
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
elif response in ["a", "always"]:
|
|
153
|
+
# Allow and persist
|
|
154
|
+
self.allowed_paths.add(path)
|
|
155
|
+
self._save_persisted_path(path)
|
|
156
|
+
logger.info(f"User permanently allowed access to: {path}")
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
elif response in ["n", "no"]:
|
|
160
|
+
logger.warning(f"User denied access to: {path}")
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
print("Please answer 'y', 'n', or 'a'.")
|