Add a persistent authentication secret to SerenaConfig

- Generate a random UUID when auth_secret is missing, null, or empty.
- Persist generated secrets and preserve configured values across reloads.
- Document the shared secret and test generation, persistence, and direct construction.
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-17 14:28:02 +02:00
1 parent 613f2ca098
commit 5a2063fc3a
4 files changed
+49

No files matched your search

+3
View File
@@ -12,6 +12,9 @@ Status of the `main` branch. Changes prior to the next official version change w
see `CONTRIBUTING.md`
* General:
- Add `auth_secret` to `serena_config.yml` for authenticating communication between Serena components
and services. When missing, null, or empty, a random UUID is generated and persisted; existing values
are preserved
- Fix: MCP `initialize` now reports Serena's version instead of the installed mcp SDK version (#1889)
- Fix: Parallel agents auto-registering projects could overwrite each other's changes to the global
project list in `serena_config.yml`
+11
View File
@@ -16,6 +16,7 @@ from enum import Enum
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, Self, TypeVar
from uuid import uuid4
import yaml
from ruamel.yaml.comments import CommentedMap
@@ -945,6 +946,11 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
# *** fields that are mapped directly to/from the configuration file (DO NOT RENAME) ***
projects: list[RegisteredProject] = field(default_factory=list)
auth_secret: str = field(default_factory=lambda: str(uuid4()), repr=False)
"""
shared secret for authenticating communication between Serena components and services.
A random UUID is generated and persisted when the configuration setting is missing or empty.
"""
gui_log_window: bool = False
log_level: int = logging.INFO
trace_lsp_communication: bool = False
@@ -1138,6 +1144,11 @@ class SerenaConfig(SharedConfig, ModeSelectionDefinitionWithBaseModes):
assert hasattr(instance, field_name)
setattr(instance, field_name, get_value_or_default(field_name))
# generate a persistent authentication secret for explicitly unset settings
if not instance.auth_secret:
instance.auth_secret = str(uuid4())
num_migrations += 1
# read projects
if "projects" not in loaded_commented_yaml:
raise SerenaConfigError("`projects` key not found in Serena configuration. Please update your `serena_config.yml` file.")
@@ -235,5 +235,9 @@ project_serena_folder_location: "$projectDir/.serena"
# The pattern "**" matches any project path, so it can be used to trust all projects.
trusted_project_path_patterns: []
# shared secret for authenticating communication between Serena components and services.
# Keep this value private. When missing, null, or empty, a random UUID is generated and saved on load.
auth_secret:
# the list of registered project paths (updated automatically).
projects: []
+31
View File
@@ -4,6 +4,7 @@ import shutil
import tempfile
from copy import deepcopy
from pathlib import Path
from uuid import UUID
import pytest
@@ -635,6 +636,36 @@ class TestSerenaConfigLoadSave:
config = SerenaConfig.from_config_file(generate_if_missing=False)
assert config.projects == []
@pytest.mark.parametrize("setting", ["", "auth_secret: null\n", 'auth_secret: ""\n'])
def test_unset_auth_secret_is_generated_and_persisted(self, setting: str) -> None:
# load an existing configuration without a usable secret
self.master_config_path.write_text("projects: []\n" + setting)
config = SerenaConfig.from_config_file(generate_if_missing=False)
# subsequent loads retain the generated random UUID
assert UUID(config.auth_secret).version == 4
assert SerenaConfig.from_config_file(generate_if_missing=False).auth_secret == config.auth_secret
def test_configured_auth_secret_is_preserved(self) -> None:
# retain a user-provided secret across loading and migration
self.master_config_path.write_text("projects: []\nauth_secret: custom-secret\n")
assert SerenaConfig.from_config_file(generate_if_missing=False).auth_secret == "custom-secret"
assert SerenaConfig.from_config_file(generate_if_missing=False).auth_secret == "custom-secret"
def test_new_config_has_persistent_auth_secret(self) -> None:
# generate the configuration from the template and retain its secret
config = SerenaConfig.from_config_file()
assert UUID(config.auth_secret).version == 4
assert SerenaConfig.from_config_file().auth_secret == config.auth_secret
def test_direct_config_instances_have_distinct_auth_secrets(self) -> None:
# directly constructed configurations receive independent secrets
first = SerenaConfig()
second = SerenaConfig()
assert UUID(first.auth_secret).version == 4
assert UUID(second.auth_secret).version == 4
assert first.auth_secret != second.auth_secret
def test_malformed_project_is_skipped_with_warning(self, caplog):
"""A malformed project.yml must not abort loading of the others."""
good_project = self._make_project_dir(