Skip to content

Add managed server hostname setting#46

Merged
chrismin13 merged 3 commits into
mainfrom
feature/server-identity-hostname
May 4, 2026
Merged

Add managed server hostname setting#46
chrismin13 merged 3 commits into
mainfrom
feature/server-identity-hostname

Conversation

@chrismin13
Copy link
Copy Markdown
Owner

@chrismin13 chrismin13 commented May 4, 2026

Summary by CodeRabbit

  • New Features

    • Server Identity UI: view and edit the server name in Network File Sharing with inline validation and warning that saving may restart discovery and interrupt clients.
    • Alert subjects now include the server name for easier identification.
  • Documentation

    • Expanded Setup, Network File Sharing, Uninstall, and design notes explaining server name behavior and hostname handling.
  • Tests

    • Added test coverage for server identity flows and related APIs.

Copilot AI review requested due to automatic review settings May 4, 2026 19:26
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

📝 Walkthrough
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature added: managed server hostname setting functionality is implemented across new services, routes, command adapters, and UI components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/server-identity-hostname

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chrismin13
Copy link
Copy Markdown
Owner Author

@CodeRabbit help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai rate limit to show your current review rate limit status.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
templates/network_file_sharing.html (1)

424-434: 💤 Low value

data.hostname fallback may be dead code — add a brief comment if intentional

currentServerName = data.server_name || data.hostname || '';

The API contract (GET /api/server_identity) returns a server_name field. If data.hostname is a fallback for an older or alternative API response shape, a one-line comment explaining the intent would avoid confusion months from now. If it's genuinely unused, remove it. As per coding guidelines, sprinkle comments on things we might forget.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/network_file_sharing.html` around lines 424 - 434, In
loadServerIdentity, clarify or remove the apparent dead fallback data.hostname
used in the assignment to currentServerName (currentServerName =
data.server_name || data.hostname || ''); either remove data.hostname if unused
by the API, or add a one-line comment immediately above that assignment
explaining that data.hostname is an intentional backward-compatibility fallback
for older API responses; update the related references (serverNameValue and
serverNameInput usage) only if you remove the fallback to ensure behavior
remains consistent.
tests/test_server_identity_routes.py (2)

8-55: ⚡ Quick win

Extract shared fake-mode app setup into a pytest fixture to eliminate ~25 lines of duplication

Both test functions are identical from lines 9–36 / 59–86 (save/restore globals, patch env vars, reset runtime, create app, create admin user) — only the request body and assertions differ. A shared fixture removes the duplication and ensures both tests get the same environment.

♻️ Suggested fixture-based refactor
import pytest

`@pytest.fixture`
def fake_app_client(tmp_path):
    """Spin up a fake-mode app with a temp data dir and a single admin user.
    Resets the module-level runtime singletons so get_runtime() re-initialises
    from the patched environment variables on each call.
    """
    import os
    from tempfile import TemporaryDirectory
    from unittest.mock import patch
    from simple_safer_server.services import runtime as _runtime_mod

    previous_runtime = _runtime_mod._runtime
    previous_fake_state = _runtime_mod._fake_state
    try:
        with TemporaryDirectory() as temp_dir:
            with patch.dict(
                os.environ,
                {"SSS_MODE": "fake", "SSS_SKIP_LOGIN": "true", "SSS_DATA_DIR": temp_dir},
                clear=False,
            ):
                # Force get_runtime() to create a new Runtime from the patched env vars.
                _runtime_mod._runtime = None
                _runtime_mod._fake_state = None
                from simple_safer_server.app_factory import create_app

                app, _ = create_app()
                app.config["TESTING"] = True

                with app.app_context():
                    svc = app.extensions["simple_safer_server"]
                    svc.config_manager.set_value("system", "setup_complete", "true")
                    svc.config_manager.set_value("system", "username", "admin")
                    ok, msg = svc.user_manager.create_user("admin", "password", is_admin=True)
                    assert ok, msg

                with app.test_client() as client:
                    yield app, client
    finally:
        _runtime_mod._runtime = previous_runtime
        _runtime_mod._fake_state = previous_fake_state


