Skip to content

fix: redact active dashboard TOTP secret - #10172

Open
iuiu-py wants to merge 1 commit into
AstrBotDevs:masterfrom
iuiu-py:fix/redact-totp-secret
Open

iuiu-py wants to merge 1 commit into
AstrBotDevs:masterfrom
iuiu-py:fix/redact-totp-secret

Conversation

@iuiu-py

@iuiu-py iuiu-py commented Sep 21, 2026

Copy link
Copy Markdown

Motivation

The dashboard config API returned the active TOTP secret after setup. An authenticated user could copy it from the manage dialog or provisioning QR and clone the authenticator. Fixes #8599.

Changes

  • Redact the active TOTP secret in system/profile config responses using a stable placeholder.
  • Restore the placeholder to the server-side secret before update comparison, so saving unrelated settings preserves TOTP without requiring a new code.
  • Remove the active secret and provisioning QR from the TOTP manage dialog while retaining rotation and recovery-code rotation.
  • Add API tests for response redaction and preservation on unrelated saves.

Testing

  • uv run pytest tests/test_dashboard.py -q (89 passed, 1 skipped)
  • Focused TOTP config-save regression tests passed before and after the fix
  • uv run ruff format ... && uv run ruff check ...
  • cd dashboard && npx pnpm@10 run typecheck
  • git diff --check

Summary by Sourcery

Protect active dashboard TOTP credentials while allowing non-security configuration changes to be saved safely.

Bug Fixes:

  • Prevent active TOTP secrets from being exposed through dashboard configuration responses or the TOTP management dialog.
  • Preserve the existing TOTP secret when users save unrelated dashboard settings.

Enhancements:

  • Retain TOTP rotation and recovery-code rotation while removing access to the active secret and provisioning QR code.

Tests:

  • Add API coverage for TOTP secret redaction and preservation during unrelated configuration saves.

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/dashboard/services/config_service.py" line_range="531" />
<code_context>
+    return response_config
+
+
+def _restore_redacted_totp_secret(config: dict, current_config: dict) -> None:
+    """Restore the server-side secret before comparing a redacted update."""
+    totp = config.get("dashboard", {}).get("totp")
+    if not isinstance(totp, dict):
+        return
</code_context>
<issue_to_address>
**issue (bug_risk):** `config.get("dashboard", {}).get("totp")` raises `AttributeError` when an update payload contains `"dashboard": None` or another non-dict value, so the API returns an unhandled server error instead of applying normal validation or returning a controlled client error.

**Triggers:** When a malformed or partially populated config payload supplies a non-dict `dashboard` value.

**Suggested fix:** Read `dashboard = config.get("dashboard")` first and return unless it is a dict before accessing `dashboard.get("totp")`.

```suggestion
    dashboard = config.get("dashboard")
    if not isinstance(dashboard, dict):
        return
    totp = dashboard.get("totp")
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and this changes how an active TOTP credential is exposed through configuration responses and how submitted configuration is persisted. If the redaction or restoration is wrong, the secret could be disclosed or replaced, and reverting would not undo any credential exposure or authentication disruption that already occurred.

Blocking findings: astrbot/dashboard/services/config_service.py:531


Sourcery is free for open source - if you like our reviews please consider sharing them ✨


def _restore_redacted_totp_secret(config: dict, current_config: dict) -> None:
"""Restore the server-side secret before comparing a redacted update."""
totp = config.get("dashboard", {}).get("totp")

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.

issue (bug_risk): config.get("dashboard", {}).get("totp") raises AttributeError when an update payload contains "dashboard": None or another non-dict value, so the API returns an unhandled server error instead of applying normal validation or returning a controlled client error.

Triggers: When a malformed or partially populated config payload supplies a non-dict dashboard value.

Suggested fix: Read dashboard = config.get("dashboard") first and return unless it is a dict before accessing dashboard.get("totp").

Suggested change
totp = config.get("dashboard", {}).get("totp")
dashboard = config.get("dashboard")
if not isinstance(dashboard, dict):
return
totp = dashboard.get("totp")

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.

fix: redact active dashboard TOTP secret from config responses

1 participant