fix(shared-ai): implementar aislamiento de credenciales locales y globales

This commit is contained in:
2026-05-28 11:18:03 -03:00
parent 7b053998f9
commit f6ce48ed8a
2 changed files with 65 additions and 0 deletions
+10
View File
@@ -160,6 +160,16 @@ class configfile:
# Deep merge: shared as base, user overrides # Deep merge: shared as base, user overrides
base = copy.deepcopy(self._shared_config.config.get(key, {})) base = copy.deepcopy(self._shared_config.config.get(key, {}))
if isinstance(base, dict) and isinstance(val, dict): if isinstance(base, dict) and isinstance(val, dict):
# Credential isolation:
# If user defines engineer credentials, discard shared ones
if "engineer_api_key" in val or "engineer_auth" in val:
base.pop("engineer_api_key", None)
base.pop("engineer_auth", None)
# If user defines architect credentials, discard shared ones
if "architect_api_key" in val or "architect_auth" in val:
base.pop("architect_api_key", None)
base.pop("architect_auth", None)
# Recursive update for inner dictionaries (like mcp_servers or model details) # Recursive update for inner dictionaries (like mcp_servers or model details)
def deep_merge(d1, d2): def deep_merge(d1, d2):
for k, v in d2.items(): for k, v in d2.items():
+55
View File
@@ -160,3 +160,58 @@ def test_registry_injection_and_hot_reload(temp_config_dir):
ai_settings_updated = provider2.config.get_effective_setting("ai") ai_settings_updated = provider2.config.get_effective_setting("ai")
assert ai_settings_updated.get("engineer_api_key") == "global-updated-key" assert ai_settings_updated.get("engineer_api_key") == "global-updated-key"
assert ai_settings_updated.get("engineer_model") == "global-model" assert ai_settings_updated.get("engineer_model") == "global-model"
def test_shared_ai_credential_isolation(temp_config_dir):
"""Test that setting user engineer/architect credentials discards corresponding shared credentials."""
shared_dir = os.path.join(temp_config_dir, "shared_isolation")
user_dir = os.path.join(temp_config_dir, "user_isolation")
os.makedirs(shared_dir, exist_ok=True)
os.makedirs(user_dir, exist_ok=True)
shared_path = os.path.join(shared_dir, "config.yaml")
user_path = os.path.join(user_dir, "config.yaml")
# Shared has both api_key and auth
shared_data = {
"config": {
"ai": {
"engineer_api_key": "global-initial-key",
"engineer_auth": {"vertex_project": "shared-project", "api_key": "shared-auth-key"},
"architect_api_key": "global-arch-key",
"architect_auth": {"project": "arch-project"}
}
},
"connections": {},
"profiles": {}
}
with open(shared_path, "w") as f:
yaml.safe_dump(shared_data, f)
# User configures ONLY engineer_api_key (expects engineer_auth to be discarded)
# and ONLY architect_auth (expects architect_api_key to be discarded)
user_data = {
"config": {
"ai": {
"engineer_api_key": "user-custom-key",
"architect_auth": {"project": "user-project", "api_key": "user-auth-key"}
}
},
"connections": {},
"profiles": {}
}
with open(user_path, "w") as f:
yaml.safe_dump(user_data, f)
shared_config = configfile(conf=shared_path)
user_config = configfile(conf=user_path, shared_config=shared_config)
effective_ai = user_config.get_effective_setting("ai")
# 1. Engineer: local api_key is present, so shared engineer_auth must be completely discarded
assert effective_ai.get("engineer_api_key") == "user-custom-key"
assert "engineer_auth" not in effective_ai
# 2. Architect: local auth is present, so shared architect_api_key must be completely discarded
assert effective_ai.get("architect_auth") == {"project": "user-project", "api_key": "user-auth-key"}
assert "architect_api_key" not in effective_ai