def test_server_identity_api_updates_fake_mode_config(fake_app_client):
    app, client = fake_app_client
    response = client.put("/api/server_identity", json={"server_name": "Family-NAS"})
    assert response.status_code == 200
    assert response.get_json()["data"]["server_name"] == "family-nas"

    with app.app_context():
        svc = app.extensions["simple_safer_server"]
        assert svc.config_manager.get_value("system", "server_name") == "family-nas"


def test_server_identity_api_rejects_invalid_name_in_fake_mode(fake_app_client):
    _, client = fake_app_client
    response = client.put("/api/server_identity", json={"server_name": "bad name"})
    assert response.status_code == 400
    assert response.get_json()["detail"].startswith("Server name may only contain")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_server_identity_routes.py` around lines 8 - 55, Extract the
duplicated fake-mode setup into a pytest fixture named fake_app_client: move the
TemporaryDirectory/patch.dict block and the runtime reset/restore of
runtime._runtime and runtime._fake_state into the fixture, call create_app() and
create the admin user inside the fixture, yield (app, client) from it, and
restore the previous runtime values in the fixture's finally block; then
simplify the tests test_server_identity_api_updates_fake_mode_config and
test_server_identity_api_rejects_invalid_name_in_fake_mode to accept
fake_app_client, unpack app and client, perform only the request/assertions, and
reference the same services via app.extensions["simple_safer_server"] as before.

8-98: ⚡ Quick win

Missing test for GET /api/server_identity and for a missing server_name key

The test suite covers only the PUT path. The GET endpoint (api_get_server_identity) has separate code paths (including an except Exception handler) with no coverage. A basic GET test after the PUT would also confirm the value is readable and the response shape is correct.

Similarly, sending {} (valid JSON but no server_name key) exercises the data.get("server_name") → None path that goes through normalize_server_name(None) — worth a one-liner assertion to lock in that behaviour.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_server_identity_routes.py` around lines 8 - 98, Add two
assertions/tests to cover the GET path and the missing server_name case: after
the successful PUT in test_server_identity_api_updates_fake_mode_config call GET
"/api/server_identity" via the same test_client and assert status_code == 200
and the JSON shape includes data.server_name == "family-nas" (verifies
api_get_server_identity path and response shape); also add a small test (or
extend test_server_identity_api_rejects_invalid_name_in_fake_mode) that sends
PUT "/api/server_identity" with json={} and assert the response is a 400 and the
error detail matches the expected message when data.get("server_name") is None
(exercises normalize_server_name(None) path). Ensure you reuse
create_app/app.test_client and the same setup code (services.config_manager and
creating admin) so tests run in the fake mode context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/network_file_sharing.md`:
- Around line 4-5: Confirm whether the server name is actually used in alert
emails in the alert-email implementation, and then update both the docs
reference and the template hint consistently: if alert emails do not include the
server name, remove or reword the phrase "and in alert emails" in
docs/network_file_sharing.md (the sentence mentioning server name usage) and in
templates/network_file_sharing.html's form-hint (the line flagged near the
server name hint); if they do include it, add a brief note in both places
describing where/how the server name appears in alerts. Ensure both files use
identical wording so the documentation and UI hint remain consistent with the
implementation.

In `@simple_safer_server/routes/server_identity.py`:
- Around line 31-50: Hoist the call to json_request_data() out of the try block
so client validation errors aren't swallowed: call data = json_request_data()
before entering the try that wraps
_get_services().server_identity_service.update_server_name(...),
ServerIdentityError handling, and the generic Exception handler; leave the
existing ServerIdentityError except (ServerIdentityError as exc ->
json_problem(ValidationProblem(...))) and the final except Exception block
intact so only server-side errors are converted to OperationProblem.

In `@templates/network_file_sharing.html`:
- Around line 120-123: Remove the incorrect claim that the server name "appears
in alert emails" by deleting that clause from the form hint associated with the
serverNameInput (the form-hint text), from the SERVER_NAME_HELP_TEXT constant in
server identity code, and from the network file sharing documentation
(network_file_sharing.md); update the texts so they only describe how the server
name is used on the network and in UI, or replace the clause with a qualified
statement if you prefer (e.g., "may be referenced in notifications" only if you
add that behavior later).

---

Nitpick comments:
In `@templates/network_file_sharing.html`:
- Around line 424-434: In loadServerIdentity, clarify or remove the apparent
dead fallback data.hostname used in the assignment to currentServerName
(currentServerName = data.server_name || data.hostname || ''); either remove
data.hostname if unused by the API, or add a one-line comment immediately above
that assignment explaining that data.hostname is an intentional
backward-compatibility fallback for older API responses; update the related
references (serverNameValue and serverNameInput usage) only if you remove the
fallback to ensure behavior remains consistent.

In `@tests/test_server_identity_routes.py`:
- Around line 8-55: Extract the duplicated fake-mode setup into a pytest fixture
named fake_app_client: move the TemporaryDirectory/patch.dict block and the
runtime reset/restore of runtime._runtime and runtime._fake_state into the
fixture, call create_app() and create the admin user inside the fixture, yield
(app, client) from it, and restore the previous runtime values in the fixture's
finally block; then simplify the tests
test_server_identity_api_updates_fake_mode_config and
test_server_identity_api_rejects_invalid_name_in_fake_mode to accept
fake_app_client, unpack app and client, perform only the request/assertions, and
reference the same services via app.extensions["simple_safer_server"] as before.
- Around line 8-98: Add two assertions/tests to cover the GET path and the
missing server_name case: after the successful PUT in
test_server_identity_api_updates_fake_mode_config call GET
"/api/server_identity" via the same test_client and assert status_code == 200
and the JSON shape includes data.server_name == "family-nas" (verifies
api_get_server_identity path and response shape); also add a small test (or
extend test_server_identity_api_rejects_invalid_name_in_fake_mode) that sends
PUT "/api/server_identity" with json={} and assert the response is a 400 and the
error detail matches the expected message when data.get("server_name") is None
(exercises normalize_server_name(None) path). Ensure you reuse
create_app/app.test_client and the same setup code (services.config_manager and
creating admin) so tests run in the fake mode context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2fe8ce2-ef77-41d2-a5cc-13034dc5ec38

📥 Commits

Reviewing files that changed from the base of the PR and between 7445c76 and 2b799fb.

📒 Files selected for processing (19)
  • docs/network_file_sharing.md
  • docs/setup.md
  • docs/uninstall.md
  • notes/0027-cloud-backup-setup-service-refactor.md
  • notes/0028-server-identity-hostname.md
  • simple_safer_server/adapters/server_identity_commands.py
  • simple_safer_server/app_factory.py
  • simple_safer_server/routes/server_identity.py
  • simple_safer_server/routes/setup_wizard.py
  • simple_safer_server/services/container.py
  • simple_safer_server/services/server_identity.py
  • static/css/styles.css
  • templates/network_file_sharing.html
  • templates/setup.html
  • tests/test_server_identity.py
  • tests/test_server_identity_routes.py
  • tests/test_setup_wizard.py
  • tests/test_uninstall.py
  • uninstall.sh

Comment thread docs/network_file_sharing.md Outdated
Comment thread simple_safer_server/routes/server_identity.py
Comment thread templates/network_file_sharing.html
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@chrismin13
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
simple_safer_server/routes/setup_wizard.py (1)

76-87: 💤 Low value

Inconsistent fallback handling between service helpers.

_cloud_backup_service() has no fallback and will raise KeyError if the extension isn't registered, while _server_identity_service() gracefully falls back to the module-level instance. This asymmetry is likely intentional (cloud backup is always app-factory managed), but consider adding a brief comment on Line 78 explaining why no fallback is needed here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@simple_safer_server/routes/setup_wizard.py` around lines 76 - 87, The helper
_cloud_backup_service currently accesses
current_app.extensions["simple_safer_server"] without a fallback which differs
from _server_identity_service; add a short explanatory comment above the
_cloud_backup_service function stating that the cloud-backup service is always
registered by the app factory (so absence is a configuration error and a
KeyError is intentional) and therefore no module-level fallback like
server_identity_service is provided, referencing the function name
_cloud_backup_service and the contrasted helper _server_identity_service to make
intent clear for future readers.
simple_safer_server/services/drive_health.py (1)

