atlas-chat 0.1.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.
- atlas/__init__.py +40 -0
- atlas/application/__init__.py +7 -0
- atlas/application/chat/__init__.py +7 -0
- atlas/application/chat/agent/__init__.py +10 -0
- atlas/application/chat/agent/act_loop.py +179 -0
- atlas/application/chat/agent/factory.py +142 -0
- atlas/application/chat/agent/protocols.py +46 -0
- atlas/application/chat/agent/react_loop.py +338 -0
- atlas/application/chat/agent/think_act_loop.py +171 -0
- atlas/application/chat/approval_manager.py +151 -0
- atlas/application/chat/elicitation_manager.py +191 -0
- atlas/application/chat/events/__init__.py +1 -0
- atlas/application/chat/events/agent_event_relay.py +112 -0
- atlas/application/chat/modes/__init__.py +1 -0
- atlas/application/chat/modes/agent.py +125 -0
- atlas/application/chat/modes/plain.py +74 -0
- atlas/application/chat/modes/rag.py +81 -0
- atlas/application/chat/modes/tools.py +179 -0
- atlas/application/chat/orchestrator.py +213 -0
- atlas/application/chat/policies/__init__.py +1 -0
- atlas/application/chat/policies/tool_authorization.py +99 -0
- atlas/application/chat/preprocessors/__init__.py +1 -0
- atlas/application/chat/preprocessors/message_builder.py +92 -0
- atlas/application/chat/preprocessors/prompt_override_service.py +104 -0
- atlas/application/chat/service.py +454 -0
- atlas/application/chat/utilities/__init__.py +6 -0
- atlas/application/chat/utilities/error_handler.py +367 -0
- atlas/application/chat/utilities/event_notifier.py +546 -0
- atlas/application/chat/utilities/file_processor.py +613 -0
- atlas/application/chat/utilities/tool_executor.py +789 -0
- atlas/atlas_chat_cli.py +347 -0
- atlas/atlas_client.py +238 -0
- atlas/core/__init__.py +0 -0
- atlas/core/auth.py +205 -0
- atlas/core/authorization_manager.py +27 -0
- atlas/core/capabilities.py +123 -0
- atlas/core/compliance.py +215 -0
- atlas/core/domain_whitelist.py +147 -0
- atlas/core/domain_whitelist_middleware.py +82 -0
- atlas/core/http_client.py +28 -0
- atlas/core/log_sanitizer.py +102 -0
- atlas/core/metrics_logger.py +59 -0
- atlas/core/middleware.py +131 -0
- atlas/core/otel_config.py +242 -0
- atlas/core/prompt_risk.py +200 -0
- atlas/core/rate_limit.py +0 -0
- atlas/core/rate_limit_middleware.py +64 -0
- atlas/core/security_headers_middleware.py +51 -0
- atlas/domain/__init__.py +37 -0
- atlas/domain/chat/__init__.py +1 -0
- atlas/domain/chat/dtos.py +85 -0
- atlas/domain/errors.py +96 -0
- atlas/domain/messages/__init__.py +12 -0
- atlas/domain/messages/models.py +160 -0
- atlas/domain/rag_mcp_service.py +664 -0
- atlas/domain/sessions/__init__.py +7 -0
- atlas/domain/sessions/models.py +36 -0
- atlas/domain/unified_rag_service.py +371 -0
- atlas/infrastructure/__init__.py +10 -0
- atlas/infrastructure/app_factory.py +135 -0
- atlas/infrastructure/events/__init__.py +1 -0
- atlas/infrastructure/events/cli_event_publisher.py +140 -0
- atlas/infrastructure/events/websocket_publisher.py +140 -0
- atlas/infrastructure/sessions/in_memory_repository.py +56 -0
- atlas/infrastructure/transport/__init__.py +7 -0
- atlas/infrastructure/transport/websocket_connection_adapter.py +33 -0
- atlas/init_cli.py +226 -0
- atlas/interfaces/__init__.py +15 -0
- atlas/interfaces/events.py +134 -0
- atlas/interfaces/llm.py +54 -0
- atlas/interfaces/rag.py +40 -0
- atlas/interfaces/sessions.py +75 -0
- atlas/interfaces/tools.py +57 -0
- atlas/interfaces/transport.py +24 -0
- atlas/main.py +564 -0
- atlas/mcp/api_key_demo/README.md +76 -0
- atlas/mcp/api_key_demo/main.py +172 -0
- atlas/mcp/api_key_demo/run.sh +56 -0
- atlas/mcp/basictable/main.py +147 -0
- atlas/mcp/calculator/main.py +149 -0
- atlas/mcp/code-executor/execution_engine.py +98 -0
- atlas/mcp/code-executor/execution_environment.py +95 -0
- atlas/mcp/code-executor/main.py +528 -0
- atlas/mcp/code-executor/result_processing.py +276 -0
- atlas/mcp/code-executor/script_generation.py +195 -0
- atlas/mcp/code-executor/security_checker.py +140 -0
- atlas/mcp/corporate_cars/main.py +437 -0
- atlas/mcp/csv_reporter/main.py +545 -0
- atlas/mcp/duckduckgo/main.py +182 -0
- atlas/mcp/elicitation_demo/README.md +171 -0
- atlas/mcp/elicitation_demo/main.py +262 -0
- atlas/mcp/env-demo/README.md +158 -0
- atlas/mcp/env-demo/main.py +199 -0
- atlas/mcp/file_size_test/main.py +284 -0
- atlas/mcp/filesystem/main.py +348 -0
- atlas/mcp/image_demo/main.py +113 -0
- atlas/mcp/image_demo/requirements.txt +4 -0
- atlas/mcp/logging_demo/README.md +72 -0
- atlas/mcp/logging_demo/main.py +103 -0
- atlas/mcp/many_tools_demo/main.py +50 -0
- atlas/mcp/order_database/__init__.py +0 -0
- atlas/mcp/order_database/main.py +369 -0
- atlas/mcp/order_database/signal_data.csv +1001 -0
- atlas/mcp/pdfbasic/main.py +394 -0
- atlas/mcp/pptx_generator/main.py +760 -0
- atlas/mcp/pptx_generator/requirements.txt +13 -0
- atlas/mcp/pptx_generator/run_test.sh +1 -0
- atlas/mcp/pptx_generator/test_pptx_generator_security.py +169 -0
- atlas/mcp/progress_demo/main.py +167 -0
- atlas/mcp/progress_updates_demo/QUICKSTART.md +273 -0
- atlas/mcp/progress_updates_demo/README.md +120 -0
- atlas/mcp/progress_updates_demo/main.py +497 -0
- atlas/mcp/prompts/main.py +222 -0
- atlas/mcp/public_demo/main.py +189 -0
- atlas/mcp/sampling_demo/README.md +169 -0
- atlas/mcp/sampling_demo/main.py +234 -0
- atlas/mcp/thinking/main.py +77 -0
- atlas/mcp/tool_planner/main.py +240 -0
- atlas/mcp/ui-demo/badmesh.png +0 -0
- atlas/mcp/ui-demo/main.py +383 -0
- atlas/mcp/ui-demo/templates/button_demo.html +32 -0
- atlas/mcp/ui-demo/templates/data_visualization.html +32 -0
- atlas/mcp/ui-demo/templates/form_demo.html +28 -0
- atlas/mcp/username-override-demo/README.md +320 -0
- atlas/mcp/username-override-demo/main.py +308 -0
- atlas/modules/__init__.py +0 -0
- atlas/modules/config/__init__.py +34 -0
- atlas/modules/config/cli.py +231 -0
- atlas/modules/config/config_manager.py +1096 -0
- atlas/modules/file_storage/__init__.py +22 -0
- atlas/modules/file_storage/cli.py +330 -0
- atlas/modules/file_storage/content_extractor.py +290 -0
- atlas/modules/file_storage/manager.py +295 -0
- atlas/modules/file_storage/mock_s3_client.py +402 -0
- atlas/modules/file_storage/s3_client.py +417 -0
- atlas/modules/llm/__init__.py +19 -0
- atlas/modules/llm/caller.py +287 -0
- atlas/modules/llm/litellm_caller.py +675 -0
- atlas/modules/llm/models.py +19 -0
- atlas/modules/mcp_tools/__init__.py +17 -0
- atlas/modules/mcp_tools/client.py +2123 -0
- atlas/modules/mcp_tools/token_storage.py +556 -0
- atlas/modules/prompts/prompt_provider.py +130 -0
- atlas/modules/rag/__init__.py +24 -0
- atlas/modules/rag/atlas_rag_client.py +336 -0
- atlas/modules/rag/client.py +129 -0
- atlas/routes/admin_routes.py +865 -0
- atlas/routes/config_routes.py +484 -0
- atlas/routes/feedback_routes.py +361 -0
- atlas/routes/files_routes.py +274 -0
- atlas/routes/health_routes.py +40 -0
- atlas/routes/mcp_auth_routes.py +223 -0
- atlas/server_cli.py +164 -0
- atlas/tests/conftest.py +20 -0
- atlas/tests/integration/test_mcp_auth_integration.py +152 -0
- atlas/tests/manual_test_sampling.py +87 -0
- atlas/tests/modules/mcp_tools/test_client_auth.py +226 -0
- atlas/tests/modules/mcp_tools/test_client_env.py +191 -0
- atlas/tests/test_admin_mcp_server_management_routes.py +141 -0
- atlas/tests/test_agent_roa.py +135 -0
- atlas/tests/test_app_factory_smoke.py +47 -0
- atlas/tests/test_approval_manager.py +439 -0
- atlas/tests/test_atlas_client.py +188 -0
- atlas/tests/test_atlas_rag_client.py +447 -0
- atlas/tests/test_atlas_rag_integration.py +224 -0
- atlas/tests/test_attach_file_flow.py +287 -0
- atlas/tests/test_auth_utils.py +165 -0
- atlas/tests/test_backend_public_url.py +185 -0
- atlas/tests/test_banner_logging.py +287 -0
- atlas/tests/test_capability_tokens_and_injection.py +203 -0
- atlas/tests/test_compliance_level.py +54 -0
- atlas/tests/test_compliance_manager.py +253 -0
- atlas/tests/test_config_manager.py +617 -0
- atlas/tests/test_config_manager_paths.py +12 -0
- atlas/tests/test_core_auth.py +18 -0
- atlas/tests/test_core_utils.py +190 -0
- atlas/tests/test_docker_env_sync.py +202 -0
- atlas/tests/test_domain_errors.py +329 -0
- atlas/tests/test_domain_whitelist.py +359 -0
- atlas/tests/test_elicitation_manager.py +408 -0
- atlas/tests/test_elicitation_routing.py +296 -0
- atlas/tests/test_env_demo_server.py +88 -0
- atlas/tests/test_error_classification.py +113 -0
- atlas/tests/test_error_flow_integration.py +116 -0
- atlas/tests/test_feedback_routes.py +333 -0
- atlas/tests/test_file_content_extraction.py +1134 -0
- atlas/tests/test_file_extraction_routes.py +158 -0
- atlas/tests/test_file_library.py +107 -0
- atlas/tests/test_file_manager_unit.py +18 -0
- atlas/tests/test_health_route.py +49 -0
- atlas/tests/test_http_client_stub.py +8 -0
- atlas/tests/test_imports_smoke.py +30 -0
- atlas/tests/test_interfaces_llm_response.py +9 -0
- atlas/tests/test_issue_access_denied_fix.py +136 -0
- atlas/tests/test_llm_env_expansion.py +836 -0
- atlas/tests/test_log_level_sensitive_data.py +285 -0
- atlas/tests/test_mcp_auth_routes.py +341 -0
- atlas/tests/test_mcp_client_auth.py +331 -0
- atlas/tests/test_mcp_data_injection.py +270 -0
- atlas/tests/test_mcp_get_authorized_servers.py +95 -0
- atlas/tests/test_mcp_hot_reload.py +512 -0
- atlas/tests/test_mcp_image_content.py +424 -0
- atlas/tests/test_mcp_logging.py +172 -0
- atlas/tests/test_mcp_progress_updates.py +313 -0
- atlas/tests/test_mcp_prompt_override_system_prompt.py +102 -0
- atlas/tests/test_mcp_prompts_server.py +39 -0
- atlas/tests/test_mcp_tool_result_parsing.py +296 -0
- atlas/tests/test_metrics_logger.py +56 -0
- atlas/tests/test_middleware_auth.py +379 -0
- atlas/tests/test_prompt_risk_and_acl.py +141 -0
- atlas/tests/test_rag_mcp_aggregator.py +204 -0
- atlas/tests/test_rag_mcp_service.py +224 -0
- atlas/tests/test_rate_limit_middleware.py +45 -0
- atlas/tests/test_routes_config_smoke.py +60 -0
- atlas/tests/test_routes_files_download_token.py +41 -0
- atlas/tests/test_routes_files_health.py +18 -0
- atlas/tests/test_runtime_imports.py +53 -0
- atlas/tests/test_sampling_integration.py +482 -0
- atlas/tests/test_security_admin_routes.py +61 -0
- atlas/tests/test_security_capability_tokens.py +65 -0
- atlas/tests/test_security_file_stats_scope.py +21 -0
- atlas/tests/test_security_header_injection.py +191 -0
- atlas/tests/test_security_headers_and_filename.py +63 -0
- atlas/tests/test_shared_session_repository.py +101 -0
- atlas/tests/test_system_prompt_loading.py +181 -0
- atlas/tests/test_token_storage.py +505 -0
- atlas/tests/test_tool_approval_config.py +93 -0
- atlas/tests/test_tool_approval_utils.py +356 -0
- atlas/tests/test_tool_authorization_group_filtering.py +223 -0
- atlas/tests/test_tool_details_in_config.py +108 -0
- atlas/tests/test_tool_planner.py +300 -0
- atlas/tests/test_unified_rag_service.py +398 -0
- atlas/tests/test_username_override_in_approval.py +258 -0
- atlas/tests/test_websocket_auth_header.py +168 -0
- atlas/version.py +6 -0
- atlas_chat-0.1.0.data/data/.env.example +253 -0
- atlas_chat-0.1.0.data/data/config/defaults/compliance-levels.json +44 -0
- atlas_chat-0.1.0.data/data/config/defaults/domain-whitelist.json +123 -0
- atlas_chat-0.1.0.data/data/config/defaults/file-extractors.json +74 -0
- atlas_chat-0.1.0.data/data/config/defaults/help-config.json +198 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig-buggy.yml +11 -0
- atlas_chat-0.1.0.data/data/config/defaults/llmconfig.yml +19 -0
- atlas_chat-0.1.0.data/data/config/defaults/mcp.json +138 -0
- atlas_chat-0.1.0.data/data/config/defaults/rag-sources.json +17 -0
- atlas_chat-0.1.0.data/data/config/defaults/splash-config.json +16 -0
- atlas_chat-0.1.0.dist-info/METADATA +236 -0
- atlas_chat-0.1.0.dist-info/RECORD +250 -0
- atlas_chat-0.1.0.dist-info/WHEEL +5 -0
- atlas_chat-0.1.0.dist-info/entry_points.txt +4 -0
- atlas_chat-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Username Override Demo MCP Server
|
|
2
|
+
|
|
3
|
+
This MCP server demonstrates the username override security feature in Atlas UI 3. This feature ensures that tools accepting a `username` parameter always run with the authenticated user's identity, preventing LLMs from impersonating other users.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This server provides tools to demonstrate and explain the username override security mechanism, which is critical for preventing unauthorized actions in multi-user environments.
|
|
8
|
+
|
|
9
|
+
## Security Feature Explained
|
|
10
|
+
|
|
11
|
+
### The Problem
|
|
12
|
+
|
|
13
|
+
Without username override, an LLM could potentially call a tool with a different username:
|
|
14
|
+
```
|
|
15
|
+
Tool call: create_user_record(username="admin@company.com", record_type="note", data="...")
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This would allow the LLM to impersonate other users and perform unauthorized actions.
|
|
19
|
+
|
|
20
|
+
### The Solution
|
|
21
|
+
|
|
22
|
+
Atlas UI 3 automatically **overrides** the `username` parameter with the authenticated user's email from the `X-User-Email` header (or the dev fallback user). The LLM cannot control this value.
|
|
23
|
+
|
|
24
|
+
**How it works:**
|
|
25
|
+
|
|
26
|
+
1. The tool's schema declares a `username` parameter
|
|
27
|
+
2. The LLM generates a tool call (with or without a username argument)
|
|
28
|
+
3. The Atlas UI backend detects that the tool accepts `username`
|
|
29
|
+
4. The backend **injects or overrides** the `username` argument with the authenticated user's email
|
|
30
|
+
5. The tool receives the correct, authenticated username
|
|
31
|
+
|
|
32
|
+
**Code Location:**
|
|
33
|
+
|
|
34
|
+
The username injection happens in `backend/application/chat/utilities/tool_utils.py`:
|
|
35
|
+
- `tool_accepts_username()` checks if a tool's schema includes a `username` parameter
|
|
36
|
+
- `inject_context_into_args()` injects the authenticated user's email into the arguments
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Add this server to your `config/overrides/mcp.json` or use the example config:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"username-override-demo": {
|
|
45
|
+
"command": ["python", "mcp/username-override-demo/main.py"],
|
|
46
|
+
"cwd": "atlas",
|
|
47
|
+
"groups": ["users"],
|
|
48
|
+
"description": "Demonstrates the username override security feature that prevents LLM user impersonation",
|
|
49
|
+
"author": "Atlas UI Team",
|
|
50
|
+
"short_description": "Username override security demo",
|
|
51
|
+
"help_email": "support@example.com",
|
|
52
|
+
"compliance_level": "Public"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or reference the pre-configured example:
|
|
58
|
+
```bash
|
|
59
|
+
# WARNING: Do NOT simply copy the example file as it will overwrite your existing config!
|
|
60
|
+
# Instead, manually merge the server configuration into your existing mcp.json file,
|
|
61
|
+
# or use a JSON merge tool to combine the configurations.
|
|
62
|
+
|
|
63
|
+
# View the example configuration:
|
|
64
|
+
cat config/mcp-example-configs/mcp-username-override-demo.json
|
|
65
|
+
|
|
66
|
+
# Manually add the "username-override-demo" entry to your config/overrides/mcp.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Available Tools
|
|
70
|
+
|
|
71
|
+
### 1. `get_user_info`
|
|
72
|
+
|
|
73
|
+
Retrieves information about the authenticated user.
|
|
74
|
+
|
|
75
|
+
**Parameters:**
|
|
76
|
+
- `username` (string): Automatically overridden with authenticated user's email
|
|
77
|
+
|
|
78
|
+
**Example Usage:**
|
|
79
|
+
```
|
|
80
|
+
Get my user information
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Example Response:**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"results": {
|
|
87
|
+
"username": "alice@example.com",
|
|
88
|
+
"message": "Current authenticated user: alice@example.com",
|
|
89
|
+
"security_note": "This username was injected by Atlas UI backend and cannot be spoofed by the LLM"
|
|
90
|
+
},
|
|
91
|
+
"meta_data": {
|
|
92
|
+
"elapsed_ms": 0.123
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. `create_user_record`
|
|
98
|
+
|
|
99
|
+
Creates a record associated with the authenticated user.
|
|
100
|
+
|
|
101
|
+
**Parameters:**
|
|
102
|
+
- `username` (string): Automatically overridden with authenticated user's email
|
|
103
|
+
- `record_type` (string): Type of record (e.g., "note", "task", "document")
|
|
104
|
+
- `data` (string): Content for the record
|
|
105
|
+
|
|
106
|
+
**Example Usage:**
|
|
107
|
+
```
|
|
108
|
+
Create a note for me with the text "Meeting at 3pm"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Security Guarantee:**
|
|
112
|
+
The record will ALWAYS be created for the authenticated user, regardless of what the LLM tries to specify.
|
|
113
|
+
|
|
114
|
+
**Approval Flow Security:**
|
|
115
|
+
When tool approval with argument editing is enabled, users cannot bypass the username override by editing the username field in the approval dialog. The system re-applies security injections after user edits to ensure username and other security-critical parameters cannot be tampered with.
|
|
116
|
+
|
|
117
|
+
### 3. `check_user_permissions`
|
|
118
|
+
|
|
119
|
+
Checks if the authenticated user has permission for a specific action.
|
|
120
|
+
|
|
121
|
+
**Parameters:**
|
|
122
|
+
- `username` (string): Automatically overridden with authenticated user's email
|
|
123
|
+
- `resource` (string): Resource to check (e.g., "document", "database", "api")
|
|
124
|
+
- `action` (string): Action to check (e.g., "read", "write", "delete")
|
|
125
|
+
|
|
126
|
+
**Example Usage:**
|
|
127
|
+
```
|
|
128
|
+
Check if I have write permission for the database
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Security Guarantee:**
|
|
132
|
+
Permission checks are ALWAYS performed for the authenticated user. The LLM cannot check another user's permissions.
|
|
133
|
+
|
|
134
|
+
### 4. `demonstrate_override_attempt`
|
|
135
|
+
|
|
136
|
+
Explicitly demonstrates the username override feature in action.
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
- `username` (string): Automatically injected by Atlas UI backend with authenticated user's email
|
|
140
|
+
- `attempted_username` (string, optional): A username the LLM might try to use (for demonstration)
|
|
141
|
+
|
|
142
|
+
**Example Usage:**
|
|
143
|
+
```
|
|
144
|
+
Try to demonstrate the username override by attempting to use admin@company.com
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Example Response:**
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"results": {
|
|
151
|
+
"actual_username": "alice@example.com",
|
|
152
|
+
"attempted_username": "admin@company.com",
|
|
153
|
+
"override_occurred": true,
|
|
154
|
+
"impersonation_attempted": true,
|
|
155
|
+
"explanation": "The authenticated user is: alice@example.com. The LLM attempted to use: admin@company.com. Atlas UI backend detected and blocked this impersonation attempt by overriding the username parameter with the real authenticated user's email."
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Testing the Username Override
|
|
161
|
+
|
|
162
|
+
To verify the username override feature works:
|
|
163
|
+
|
|
164
|
+
1. Start Atlas UI and enable this server
|
|
165
|
+
2. Open the chat interface
|
|
166
|
+
3. Try these test scenarios:
|
|
167
|
+
|
|
168
|
+
**Test 1: Basic Override**
|
|
169
|
+
```
|
|
170
|
+
Use get_user_info to show me who I am
|
|
171
|
+
```
|
|
172
|
+
Expected: Returns your authenticated email from the X-User-Email header
|
|
173
|
+
|
|
174
|
+
**Test 2: Explicit Override Attempt**
|
|
175
|
+
```
|
|
176
|
+
Use demonstrate_override_attempt and try to use the username "admin@company.com"
|
|
177
|
+
```
|
|
178
|
+
Expected: Shows that the actual username is your authenticated email, not admin@company.com
|
|
179
|
+
|
|
180
|
+
**Test 3: Record Creation**
|
|
181
|
+
```
|
|
182
|
+
Create a note for user "someoneelse@company.com" with the data "test"
|
|
183
|
+
```
|
|
184
|
+
Expected: The record is created for YOUR authenticated email, not someoneelse@company.com
|
|
185
|
+
|
|
186
|
+
**Test 4: Permission Check**
|
|
187
|
+
```
|
|
188
|
+
Check if user "admin@company.com" has write permission for database
|
|
189
|
+
```
|
|
190
|
+
Expected: The permission check is performed for YOUR authenticated email, not admin
|
|
191
|
+
|
|
192
|
+
## Security Implications
|
|
193
|
+
|
|
194
|
+
This username override feature is critical for:
|
|
195
|
+
|
|
196
|
+
1. **Preventing User Impersonation**: LLMs cannot impersonate other users
|
|
197
|
+
2. **Audit Trail Integrity**: All actions are correctly attributed to the authenticated user
|
|
198
|
+
3. **Authorization Enforcement**: Permission checks always use the real user identity
|
|
199
|
+
4. **Data Isolation**: Users can only access their own data, even if the LLM tries otherwise
|
|
200
|
+
|
|
201
|
+
## Implementation Notes
|
|
202
|
+
|
|
203
|
+
### For Tool Developers
|
|
204
|
+
|
|
205
|
+
When creating MCP tools that need user context:
|
|
206
|
+
|
|
207
|
+
1. **Add a `username` parameter to your tool's schema** if the tool needs to know who is calling it
|
|
208
|
+
2. **Trust the username value** - it will always be the authenticated user
|
|
209
|
+
3. **Never accept username from other parameters** - use the injected `username` parameter only
|
|
210
|
+
4. **Document that username is automatically provided** - callers don't need to supply it
|
|
211
|
+
|
|
212
|
+
**Example Tool Schema:**
|
|
213
|
+
```python
|
|
214
|
+
@mcp.tool
|
|
215
|
+
def my_user_specific_tool(username: str, other_param: str) -> Dict[str, Any]:
|
|
216
|
+
"""Do something for the authenticated user.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
username: The authenticated user (automatically injected by Atlas UI)
|
|
220
|
+
other_param: Some other parameter the LLM provides
|
|
221
|
+
"""
|
|
222
|
+
# username is guaranteed to be the authenticated user
|
|
223
|
+
return {
|
|
224
|
+
"results": {
|
|
225
|
+
"username": username,
|
|
226
|
+
"result": f"Action performed for {username}"
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Schema Awareness
|
|
232
|
+
|
|
233
|
+
The username injection is **schema-aware**:
|
|
234
|
+
- Only tools that declare a `username` parameter in their schema receive it
|
|
235
|
+
- Tools without a `username` parameter are not affected
|
|
236
|
+
- This prevents breaking tools that don't expect this parameter
|
|
237
|
+
|
|
238
|
+
### Default User in Development
|
|
239
|
+
|
|
240
|
+
When running locally without a reverse proxy:
|
|
241
|
+
- Atlas UI falls back to a test user (configured in `APP_DEV_USER_EMAIL`)
|
|
242
|
+
- The username override still works, using the dev fallback user
|
|
243
|
+
- This allows testing the feature in development environments
|
|
244
|
+
|
|
245
|
+
## Related Documentation
|
|
246
|
+
|
|
247
|
+
- **Admin Guide**: `docs/admin/mcp-servers.md` - See "A Note on the `username` Argument" section
|
|
248
|
+
- **MCP Tool Outputs**: `docs/developer/mcp-tool-outputs.md` - Best practices for tool development
|
|
249
|
+
- **Authentication**: `.github/copilot-instructions.md` - How X-User-Email authentication works
|
|
250
|
+
|
|
251
|
+
## Troubleshooting
|
|
252
|
+
|
|
253
|
+
**Q: The LLM says it needs a username parameter, what should I provide?**
|
|
254
|
+
|
|
255
|
+
A: Tell the LLM that the username is automatically provided by the system and it should not include it in the tool call arguments.
|
|
256
|
+
|
|
257
|
+
**Q: How can I verify which user is authenticated?**
|
|
258
|
+
|
|
259
|
+
A: Use the `get_user_info` tool from this server, or check the `/api/config` endpoint which returns the current user.
|
|
260
|
+
|
|
261
|
+
**Q: Can admin users override the username for testing?**
|
|
262
|
+
|
|
263
|
+
A: No. The username override is enforced for all users, including admins. This ensures consistent security. For testing, you would need to authenticate as the target user.
|
|
264
|
+
|
|
265
|
+
**Q: What if I want to build a tool that can act on behalf of other users (e.g., an admin tool)?**
|
|
266
|
+
|
|
267
|
+
A: The tool should accept a separate parameter (e.g., `target_user`) and then check if the authenticated `username` has admin permissions before acting on behalf of `target_user`.
|
|
268
|
+
|
|
269
|
+
## Security Best Practices
|
|
270
|
+
|
|
271
|
+
1. **Always use the `username` parameter** for user-specific operations
|
|
272
|
+
2. **Never trust username from other sources** (e.g., tool name, other parameters, environment variables)
|
|
273
|
+
3. **Log the username** for audit trails and debugging
|
|
274
|
+
4. **Implement additional authorization checks** based on the username (e.g., check if user has permission for the requested action)
|
|
275
|
+
5. **Document security assumptions** in your tool descriptions
|
|
276
|
+
|
|
277
|
+
## Example: Admin Tool Pattern
|
|
278
|
+
|
|
279
|
+
If you need a tool that can act on behalf of other users (admin use case):
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
@mcp.tool
|
|
283
|
+
def admin_create_record_for_user(
|
|
284
|
+
username: str, # The authenticated admin (auto-injected)
|
|
285
|
+
target_user: str, # The user to create record for
|
|
286
|
+
record_type: str,
|
|
287
|
+
data: str
|
|
288
|
+
) -> Dict[str, Any]:
|
|
289
|
+
"""Create a record for another user (admin only).
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
username: The authenticated admin user (automatically injected)
|
|
293
|
+
target_user: The user to create the record for
|
|
294
|
+
record_type: Type of record
|
|
295
|
+
data: Record content
|
|
296
|
+
"""
|
|
297
|
+
# Check if username has admin permissions
|
|
298
|
+
if not is_admin(username):
|
|
299
|
+
return {
|
|
300
|
+
"results": {
|
|
301
|
+
"error": f"User {username} does not have admin permissions"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Now safe to create record for target_user
|
|
306
|
+
return {
|
|
307
|
+
"results": {
|
|
308
|
+
"success": True,
|
|
309
|
+
"admin_user": username,
|
|
310
|
+
"target_user": target_user,
|
|
311
|
+
"message": f"Admin {username} created {record_type} for {target_user}"
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
This pattern ensures:
|
|
317
|
+
- The authenticated user (`username`) is always correct
|
|
318
|
+
- Admin authorization is checked
|
|
319
|
+
- Audit logs show who performed the action
|
|
320
|
+
- The LLM cannot impersonate an admin
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Username Override Demo MCP Server using FastMCP
|
|
4
|
+
|
|
5
|
+
This server demonstrates the security feature where the Atlas UI backend
|
|
6
|
+
automatically overrides the username parameter with the authenticated user's
|
|
7
|
+
email. This prevents LLMs from impersonating other users.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
|
|
15
|
+
# Initialize the MCP server
|
|
16
|
+
mcp = FastMCP("Username Override Demo")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@mcp.tool
|
|
20
|
+
def get_user_info(username: str) -> Dict[str, Any]:
|
|
21
|
+
"""Get information about the current user.
|
|
22
|
+
|
|
23
|
+
This tool demonstrates the username override security feature. Even if the LLM
|
|
24
|
+
tries to pass a different username, the Atlas UI backend will always override
|
|
25
|
+
it with the authenticated user's email from the X-User-Email header.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
username: The username parameter. This will be automatically overridden
|
|
29
|
+
by Atlas UI backend with the authenticated user's email.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
MCP contract shape with user information:
|
|
33
|
+
{
|
|
34
|
+
"results": {
|
|
35
|
+
"username": str, # The actual authenticated user
|
|
36
|
+
"message": str,
|
|
37
|
+
"security_note": str
|
|
38
|
+
},
|
|
39
|
+
"meta_data": {
|
|
40
|
+
"elapsed_ms": float
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
"""
|
|
44
|
+
start = time.perf_counter()
|
|
45
|
+
|
|
46
|
+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
"results": {
|
|
50
|
+
"username": username,
|
|
51
|
+
"message": f"Current authenticated user: {username}",
|
|
52
|
+
"security_note": "This username was injected by Atlas UI backend and cannot be spoofed by the LLM"
|
|
53
|
+
},
|
|
54
|
+
"meta_data": {
|
|
55
|
+
"elapsed_ms": elapsed_ms
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@mcp.tool
|
|
61
|
+
def create_user_record(username: str, record_type: str, data: str) -> Dict[str, Any]:
|
|
62
|
+
"""Create a record associated with the authenticated user.
|
|
63
|
+
|
|
64
|
+
This tool demonstrates how username override ensures that records are always
|
|
65
|
+
created with the correct user context, preventing unauthorized actions.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
username: The username parameter (automatically overridden with authenticated user)
|
|
69
|
+
record_type: Type of record to create (e.g., "note", "task", "document")
|
|
70
|
+
data: The content/data for the record
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
MCP contract shape with record creation confirmation:
|
|
74
|
+
{
|
|
75
|
+
"results": {
|
|
76
|
+
"success": bool,
|
|
77
|
+
"username": str,
|
|
78
|
+
"record_type": str,
|
|
79
|
+
"data_length": int,
|
|
80
|
+
"message": str
|
|
81
|
+
},
|
|
82
|
+
"meta_data": {
|
|
83
|
+
"elapsed_ms": float
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
"""
|
|
87
|
+
start = time.perf_counter()
|
|
88
|
+
|
|
89
|
+
# In a real implementation, this would create a record in a database
|
|
90
|
+
# associated with the username
|
|
91
|
+
|
|
92
|
+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"results": {
|
|
96
|
+
"success": True,
|
|
97
|
+
"username": username,
|
|
98
|
+
"record_type": record_type,
|
|
99
|
+
"data_length": len(data),
|
|
100
|
+
"message": f"Created {record_type} for user {username} with {len(data)} characters of data"
|
|
101
|
+
},
|
|
102
|
+
"meta_data": {
|
|
103
|
+
"elapsed_ms": elapsed_ms
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@mcp.tool
|
|
109
|
+
def check_user_permissions(username: str, resource: str, action: str) -> Dict[str, Any]:
|
|
110
|
+
"""Check if the authenticated user has permission for a specific action.
|
|
111
|
+
|
|
112
|
+
This tool shows how username override ensures permission checks are always
|
|
113
|
+
performed for the actual authenticated user, not a user the LLM might try
|
|
114
|
+
to impersonate.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
username: The username parameter (automatically overridden with authenticated user)
|
|
118
|
+
resource: The resource to check permissions for (e.g., "document", "database", "api")
|
|
119
|
+
action: The action to check (e.g., "read", "write", "delete", "admin")
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
MCP contract shape with permission check results:
|
|
123
|
+
{
|
|
124
|
+
"results": {
|
|
125
|
+
"username": str,
|
|
126
|
+
"resource": str,
|
|
127
|
+
"action": str,
|
|
128
|
+
"has_permission": bool,
|
|
129
|
+
"message": str,
|
|
130
|
+
"security_note": str
|
|
131
|
+
},
|
|
132
|
+
"meta_data": {
|
|
133
|
+
"elapsed_ms": float
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
"""
|
|
137
|
+
start = time.perf_counter()
|
|
138
|
+
|
|
139
|
+
# This is a demo, so we'll simulate permission logic
|
|
140
|
+
# In a real system, this would check against a permission database
|
|
141
|
+
simulated_permissions = {
|
|
142
|
+
"document": ["read", "write"],
|
|
143
|
+
"database": ["read"],
|
|
144
|
+
"api": ["read"]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
has_permission = action in simulated_permissions.get(resource, [])
|
|
148
|
+
|
|
149
|
+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
"results": {
|
|
153
|
+
"username": username,
|
|
154
|
+
"resource": resource,
|
|
155
|
+
"action": action,
|
|
156
|
+
"has_permission": has_permission,
|
|
157
|
+
"message": f"User {username} {'has' if has_permission else 'does not have'} {action} permission for {resource}",
|
|
158
|
+
"security_note": "Permission checked for authenticated user only - LLM cannot check permissions for other users"
|
|
159
|
+
},
|
|
160
|
+
"meta_data": {
|
|
161
|
+
"elapsed_ms": elapsed_ms
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@mcp.tool
|
|
167
|
+
def demonstrate_override_attempt(username: str, attempted_username: Optional[str] = None) -> Dict[str, Any]:
|
|
168
|
+
"""Demonstrate what happens when trying to override the username.
|
|
169
|
+
|
|
170
|
+
This tool explicitly shows the security feature in action. Even if the LLM
|
|
171
|
+
tries to pass an attempted_username, the backend will always inject the
|
|
172
|
+
authenticated user's email into the username parameter.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
username: The authenticated user (automatically injected by Atlas UI backend)
|
|
176
|
+
attempted_username: A username the LLM might try to use (for demonstration)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
MCP contract shape demonstrating the override:
|
|
180
|
+
{
|
|
181
|
+
"results": {
|
|
182
|
+
"actual_username": str,
|
|
183
|
+
"attempted_username": str or None,
|
|
184
|
+
"override_occurred": bool,
|
|
185
|
+
"impersonation_attempted": bool,
|
|
186
|
+
"explanation": str
|
|
187
|
+
},
|
|
188
|
+
"meta_data": {
|
|
189
|
+
"elapsed_ms": float
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
"""
|
|
193
|
+
start = time.perf_counter()
|
|
194
|
+
|
|
195
|
+
# The override always occurs - username is always injected by the backend
|
|
196
|
+
# when a tool declares it accepts a username parameter
|
|
197
|
+
override_occurred = True
|
|
198
|
+
# Detect if an impersonation attempt was made
|
|
199
|
+
impersonation_attempted = attempted_username is not None and username != attempted_username
|
|
200
|
+
|
|
201
|
+
if impersonation_attempted:
|
|
202
|
+
explanation = (
|
|
203
|
+
f"The authenticated user is: {username}. "
|
|
204
|
+
f"The LLM attempted to use: {attempted_username}. "
|
|
205
|
+
"Atlas UI backend detected and blocked this impersonation attempt by "
|
|
206
|
+
"overriding the username parameter with the real authenticated user's email."
|
|
207
|
+
)
|
|
208
|
+
elif attempted_username and username == attempted_username:
|
|
209
|
+
explanation = (
|
|
210
|
+
f"The authenticated user is: {username}. "
|
|
211
|
+
"The LLM correctly identified the authenticated user. "
|
|
212
|
+
"The Atlas UI backend still injects the username parameter as a security measure."
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
explanation = (
|
|
216
|
+
f"The authenticated user is: {username}. "
|
|
217
|
+
"Atlas UI backend automatically injected the authenticated user's email "
|
|
218
|
+
"into the username parameter. No impersonation attempt was made."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"results": {
|
|
225
|
+
"actual_username": username,
|
|
226
|
+
"attempted_username": attempted_username,
|
|
227
|
+
"override_occurred": override_occurred,
|
|
228
|
+
"impersonation_attempted": impersonation_attempted,
|
|
229
|
+
"explanation": explanation
|
|
230
|
+
},
|
|
231
|
+
"meta_data": {
|
|
232
|
+
"elapsed_ms": elapsed_ms
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@mcp.tool
|
|
238
|
+
def plan_with_tools(
|
|
239
|
+
task: str,
|
|
240
|
+
_mcp_data: Optional[Dict[str, Any]] = None,
|
|
241
|
+
username: Optional[str] = None,
|
|
242
|
+
) -> Dict[str, Any]:
|
|
243
|
+
"""Plan how to accomplish a task using available MCP tools.
|
|
244
|
+
|
|
245
|
+
This tool demonstrates the _mcp_data injection feature. Atlas UI backend
|
|
246
|
+
automatically populates _mcp_data with structured metadata about all
|
|
247
|
+
available MCP tools, enabling this tool to reason about capabilities.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
task: Description of the task to plan for.
|
|
251
|
+
_mcp_data: Automatically injected by Atlas UI with available tool metadata.
|
|
252
|
+
Do not provide this manually.
|
|
253
|
+
username: The authenticated user (automatically injected by Atlas UI backend).
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
MCP contract shape with a plan based on available tools:
|
|
257
|
+
{
|
|
258
|
+
"results": {
|
|
259
|
+
"task": str,
|
|
260
|
+
"username": str,
|
|
261
|
+
"available_server_count": int,
|
|
262
|
+
"available_tool_count": int,
|
|
263
|
+
"plan_steps": list,
|
|
264
|
+
"note": str
|
|
265
|
+
},
|
|
266
|
+
"meta_data": {
|
|
267
|
+
"elapsed_ms": float
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
"""
|
|
271
|
+
start = time.perf_counter()
|
|
272
|
+
|
|
273
|
+
mcp_data = _mcp_data or {}
|
|
274
|
+
servers = mcp_data.get("available_servers", [])
|
|
275
|
+
total_tools = sum(len(s.get("tools", [])) for s in servers)
|
|
276
|
+
|
|
277
|
+
# Build a simple plan listing available tools
|
|
278
|
+
plan_steps = []
|
|
279
|
+
for server in servers:
|
|
280
|
+
for tool in server.get("tools", []):
|
|
281
|
+
plan_steps.append({
|
|
282
|
+
"tool": tool.get("name", "unknown"),
|
|
283
|
+
"server": server.get("server_name", "unknown"),
|
|
284
|
+
"description": tool.get("description", "")[:100],
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
"results": {
|
|
291
|
+
"task": task,
|
|
292
|
+
"username": username or "unknown",
|
|
293
|
+
"available_server_count": len(servers),
|
|
294
|
+
"available_tool_count": total_tools,
|
|
295
|
+
"plan_steps": plan_steps,
|
|
296
|
+
"note": (
|
|
297
|
+
"This is a demo showing that _mcp_data was automatically "
|
|
298
|
+
"injected with metadata about all available MCP tools."
|
|
299
|
+
),
|
|
300
|
+
},
|
|
301
|
+
"meta_data": {
|
|
302
|
+
"elapsed_ms": elapsed_ms
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
mcp.run()
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Configuration module for the chat backend.
|
|
2
|
+
|
|
3
|
+
This module provides centralized configuration management with:
|
|
4
|
+
- Pydantic models for validation
|
|
5
|
+
- Environment variable loading
|
|
6
|
+
- File-based configuration
|
|
7
|
+
- CLI tools for validation and inspection
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .config_manager import (
|
|
11
|
+
AppSettings,
|
|
12
|
+
ConfigManager,
|
|
13
|
+
LLMConfig,
|
|
14
|
+
MCPConfig,
|
|
15
|
+
MCPServerConfig,
|
|
16
|
+
ModelConfig,
|
|
17
|
+
config_manager,
|
|
18
|
+
get_app_settings,
|
|
19
|
+
get_llm_config,
|
|
20
|
+
get_mcp_config,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ConfigManager",
|
|
25
|
+
"AppSettings",
|
|
26
|
+
"LLMConfig",
|
|
27
|
+
"MCPConfig",
|
|
28
|
+
"ModelConfig",
|
|
29
|
+
"MCPServerConfig",
|
|
30
|
+
"config_manager",
|
|
31
|
+
"get_app_settings",
|
|
32
|
+
"get_llm_config",
|
|
33
|
+
"get_mcp_config",
|
|
34
|
+
]
|