Skip to content

feat(bridge-auth): send X-Admin-Token on all /xiaozhi/admin/* calls#152

Merged
BrettKinny merged 1 commit into
mainfrom
audit/bridge-admin-auth
Jun 6, 2026
Merged

feat(bridge-auth): send X-Admin-Token on all /xiaozhi/admin/* calls#152
BrettKinny merged 1 commit into
mainfrom
audit/bridge-admin-auth

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Admin-auth epic — part 4 of 4 (bridge caller, the scattered one). Pairs with the permissive /xiaozhi/admin/* middleware (#149). Off main. Completes the foundation.

Adds a _xiaozhi_admin_headers() helper (reads DOTTY_ADMIN_TOKEN into module-level _ADMIN_TOKEN) to both bridge.py and bridge/dashboard.py, threaded through every admin call site:

  • bridge.py (5 POSTs): _dispatch_abort / _dispatch_set_state / _dispatch_set_toggle, plus the _dashboard_abort_device / _dashboard_inject_to_device helpers.
  • dashboard.py (3 sites): _xiaozhi_device_count + _xiaozhi_list_songs (urllib GETs → urllib.request.Request with headers) and play_song's play-asset POST.

Calls to dotty-behaviour (vision photo proxy, perception getters) deliberately do not get the header. Header only sent when the token is set — no-op until the enforcement flip.

Tests: new tests/test_admin_headers.py (6 cases) — both helpers set/unset + an integration through _dispatch_abort capturing the headers kwarg. Full tests/ 71 passed; ruff clean.

Epic status

Foundation complete across all 4 services: #149 (server middleware) · #150 (behaviour) · #151 (pi-ext) · this (bridge).

Next — the enforcement flip (separate ops step, when you're ready): set the same DOTTY_ADMIN_TOKEN in all 4 container envs, deploy callers first (bridge/behaviour/pi-ext), xiaozhi last. Until then everything is a no-op.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings June 5, 2026 10:58
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR completes the bridge-side wiring for the admin-auth epic by conditionally attaching an X-Admin-Token header (sourced from DOTTY_ADMIN_TOKEN) to bridge→xiaozhi /xiaozhi/admin/* requests, staying a no-op until the coordinated enforcement flip.

Changes:

  • Add _xiaozhi_admin_headers() helpers (backed by module-level _ADMIN_TOKEN) in bridge.py and bridge/dashboard.py.
  • Thread the helper through all updated bridge/dashboard /xiaozhi/admin/* HTTP call sites (requests + urllib).
  • Add unit tests covering header helper behavior and one integration path through _dispatch_abort.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
bridge.py Adds admin-token helper and applies it to bridge-side admin POST dispatchers.
bridge/dashboard.py Adds admin-token helper and applies it to dashboard admin GET/POST calls (urllib + requests).
tests/test_admin_headers.py Adds tests validating helper behavior and header propagation via _dispatch_abort.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bridge.py
Comment on lines +169 to +176
_ADMIN_TOKEN = os.environ.get("DOTTY_ADMIN_TOKEN", "").strip()


def _xiaozhi_admin_headers() -> dict:
"""X-Admin-Token header for /xiaozhi/admin/* requests when DOTTY_ADMIN_TOKEN
is set (matches the xiaozhi-server middleware); empty dict otherwise, so the
bridge is a no-op until the coordinated enforcement flip."""
return {"X-Admin-Token": _ADMIN_TOKEN} if _ADMIN_TOKEN else {}
Comment thread bridge.py
_ADMIN_TOKEN = os.environ.get("DOTTY_ADMIN_TOKEN", "").strip()


def _xiaozhi_admin_headers() -> dict:
Admin-auth epic, part 4 of 4 (bridge caller — the scattered one). Pairs with the
permissive /xiaozhi/admin/* middleware (part 1). Adds a `_xiaozhi_admin_headers()`
helper (reads DOTTY_ADMIN_TOKEN into a module-level `_ADMIN_TOKEN`) to both
bridge.py and bridge/dashboard.py, threaded through every admin call site:

- bridge.py: _dispatch_abort / _dispatch_set_state / _dispatch_set_toggle and the
  dashboard _dashboard_abort_device / _dashboard_inject_to_device helpers (5 POSTs).
- dashboard.py: _xiaozhi_device_count + _xiaozhi_list_songs (urllib GETs, now via
  urllib.request.Request with headers) and play_song's play-asset POST (3 sites).

Calls to dotty-behaviour (vision photo proxy, perception getters) are deliberately
NOT given the header. Header only sent when the token is set — no-op until the flip.

Tests: new tests/test_admin_headers.py (6 cases) — both helpers set/unset, and an
integration through _dispatch_abort capturing the headers kwarg. Full tests/ 71
passed; ruff clean.

Completes the foundation: #149 (server) + #150 (behaviour) + #151 (pi-ext) + this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BrettKinny BrettKinny force-pushed the audit/bridge-admin-auth branch from e579b98 to 30706cc Compare June 6, 2026 11:17
@BrettKinny BrettKinny merged commit 0214e96 into main Jun 6, 2026
9 checks passed
@BrettKinny BrettKinny deleted the audit/bridge-admin-auth branch June 6, 2026 11: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