801-804: 💤 Low value

Add a comment explaining the double-default pattern.

The outer or "SimpleSaferServer" guard handles the case where the config key exists but is explicitly None or an empty string (the inner get_value default only fires when the key is absent). This is non-obvious and easy to wonder about on a future read. Per coding guidelines, things we might forget in a few months should have a brief comment.

💡 Suggested comment
+    # get_value's default covers a missing key; the `or` covers an explicitly
+    # blank/None value stored in config.
     server_name = (
         config_manager.get_value("system", "server_name", "SimpleSaferServer")
         or "SimpleSaferServer"
     ).strip()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@simple_safer_server/services/drive_health.py` around lines 801 - 804, Add a
brief inline comment above the server_name assignment explaining the
double-default pattern: note that config_manager.get_value("system",
"server_name", "SimpleSaferServer") provides a default only when the key is
missing, whereas the trailing or "SimpleSaferServer" handles cases where the key
exists but is None or an empty string, and then .strip() is applied; reference
the server_name variable and the config_manager.get_value call so future readers
understand why both defaults are present.
tests/test_drive_health.py (1)

173-197: 💤 Low value

Consider using call_args.args[2] and adding a default-fallback test.

Two minor improvements worth considering:

  1. Accessor stylecall_args[0] (positional args tuple) and call_args.args are identical references, but call_args.args[2] is the more explicit, named form and avoids the magic numeric index into the outer tuple.

  2. Missing coverage for the default path — the test only exercises the configured-name path ("nas-01"). A second test (or a subTest) that omits ("system", "server_name") from the mock map would verify that the double-default falls back correctly to "SimpleSaferServer" in the subject line.

✨ Suggested improvements
-        self.assertIn("Subject: Drive health - nas-01", mock_send_email.call_args[0][2])
+        self.assertIn("Subject: Drive health - nas-01", mock_send_email.call_args.args[2])

Additional test for the default fallback:

`@patch`(
    "simple_safer_server.services.drive_health.drive_health_command_adapter.send_email",
    side_effect=TimeoutExpired(cmd=["msmtp"], timeout=30),
)
def test_log_and_email_alert_uses_default_server_name(self, mock_send_email):
    config_manager = SimpleNamespace(
        log_alert=MagicMock(),
        get_value=lambda section, key, default="": {
            ("backup", "email_address"): "admin@example.com",
            ("backup", "from_address"): "server@example.com",
            # ("system", "server_name") intentionally absent → default kicks in
        }.get((section, key), default),
    )
    runtime = SimpleNamespace(is_fake=False)

    drive_health._log_and_email_alert(
        config_manager,
        runtime,
        "Drive health",
        "message",
        alert_type="warning",
        source="drive_health",
    )

    self.assertIn(
        "Subject: Drive health - SimpleSaferServer",
        mock_send_email.call_args.args[2],
    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_drive_health.py` around lines 173 - 197, Update the test to use
