fix(dashboard): profile-scope Channels endpoints and seed per-profile .env (#44792)
Two halves of the same community report (dashboard Profile Builder): 1. A fresh dashboard/CLI-created profile got no .env file unless cloned, so it silently inherited API keys and messaging tokens from the shell environment / root install. create_profile() now seeds a placeholder .env (0600) for non-clone profiles, matching the SOUL.md seeding. 2. The Channels endpoints (/api/messaging/platforms GET/PUT/test) were not profile-scoped: they read/wrote the dashboard process's own .env via load_env()/save_env_value() regardless of the global profile switcher. They now accept the standard optional profile param (body beats query on the PUT, matching other scoped writes) and run inside _profile_scope(). When scoped, the payload no longer falls back to os.environ or load_gateway_config()'s env-override layer — both carry the ROOT install's credentials and would misreport them as the profile's. /api/messaging/platforms added to PROFILE_SCOPED_PREFIXES so the sidebar switcher scopes the Channels page automatically.
This commit is contained in:
@@ -158,6 +158,30 @@ class TestCreateProfile:
|
||||
"plans", "workspace", "cron"]:
|
||||
assert (profile_dir / subdir).is_dir(), f"Missing subdir: {subdir}"
|
||||
|
||||
def test_seeds_placeholder_env_file(self, profile_env):
|
||||
"""Fresh profiles get their own .env (owner-only) so channel/env
|
||||
writes are profile-scoped from day one instead of falling through
|
||||
to the shell environment / root install."""
|
||||
import stat
|
||||
profile_dir = create_profile("coder", no_alias=True)
|
||||
env_path = profile_dir / ".env"
|
||||
assert env_path.exists()
|
||||
content = env_path.read_text(encoding="utf-8")
|
||||
# Placeholder only — no credentials leak in from anywhere.
|
||||
assert all(
|
||||
line.startswith("#") or not line.strip()
|
||||
for line in content.splitlines()
|
||||
)
|
||||
mode = stat.S_IMODE(env_path.stat().st_mode)
|
||||
assert mode == 0o600
|
||||
|
||||
def test_seeded_env_does_not_clobber_cloned_env(self, profile_env):
|
||||
tmp_path = profile_env
|
||||
default_home = tmp_path / ".hermes"
|
||||
(default_home / ".env").write_text("KEY=val")
|
||||
profile_dir = create_profile("coder", clone_config=True, no_alias=True)
|
||||
assert (profile_dir / ".env").read_text() == "KEY=val"
|
||||
|
||||
def test_duplicate_raises_file_exists(self, profile_env):
|
||||
create_profile("coder", no_alias=True)
|
||||
with pytest.raises(FileExistsError):
|
||||
@@ -304,7 +328,9 @@ class TestCreateProfile:
|
||||
profile_dir = create_profile("coder", clone_config=True, no_alias=True)
|
||||
# No error; optional files just not copied
|
||||
assert not (profile_dir / "config.yaml").exists()
|
||||
assert not (profile_dir / ".env").exists()
|
||||
# .env is always seeded (placeholder) so the profile has its own
|
||||
# credentials file even when the clone source lacked one.
|
||||
assert (profile_dir / ".env").exists()
|
||||
# SOUL.md is always seeded with the default even when clone source lacks it
|
||||
assert (profile_dir / "SOUL.md").exists()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user