open-edison 0.1.10__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.
- open_edison-0.1.10.dist-info/METADATA +332 -0
- open_edison-0.1.10.dist-info/RECORD +17 -0
- open_edison-0.1.10.dist-info/WHEEL +4 -0
- open_edison-0.1.10.dist-info/entry_points.txt +3 -0
- open_edison-0.1.10.dist-info/licenses/LICENSE +674 -0
- src/__init__.py +11 -0
- src/__main__.py +10 -0
- src/cli.py +274 -0
- src/config.py +224 -0
- src/frontend_dist/assets/index-CKkid2y-.js +51 -0
- src/frontend_dist/assets/index-CRxojymD.css +1 -0
- src/frontend_dist/index.html +21 -0
- src/mcp_manager.py +137 -0
- src/middleware/data_access_tracker.py +510 -0
- src/middleware/session_tracking.py +477 -0
- src/server.py +560 -0
- src/single_user_mcp.py +403 -0
@@ -0,0 +1,510 @@
|
|
1
|
+
"""
|
2
|
+
Data Access Tracker for Open Edison
|
3
|
+
|
4
|
+
This module defines the DataAccessTracker class that monitors the "lethal trifecta"
|
5
|
+
of security risks for AI agents: access to private data, exposure to untrusted content,
|
6
|
+
and ability to externally communicate.
|
7
|
+
|
8
|
+
Permissions are loaded from external JSON configuration files that map
|
9
|
+
names (with server-name/path prefixes) to their security classifications:
|
10
|
+
- tool_permissions.json: Tool security classifications
|
11
|
+
- resource_permissions.json: Resource access security classifications
|
12
|
+
- prompt_permissions.json: Prompt security classifications
|
13
|
+
"""
|
14
|
+
|
15
|
+
import json
|
16
|
+
from dataclasses import dataclass
|
17
|
+
from functools import lru_cache
|
18
|
+
from pathlib import Path
|
19
|
+
from typing import Any
|
20
|
+
|
21
|
+
from loguru import logger as log
|
22
|
+
|
23
|
+
from src.config import ConfigError
|
24
|
+
|
25
|
+
|
26
|
+
def _flat_permissions_loader(config_path: Path) -> dict[str, dict[str, bool]]:
|
27
|
+
if config_path.exists():
|
28
|
+
with open(config_path) as f:
|
29
|
+
data: dict[str, Any] = json.load(f)
|
30
|
+
|
31
|
+
# Handle new format: server -> {tool -> permissions}
|
32
|
+
# Convert to flat tool -> permissions format
|
33
|
+
flat_permissions: dict[str, dict[str, bool]] = {}
|
34
|
+
tool_to_server: dict[str, str] = {}
|
35
|
+
server_tools: dict[str, set[str]] = {}
|
36
|
+
|
37
|
+
for server_name, server_data in data.items():
|
38
|
+
if not isinstance(server_data, dict):
|
39
|
+
log.warning(
|
40
|
+
f"Invalid server data for {server_name}: expected dict, got {type(server_data)}"
|
41
|
+
)
|
42
|
+
continue
|
43
|
+
|
44
|
+
if server_name == "_metadata":
|
45
|
+
flat_permissions["_metadata"] = server_data
|
46
|
+
continue
|
47
|
+
|
48
|
+
server_tools[server_name] = set()
|
49
|
+
|
50
|
+
for tool_name, tool_permissions in server_data.items(): # type: ignore
|
51
|
+
if not isinstance(tool_permissions, dict):
|
52
|
+
log.warning(
|
53
|
+
f"Invalid tool permissions for {server_name}/{tool_name}: expected dict, got {type(tool_permissions)}" # type: ignore
|
54
|
+
) # type: ignore
|
55
|
+
continue
|
56
|
+
|
57
|
+
# Check for duplicates within the same server
|
58
|
+
if tool_name in server_tools[server_name]:
|
59
|
+
log.error(f"Duplicate tool '{tool_name}' found in server '{server_name}'")
|
60
|
+
raise ConfigError(
|
61
|
+
f"Duplicate tool '{tool_name}' found in server '{server_name}'"
|
62
|
+
)
|
63
|
+
|
64
|
+
# Check for duplicates across different servers
|
65
|
+
if tool_name in tool_to_server:
|
66
|
+
existing_server = tool_to_server[tool_name]
|
67
|
+
log.error(
|
68
|
+
f"Duplicate tool '{tool_name}' found in servers '{existing_server}' and '{server_name}'"
|
69
|
+
)
|
70
|
+
raise ConfigError(
|
71
|
+
f"Duplicate tool '{tool_name}' found in servers '{existing_server}' and '{server_name}'"
|
72
|
+
)
|
73
|
+
|
74
|
+
# Add to tracking maps
|
75
|
+
tool_to_server[tool_name] = server_name
|
76
|
+
server_tools[server_name].add(tool_name) # type: ignore
|
77
|
+
|
78
|
+
# Convert to flat format with explicit type casting
|
79
|
+
tool_perms_dict: dict[str, Any] = tool_permissions # type: ignore
|
80
|
+
flat_permissions[tool_name] = {
|
81
|
+
"enabled": bool(tool_perms_dict.get("enabled", True)),
|
82
|
+
"write_operation": bool(tool_perms_dict.get("write_operation", False)),
|
83
|
+
"read_private_data": bool(tool_perms_dict.get("read_private_data", False)),
|
84
|
+
"read_untrusted_public_data": bool(
|
85
|
+
tool_perms_dict.get("read_untrusted_public_data", False)
|
86
|
+
),
|
87
|
+
}
|
88
|
+
|
89
|
+
log.debug(
|
90
|
+
f"Loaded {len(flat_permissions)} tool permissions from {len(server_tools)} servers in {config_path}"
|
91
|
+
)
|
92
|
+
# Convert sets to lists for JSON serialization
|
93
|
+
server_tools_serializable = {
|
94
|
+
server: list(tools) for server, tools in server_tools.items()
|
95
|
+
}
|
96
|
+
log.debug(f"Server tools: {json.dumps(server_tools_serializable, indent=2)}")
|
97
|
+
return flat_permissions
|
98
|
+
else:
|
99
|
+
log.warning(f"Tool permissions file not found at {config_path}")
|
100
|
+
return {}
|
101
|
+
|
102
|
+
|
103
|
+
@lru_cache(maxsize=1)
|
104
|
+
def _load_tool_permissions_cached() -> dict[str, dict[str, bool]]:
|
105
|
+
"""Load tool permissions from JSON configuration file with LRU caching."""
|
106
|
+
config_path = Path(__file__).parent.parent.parent / "tool_permissions.json"
|
107
|
+
|
108
|
+
try:
|
109
|
+
return _flat_permissions_loader(config_path)
|
110
|
+
except ConfigError as e:
|
111
|
+
log.error(f"Failed to load tool permissions from {config_path}: {e}")
|
112
|
+
raise e
|
113
|
+
except Exception as e:
|
114
|
+
log.error(f"Failed to load tool permissions from {config_path}: {e}")
|
115
|
+
return {}
|
116
|
+
|
117
|
+
|
118
|
+
@lru_cache(maxsize=1)
|
119
|
+
def _load_resource_permissions_cached() -> dict[str, dict[str, bool]]:
|
120
|
+
"""Load resource permissions from JSON configuration file with LRU caching."""
|
121
|
+
config_path = Path(__file__).parent.parent.parent / "resource_permissions.json"
|
122
|
+
|
123
|
+
try:
|
124
|
+
return _flat_permissions_loader(config_path)
|
125
|
+
except ConfigError as e:
|
126
|
+
log.error(f"Failed to load resource permissions from {config_path}: {e}")
|
127
|
+
raise e
|
128
|
+
except Exception as e:
|
129
|
+
log.error(f"Failed to load resource permissions from {config_path}: {e}")
|
130
|
+
return {}
|
131
|
+
|
132
|
+
|
133
|
+
@lru_cache(maxsize=1)
|
134
|
+
def _load_prompt_permissions_cached() -> dict[str, dict[str, bool]]:
|
135
|
+
"""Load prompt permissions from JSON configuration file with LRU caching."""
|
136
|
+
config_path = Path(__file__).parent.parent.parent / "prompt_permissions.json"
|
137
|
+
|
138
|
+
try:
|
139
|
+
return _flat_permissions_loader(config_path)
|
140
|
+
except ConfigError as e:
|
141
|
+
log.error(f"Failed to load prompt permissions from {config_path}: {e}")
|
142
|
+
raise e
|
143
|
+
except Exception as e:
|
144
|
+
log.error(f"Failed to load prompt permissions from {config_path}: {e}")
|
145
|
+
return {}
|
146
|
+
|
147
|
+
|
148
|
+
@lru_cache(maxsize=128)
|
149
|
+
def _classify_tool_permissions_cached(tool_name: str) -> dict[str, bool]:
|
150
|
+
"""Classify tool permissions with LRU caching."""
|
151
|
+
return _classify_permissions_cached(tool_name, _load_tool_permissions_cached(), "tool")
|
152
|
+
|
153
|
+
|
154
|
+
@lru_cache(maxsize=128)
|
155
|
+
def _classify_resource_permissions_cached(resource_name: str) -> dict[str, bool]:
|
156
|
+
"""Classify resource permissions with LRU caching."""
|
157
|
+
return _classify_permissions_cached(
|
158
|
+
resource_name, _load_resource_permissions_cached(), "resource"
|
159
|
+
)
|
160
|
+
|
161
|
+
|
162
|
+
@lru_cache(maxsize=128)
|
163
|
+
def _classify_prompt_permissions_cached(prompt_name: str) -> dict[str, bool]:
|
164
|
+
"""Classify prompt permissions with LRU caching."""
|
165
|
+
return _classify_permissions_cached(prompt_name, _load_prompt_permissions_cached(), "prompt")
|
166
|
+
|
167
|
+
|
168
|
+
def _get_builtin_tool_permissions(name: str) -> dict[str, bool] | None:
|
169
|
+
"""Get permissions for built-in safe tools."""
|
170
|
+
builtin_safe_tools = ["echo", "get_server_info", "get_security_status"]
|
171
|
+
if name in builtin_safe_tools:
|
172
|
+
permissions = {
|
173
|
+
"enabled": True,
|
174
|
+
"write_operation": False,
|
175
|
+
"read_private_data": False,
|
176
|
+
"read_untrusted_public_data": False,
|
177
|
+
}
|
178
|
+
log.debug(f"Built-in safe tool {name}: {permissions}")
|
179
|
+
return permissions
|
180
|
+
return None
|
181
|
+
|
182
|
+
|
183
|
+
def _get_exact_match_permissions(
|
184
|
+
name: str, permissions_config: dict[str, dict[str, bool]], type_name: str
|
185
|
+
) -> dict[str, bool] | None:
|
186
|
+
"""Check for exact match permissions."""
|
187
|
+
if name in permissions_config and not name.startswith("_"):
|
188
|
+
config_perms = permissions_config[name]
|
189
|
+
permissions = {
|
190
|
+
"enabled": config_perms.get("enabled", False),
|
191
|
+
"write_operation": config_perms.get("write_operation", False),
|
192
|
+
"read_private_data": config_perms.get("read_private_data", False),
|
193
|
+
"read_untrusted_public_data": config_perms.get("read_untrusted_public_data", False),
|
194
|
+
}
|
195
|
+
log.debug(f"Found exact match for {type_name} {name}: {permissions}")
|
196
|
+
return permissions
|
197
|
+
return None
|
198
|
+
|
199
|
+
|
200
|
+
def _get_wildcard_patterns(name: str, type_name: str) -> list[str]:
|
201
|
+
"""Generate wildcard patterns based on name and type."""
|
202
|
+
wildcard_patterns: list[str] = []
|
203
|
+
|
204
|
+
if type_name == "tool" and "/" in name:
|
205
|
+
# For tools: server_name/*
|
206
|
+
server_name, _ = name.split("/", 1)
|
207
|
+
wildcard_patterns.append(f"{server_name}/*")
|
208
|
+
elif type_name == "resource":
|
209
|
+
# For resources: scheme:*, just like tools do server_name/*
|
210
|
+
if ":" in name:
|
211
|
+
scheme, _ = name.split(":", 1)
|
212
|
+
wildcard_patterns.append(f"{scheme}:*")
|
213
|
+
elif type_name == "prompt":
|
214
|
+
# For prompts: template:*, prompt:file:*, etc.
|
215
|
+
if ":" in name:
|
216
|
+
parts = name.split(":")
|
217
|
+
if len(parts) >= 2:
|
218
|
+
wildcard_patterns.append(f"{parts[0]}:*")
|
219
|
+
# For nested patterns like prompt:file:*, check prompt:file:*
|
220
|
+
if len(parts) >= 3:
|
221
|
+
wildcard_patterns.append(f"{parts[0]}:{parts[1]}:*")
|
222
|
+
|
223
|
+
return wildcard_patterns
|
224
|
+
|
225
|
+
|
226
|
+
def _classify_permissions_cached(
|
227
|
+
name: str, permissions_config: dict[str, dict[str, bool]], type_name: str
|
228
|
+
) -> dict[str, bool]:
|
229
|
+
"""Generic permission classification with pattern matching support."""
|
230
|
+
# Built-in safe tools that don't need external config (only for tools)
|
231
|
+
if type_name == "tool":
|
232
|
+
builtin_perms = _get_builtin_tool_permissions(name)
|
233
|
+
if builtin_perms is not None:
|
234
|
+
return builtin_perms
|
235
|
+
|
236
|
+
# Check for exact match first
|
237
|
+
exact_perms = _get_exact_match_permissions(name, permissions_config, type_name)
|
238
|
+
if exact_perms is not None:
|
239
|
+
return exact_perms
|
240
|
+
|
241
|
+
# Try wildcard patterns
|
242
|
+
wildcard_patterns = _get_wildcard_patterns(name, type_name)
|
243
|
+
for pattern in wildcard_patterns:
|
244
|
+
if pattern in permissions_config:
|
245
|
+
config_perms = permissions_config[pattern]
|
246
|
+
permissions = {
|
247
|
+
"enabled": config_perms.get("enabled", False),
|
248
|
+
"write_operation": config_perms.get("write_operation", False),
|
249
|
+
"read_private_data": config_perms.get("read_private_data", False),
|
250
|
+
"read_untrusted_public_data": config_perms.get("read_untrusted_public_data", False),
|
251
|
+
}
|
252
|
+
log.debug(f"Found wildcard match for {type_name} {name} using {pattern}: {permissions}")
|
253
|
+
return permissions
|
254
|
+
|
255
|
+
# No configuration found - raise error instead of defaulting to safe
|
256
|
+
config_file = f"{type_name}_permissions.json"
|
257
|
+
log.error(
|
258
|
+
f"No security configuration found for {type_name} '{name}'. All {type_name}s must be explicitly configured in {config_file}"
|
259
|
+
)
|
260
|
+
raise ValueError(
|
261
|
+
f"No security configuration found for {type_name} '{name}'. All {type_name}s must be explicitly configured in {config_file}"
|
262
|
+
)
|
263
|
+
|
264
|
+
|
265
|
+
@dataclass
|
266
|
+
class DataAccessTracker:
|
267
|
+
"""
|
268
|
+
Tracks the "lethal trifecta" of security risks for AI agents.
|
269
|
+
|
270
|
+
The lethal trifecta consists of:
|
271
|
+
1. Access to private data (read_private_data)
|
272
|
+
2. Exposure to untrusted content (read_untrusted_public_data)
|
273
|
+
3. Ability to externally communicate (write_operation)
|
274
|
+
"""
|
275
|
+
|
276
|
+
# Lethal trifecta flags
|
277
|
+
has_private_data_access: bool = False
|
278
|
+
has_untrusted_content_exposure: bool = False
|
279
|
+
has_external_communication: bool = False
|
280
|
+
|
281
|
+
def is_trifecta_achieved(self) -> bool:
|
282
|
+
"""Check if the lethal trifecta has been achieved."""
|
283
|
+
return (
|
284
|
+
self.has_private_data_access
|
285
|
+
and self.has_untrusted_content_exposure
|
286
|
+
and self.has_external_communication
|
287
|
+
)
|
288
|
+
|
289
|
+
def _load_tool_permissions(self) -> dict[str, dict[str, bool]]:
|
290
|
+
"""Load tool permissions from JSON configuration file with caching."""
|
291
|
+
return _load_tool_permissions_cached()
|
292
|
+
|
293
|
+
def _load_resource_permissions(self) -> dict[str, dict[str, bool]]:
|
294
|
+
"""Load resource permissions from JSON configuration file with caching."""
|
295
|
+
return _load_resource_permissions_cached()
|
296
|
+
|
297
|
+
def _load_prompt_permissions(self) -> dict[str, dict[str, bool]]:
|
298
|
+
"""Load prompt permissions from JSON configuration file with caching."""
|
299
|
+
return _load_prompt_permissions_cached()
|
300
|
+
|
301
|
+
def _classify_by_tool_name(self, tool_name: str) -> dict[str, bool]:
|
302
|
+
"""Classify permissions based on external JSON configuration only."""
|
303
|
+
return _classify_tool_permissions_cached(tool_name)
|
304
|
+
|
305
|
+
def _classify_by_resource_name(self, resource_name: str) -> dict[str, bool]:
|
306
|
+
"""Classify resource permissions based on external JSON configuration only."""
|
307
|
+
return _classify_resource_permissions_cached(resource_name)
|
308
|
+
|
309
|
+
def _classify_by_prompt_name(self, prompt_name: str) -> dict[str, bool]:
|
310
|
+
"""Classify prompt permissions based on external JSON configuration only."""
|
311
|
+
return _classify_prompt_permissions_cached(prompt_name)
|
312
|
+
|
313
|
+
def _classify_tool_permissions(self, tool_name: str) -> dict[str, bool]:
|
314
|
+
"""
|
315
|
+
Classify tool permissions based on tool name.
|
316
|
+
|
317
|
+
Args:
|
318
|
+
tool_name: Name of the tool to classify
|
319
|
+
Returns:
|
320
|
+
Dictionary with permission flags
|
321
|
+
"""
|
322
|
+
permissions = self._classify_by_tool_name(tool_name)
|
323
|
+
log.debug(f"Classified tool {tool_name}: {permissions}")
|
324
|
+
return permissions
|
325
|
+
|
326
|
+
def _classify_resource_permissions(self, resource_name: str) -> dict[str, bool]:
|
327
|
+
"""
|
328
|
+
Classify resource permissions based on resource name.
|
329
|
+
|
330
|
+
Args:
|
331
|
+
resource_name: Name/URI of the resource to classify
|
332
|
+
Returns:
|
333
|
+
Dictionary with permission flags
|
334
|
+
"""
|
335
|
+
permissions = self._classify_by_resource_name(resource_name)
|
336
|
+
log.debug(f"Classified resource {resource_name}: {permissions}")
|
337
|
+
return permissions
|
338
|
+
|
339
|
+
def _classify_prompt_permissions(self, prompt_name: str) -> dict[str, bool]:
|
340
|
+
"""
|
341
|
+
Classify prompt permissions based on prompt name.
|
342
|
+
|
343
|
+
Args:
|
344
|
+
prompt_name: Name/type of the prompt to classify
|
345
|
+
Returns:
|
346
|
+
Dictionary with permission flags
|
347
|
+
"""
|
348
|
+
permissions = self._classify_by_prompt_name(prompt_name)
|
349
|
+
log.debug(f"Classified prompt {prompt_name}: {permissions}")
|
350
|
+
return permissions
|
351
|
+
|
352
|
+
def get_tool_permissions(self, tool_name: str) -> dict[str, bool]:
|
353
|
+
"""Get tool permissions based on tool name."""
|
354
|
+
return self._classify_tool_permissions(tool_name)
|
355
|
+
|
356
|
+
def get_resource_permissions(self, resource_name: str) -> dict[str, bool]:
|
357
|
+
"""Get resource permissions based on resource name."""
|
358
|
+
return self._classify_resource_permissions(resource_name)
|
359
|
+
|
360
|
+
def get_prompt_permissions(self, prompt_name: str) -> dict[str, bool]:
|
361
|
+
"""Get prompt permissions based on prompt name."""
|
362
|
+
return self._classify_prompt_permissions(prompt_name)
|
363
|
+
|
364
|
+
def add_tool_call(self, tool_name: str) -> str:
|
365
|
+
"""
|
366
|
+
Add a tool call and update trifecta flags based on tool classification.
|
367
|
+
|
368
|
+
Args:
|
369
|
+
tool_name: Name of the tool being called
|
370
|
+
|
371
|
+
Returns:
|
372
|
+
Placeholder ID for compatibility
|
373
|
+
|
374
|
+
Raises:
|
375
|
+
SecurityError: If the lethal trifecta is already achieved and this call would be blocked
|
376
|
+
"""
|
377
|
+
# Check if trifecta is already achieved before processing this call
|
378
|
+
if self.is_trifecta_achieved():
|
379
|
+
log.error(f"🚫 BLOCKING tool call {tool_name} - lethal trifecta already achieved")
|
380
|
+
raise SecurityError(f"Tool call '{tool_name}' blocked: lethal trifecta achieved")
|
381
|
+
|
382
|
+
# Get tool permissions and update trifecta flags
|
383
|
+
permissions = self._classify_tool_permissions(tool_name)
|
384
|
+
|
385
|
+
log.debug(f"add_tool_call: Tool permissions: {permissions}")
|
386
|
+
|
387
|
+
# Check if tool is enabled
|
388
|
+
if permissions["enabled"] is False:
|
389
|
+
log.warning(f"🚫 BLOCKING tool call {tool_name} - tool is disabled")
|
390
|
+
raise SecurityError(f"Tool call '{tool_name}' blocked: tool is disabled")
|
391
|
+
|
392
|
+
if permissions["read_private_data"]:
|
393
|
+
self.has_private_data_access = True
|
394
|
+
log.info(f"🔒 Private data access detected: {tool_name}")
|
395
|
+
|
396
|
+
if permissions["read_untrusted_public_data"]:
|
397
|
+
self.has_untrusted_content_exposure = True
|
398
|
+
log.info(f"🌐 Untrusted content exposure detected: {tool_name}")
|
399
|
+
|
400
|
+
if permissions["write_operation"]:
|
401
|
+
self.has_external_communication = True
|
402
|
+
log.info(f"✍️ Write operation detected: {tool_name}")
|
403
|
+
|
404
|
+
# Log if trifecta is achieved after this call
|
405
|
+
if self.is_trifecta_achieved():
|
406
|
+
log.warning(f"⚠️ LETHAL TRIFECTA ACHIEVED after tool call: {tool_name}")
|
407
|
+
|
408
|
+
return "placeholder_id"
|
409
|
+
|
410
|
+
def add_resource_access(self, resource_name: str) -> str:
|
411
|
+
"""
|
412
|
+
Add a resource access and update trifecta flags based on resource classification.
|
413
|
+
|
414
|
+
Args:
|
415
|
+
resource_name: Name/URI of the resource being accessed
|
416
|
+
|
417
|
+
Returns:
|
418
|
+
Placeholder ID for compatibility
|
419
|
+
|
420
|
+
Raises:
|
421
|
+
SecurityError: If the lethal trifecta is already achieved and this access would be blocked
|
422
|
+
"""
|
423
|
+
# Check if trifecta is already achieved before processing this access
|
424
|
+
if self.is_trifecta_achieved():
|
425
|
+
log.error(
|
426
|
+
f"🚫 BLOCKING resource access {resource_name} - lethal trifecta already achieved"
|
427
|
+
)
|
428
|
+
raise SecurityError(
|
429
|
+
f"Resource access '{resource_name}' blocked: lethal trifecta achieved"
|
430
|
+
)
|
431
|
+
|
432
|
+
# Get resource permissions and update trifecta flags
|
433
|
+
permissions = self._classify_resource_permissions(resource_name)
|
434
|
+
|
435
|
+
if permissions["read_private_data"]:
|
436
|
+
self.has_private_data_access = True
|
437
|
+
log.info(f"🔒 Private data access detected via resource: {resource_name}")
|
438
|
+
|
439
|
+
if permissions["read_untrusted_public_data"]:
|
440
|
+
self.has_untrusted_content_exposure = True
|
441
|
+
log.info(f"🌐 Untrusted content exposure detected via resource: {resource_name}")
|
442
|
+
|
443
|
+
if permissions["write_operation"]:
|
444
|
+
self.has_external_communication = True
|
445
|
+
log.info(f"✍️ Write operation detected via resource: {resource_name}")
|
446
|
+
|
447
|
+
# Log if trifecta is achieved after this access
|
448
|
+
if self.is_trifecta_achieved():
|
449
|
+
log.warning(f"⚠️ LETHAL TRIFECTA ACHIEVED after resource access: {resource_name}")
|
450
|
+
|
451
|
+
return "placeholder_id"
|
452
|
+
|
453
|
+
def add_prompt_access(self, prompt_name: str) -> str:
|
454
|
+
"""
|
455
|
+
Add a prompt access and update trifecta flags based on prompt classification.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
prompt_name: Name/type of the prompt being accessed
|
459
|
+
|
460
|
+
Returns:
|
461
|
+
Placeholder ID for compatibility
|
462
|
+
|
463
|
+
Raises:
|
464
|
+
SecurityError: If the lethal trifecta is already achieved and this access would be blocked
|
465
|
+
"""
|
466
|
+
# Check if trifecta is already achieved before processing this access
|
467
|
+
if self.is_trifecta_achieved():
|
468
|
+
log.error(f"🚫 BLOCKING prompt access {prompt_name} - lethal trifecta already achieved")
|
469
|
+
raise SecurityError(f"Prompt access '{prompt_name}' blocked: lethal trifecta achieved")
|
470
|
+
|
471
|
+
# Get prompt permissions and update trifecta flags
|
472
|
+
permissions = self._classify_prompt_permissions(prompt_name)
|
473
|
+
|
474
|
+
if permissions["read_private_data"]:
|
475
|
+
self.has_private_data_access = True
|
476
|
+
log.info(f"🔒 Private data access detected via prompt: {prompt_name}")
|
477
|
+
|
478
|
+
if permissions["read_untrusted_public_data"]:
|
479
|
+
self.has_untrusted_content_exposure = True
|
480
|
+
log.info(f"🌐 Untrusted content exposure detected via prompt: {prompt_name}")
|
481
|
+
|
482
|
+
if permissions["write_operation"]:
|
483
|
+
self.has_external_communication = True
|
484
|
+
log.info(f"✍️ Write operation detected via prompt: {prompt_name}")
|
485
|
+
|
486
|
+
# Log if trifecta is achieved after this access
|
487
|
+
if self.is_trifecta_achieved():
|
488
|
+
log.warning(f"⚠️ LETHAL TRIFECTA ACHIEVED after prompt access: {prompt_name}")
|
489
|
+
|
490
|
+
return "placeholder_id"
|
491
|
+
|
492
|
+
def to_dict(self) -> dict[str, Any]:
|
493
|
+
"""
|
494
|
+
Convert tracker to dictionary for serialization.
|
495
|
+
|
496
|
+
Returns:
|
497
|
+
Dictionary representation of the tracker
|
498
|
+
"""
|
499
|
+
return {
|
500
|
+
"lethal_trifecta": {
|
501
|
+
"has_private_data_access": self.has_private_data_access,
|
502
|
+
"has_untrusted_content_exposure": self.has_untrusted_content_exposure,
|
503
|
+
"has_external_communication": self.has_external_communication,
|
504
|
+
"trifecta_achieved": self.is_trifecta_achieved(),
|
505
|
+
},
|
506
|
+
}
|
507
|
+
|
508
|
+
|
509
|
+
class SecurityError(Exception):
|
510
|
+
"""Raised when a security policy violation occurs."""
|