the explicit call-args accessor and add coverage for the default server name:
replace mock_send_email.call_args[0][2] with mock_send_email.call_args.args[2]
when asserting the Subject line, and add a second test (or subTest) that
constructs the config_manager.get_value map without the ("system","server_name")
entry and calls drive_health._log_and_email_alert (with runtime
SimpleNamespace(is_fake=False)) to assert that mock_send_email.call_args.args[2]
contains "Subject: Drive health - SimpleSaferServer".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@simple_safer_server/routes/setup_wizard.py`:
- Around line 935-938: The responses for related endpoints are inconsistent:
update mega_connect to return the full MegaFolderList-shaped object (not just
{"folders": [...]}) so it matches mega_list_folders; specifically, in the
mega_connect handler build/return the same MegaFolderList structure (including
"folders", "path", and "parent")—use the same
_cloud_backup_service().list_mega_folders call or construct a MegaFolderList
dataclass and pass it through the same serialize/json helper (e.g.,
serialize_api_data/json_data) so both mega_connect and mega_list_folders produce
the same response shape.

In `@simple_safer_server/services/drive_health.py`:
- Around line 801-809: The server_name value retrieved via
config_manager.get_value and used when constructing email_body must have any
CR/LF characters removed to prevent header injection; instead of only calling
.strip(), sanitize server_name (the variable assigned from
config_manager.get_value(...)) by using the existing re import to remove or
replace any \r or \n characters (e.g., re.sub to replace them with a single
space) before embedding it into the Subject header in the email_body
construction.

---

Nitpick comments:
In `@simple_safer_server/routes/setup_wizard.py`:
- Around line 76-87: The helper _cloud_backup_service currently accesses
current_app.extensions["simple_safer_server"] without a fallback which differs
from _server_identity_service; add a short explanatory comment above the
_cloud_backup_service function stating that the cloud-backup service is always
registered by the app factory (so absence is a configuration error and a
KeyError is intentional) and therefore no module-level fallback like
server_identity_service is provided, referencing the function name
_cloud_backup_service and the contrasted helper _server_identity_service to make
intent clear for future readers.

In `@simple_safer_server/services/drive_health.py`:
- Around line 801-804: Add a brief inline comment above the server_name
assignment explaining the double-default pattern: note that
config_manager.get_value("system", "server_name", "SimpleSaferServer") provides
a default only when the key is missing, whereas the trailing or
"SimpleSaferServer" handles cases where the key exists but is None or an empty
string, and then .strip() is applied; reference the server_name variable and the
config_manager.get_value call so future readers understand why both defaults are
present.

In `@tests/test_drive_health.py`:
- Around line 173-197: Update the test to use the explicit call-args accessor
and add coverage for the default server name: replace
mock_send_email.call_args[0][2] with mock_send_email.call_args.args[2] when
asserting the Subject line, and add a second test (or subTest) that constructs
the config_manager.get_value map without the ("system","server_name") entry and
calls drive_health._log_and_email_alert (with runtime
SimpleNamespace(is_fake=False)) to assert that mock_send_email.call_args.args[2]
contains "Subject: Drive health - SimpleSaferServer".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c391b51-cdaf-4637-a9b5-1ab463b22533

📥 Commits

Reviewing files that changed from the base of the PR and between 2b799fb and bb0306a.

📒 Files selected for processing (9)
  • docs/network_file_sharing.md
  • simple_safer_server/routes/server_identity.py
  • simple_safer_server/routes/setup_wizard.py
  • simple_safer_server/services/drive_health.py
  • simple_safer_server/services/server_identity.py
  • templates/network_file_sharing.html
  • templates/setup.html
  • tests/test_drive_health.py
  • tests/test_server_identity_routes.py
✅ Files skipped from review due to trivial changes (2)
  • docs/network_file_sharing.md
  • templates/network_file_sharing.html
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/test_server_identity_routes.py
  • templates/setup.html
  • simple_safer_server/routes/server_identity.py
  • simple_safer_server/services/server_identity.py

Comment on lines +935 to +938
folder_list = _cloud_backup_service().list_mega_folders(
{'email': email, 'password': password, 'path': path}
)
return json_data(folder_list)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the return type of list_mega_folders and json_data signature
echo "=== Checking list_mega_folders return type ==="
rg -n -A 10 'def list_mega_folders' --type py

echo "=== Checking json_data signature ==="
rg -n -A 5 'def json_data' --type py

Repository: chrismin13/SimpleSaferServer

Length of output: 2579


🏁 Script executed:

#!/bin/bash
echo "=== Checking serialize_api_data implementation ==="
rg -n -A 15 'def serialize_api_data' --type py

echo "=== Checking MegaFolderList class definition ==="
rg -n -B 2 -A 10 'class MegaFolderList' --type py

echo "=== Checking mega_connect return format (line 916) ==="
rg -n -B 5 -A 2 'mega_connect' simple_safer_server/routes/setup_wizard.py | head -40

Repository: chrismin13/SimpleSaferServer

Length of output: 2693


🏁 Script executed:

#!/bin/bash
echo "=== Checking mega_connect function (around line 916) ==="
sed -n '903,925p' simple_safer_server/routes/setup_wizard.py

echo "=== Checking mega_list_folders function (around line 935) ==="
sed -n '925,945p' simple_safer_server/routes/setup_wizard.py

Repository: chrismin13/SimpleSaferServer

Length of output: 1840


Inconsistent API response structure between mega_connect and mega_list_folders.

mega_connect returns {"data": {"folders": [...]}} while mega_list_folders returns {"data": {"folders": [...], "path": "...", "parent": "..."}}. Although serialize_api_data will successfully convert the MegaFolderList dataclass, the response shapes differ, which may confuse API clients expecting a consistent structure across related endpoints.

Consider either updating mega_connect to return the full MegaFolderList object for consistency, or document why the endpoints intentionally return different shapes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@simple_safer_server/routes/setup_wizard.py` around lines 935 - 938, The
responses for related endpoints are inconsistent: update mega_connect to return
the full MegaFolderList-shaped object (not just {"folders": [...]}) so it
matches mega_list_folders; specifically, in the mega_connect handler
build/return the same MegaFolderList structure (including "folders", "path", and
"parent")—use the same _cloud_backup_service().list_mega_folders call or
construct a MegaFolderList dataclass and pass it through the same serialize/json
helper (e.g., serialize_api_data/json_data) so both mega_connect and
mega_list_folders produce the same response shape.

Comment on lines +801 to +809
server_name = (
config_manager.get_value("system", "server_name", "SimpleSaferServer")
or "SimpleSaferServer"
).strip()
if not email_address or not from_address:
LOGGER.warning("Skipping email alert because email settings are incomplete.")
return

email_body = f"Subject: {title}\nFrom: {from_address}\n\n{message}"
email_body = f"Subject: {title} - {server_name}\nFrom: {from_address}\n\n{message}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strip CRLF from server_name before embedding it in the email Subject header.

.strip() only trims leading/trailing whitespace. If a newline is present anywhere inside the stored value (e.g., "nas-01\nBcc: attacker@example.com"), it will split the Subject: line and inject arbitrary headers into the raw email string built at line 809. Even though server_name is admin-configured, defence-in-depth applies.

🛡️ Proposed fix
-    server_name = (
-        config_manager.get_value("system", "server_name", "SimpleSaferServer")
-        or "SimpleSaferServer"
-    ).strip()
+    # Read the configured server name; fall back to "SimpleSaferServer" if the
+    # key is absent or explicitly blank. Strip whitespace and control characters
+    # (including embedded newlines) to prevent email-header injection.
+    server_name = re.sub(
+        r"[\r\n]",
+        "",
+        (
+            config_manager.get_value("system", "server_name", "SimpleSaferServer")
+            or "SimpleSaferServer"
+        ).strip(),
+    )

re is already imported at the top of the module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@simple_safer_server/services/drive_health.py` around lines 801 - 809, The
server_name value retrieved via config_manager.get_value and used when
constructing email_body must have any CR/LF characters removed to prevent header
injection; instead of only calling .strip(), sanitize server_name (the variable
assigned from config_manager.get_value(...)) by using the existing re import to
remove or replace any \r or \n characters (e.g., re.sub to replace them with a
single space) before embedding it into the Subject header in the email_body
construction.

@chrismin13 chrismin13 closed this May 4, 2026
@chrismin13 chrismin13 reopened this May 4, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@chrismin13 chrismin13 merged commit 05cb76b into main May 4, 2026
5 checks passed
@chrismin13 chrismin13 deleted the feature/server-identity-hostname branch May 10, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants