Skip to content

feat(server): separate admin token for admin operations (#154 phase 1) - #194

Merged
jaylfc merged 1 commit into
masterfrom
feat/admin-token-separation
Jul 11, 2026
Merged

feat(server): separate admin token for admin operations (#154 phase 1)#194
jaylfc merged 1 commit into
masterfrom
feat/admin-token-separation

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Closes #154 (phase 1). HELD for Jay's review, do not merge.

What #154 asked

The server token (server_token / TAOSMD_TOKEN), when set, gates every data and A2A endpoint via _check_token. The admin surface (PR #153) fails closed and required that same server token. So on a free-handle (token-less) deployment the only way to authorize an admin op was to set a server token, which simultaneously locked out every agent on the data plane for the duration of the admin window. This hit the Pi bus in production for about three minutes during a channel cleanup.

The requested fix: an admin_token config value distinct from server_token, checked by _check_admin_token, preferring admin_token and falling back to server_token for migration. Only-admin_token set keeps the data plane open while the admin surface works; both set keeps current data-plane behavior.

Design note: "never lock the data plane" is the AUTH lockout

Re-reading the issue, the "lock" is the auth coupling, not the service loop. Today you cannot gate admin without also gating the data plane because they share one token. A separate admin token removes that coupling. This is fully solved by token separation; it does not require moving admin ops off the single asyncio service loop.

What phase 1 builds

  • config.get_admin_token / set_admin_token (env TAOSMD_ADMIN_TOKEN, config key admin_token), exported and mirrored by taosmd config set-admin-token plus a config show line.
  • _check_admin_token now uses expected = admin_token or server_token (prefer admin, fall back to server), still fail-closed (403) when neither is set.
  • The admin write routes (POST /shelves, POST /shelves/{id}/archive|unarchive, POST /a2a/admin/delete-channel|rename-channel|supersede-message) are exempt from the data-plane _check_token gate in _dispatch via a new _is_admin_route. They already enforce the stricter, fail-closed _check_admin_token themselves, so the exemption opens no hole; it is exactly what decouples admin auth from the data plane. _check_token (the data plane) is unchanged.

Behavior matrix:

server_token admin_token data plane admin surface
unset unset open fails closed (403)
set unset gated by server_token gated by server_token (back-compat)
unset set open gated by admin_token
set set gated by server_token gated by admin_token (server-token-only caller rejected)

Back-compat

Existing token-secured installs (only server_token set) are unchanged: the admin surface still requires the server token and still fails closed with no token. No config migration needed.

Deferred to phase 2 (not built)

Isolating admin operations from the single _ServiceLoop: a slow or heavy admin op marshalled onto the one asyncio loop (via runner.run) can still stall data reads/writes while it executes. #154 does not ask for this and it is a larger architectural change (a second loop/worker, ordering and SQLite-connection-ownership questions). In practice the current admin ops are short metadata updates, so the acute pain (the 3-minute lockout) is the auth coupling, which phase 1 fixes. Recommend tracking phase 2 as its own issue.

Tests

  • tests/test_admin_token_separation.py (live server): admin token accepted when only admin_token set; data plane open (tokenless ingest succeeds) when only admin_token set; admin fails closed (401) without the admin token; with both set a server-token-only caller is rejected on admin while the admin token works; server token still gates admin when no admin token (back-compat); fails closed (403) when neither set.
  • tests/test_config_admin_token.py: env-over-file precedence, round-trip, clear, empty-string raises, independence from server_token.
  • tests/test_admin_surface.py: updated the 403 message assertion to the new wording.

Full suite: 1104 passed.

Summary by CodeRabbit

  • New Features

    • Added separate admin-token configuration for protected administrative operations.
    • Added CLI commands to set, clear, and view admin-token status.
    • Admin endpoints now fail safely with access denied when no valid admin credentials are configured.
    • Data-plane and administrative authentication can now be configured independently.
  • Documentation

    • Documented admin routes, token behavior, fallback rules, and security requirements.
  • Tests

    • Added coverage for token configuration, separation, authorization, and backward-compatible fallback behavior.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Admin token configuration is added separately from the server token. CLI and config APIs manage it, while HTTP admin routes prefer it, fall back to the server token, and bypass data-plane gating. Tests and documentation cover the new behavior.

Changes

Admin token separation

Layer / File(s) Summary
Token configuration and CLI
taosmd/config.py, taosmd/cli.py, tests/test_config_admin_token.py
Adds admin-token storage, environment precedence, validation, clearing, CLI commands, status output, and configuration tests.
HTTP admin authorization
taosmd/http_server.py
Separates admin-route authorization from data-plane token checks, supports server-token fallback, and returns 403 when no admin credential is configured.
Authorization validation and reference updates
tests/test_admin_token_separation.py, tests/test_admin_surface.py, taosmd/docs/a2a-comms.md, CHANGELOG.md
Covers token combinations, fallback behavior, failure modes, and documents the updated admin routes and configuration.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant taosmd HTTP handler
  participant taosmd config
  Client->>taosmd HTTP handler: Send admin or data-plane request
  taosmd HTTP handler->>taosmd config: Read admin_token and server_token
  taosmd HTTP handler->>taosmd HTTP handler: Authorize the selected route
  taosmd HTTP handler-->>Client: Return response or 403
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.00% 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 clearly summarizes the main change: separating the admin token for admin operations.
Linked Issues check ✅ Passed The changes add admin_token support, preserve server_token fallback, keep data/A2A open when only admin_token is set, and fail closed when neither is set.
Out of Scope Changes check ✅ Passed The PR stays focused on admin-token separation and related tests/docs, with no obvious unrelated code changes.
✨ 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 feat/admin-token-separation

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.

@gitar-bot

gitar-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Comment thread taosmd/http_server.py
"""
if method != "POST":
return False
if path == "/shelves" or path.startswith("/shelves/"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: The prefix match path.startswith("/shelves/") is broader than the actual admin routes and weakens the auth model.

This makes every POST /shelves/* path exempt from the data-plane _check_token gate, but only /shelves/{id}/archive and /shelves/{id}/unarchive reach a handler that enforces _check_admin_token. Two consequences:

  1. Enumeration protection regression: An unauthenticated POST /shelves/anything-else now bypasses the token gate and returns 404 unknown shelf action (see line 921) instead of 401. Previously the pre-routing _check_token protected these paths ("prevents enumeration without a token"). The /shelves/ namespace is now probeable without any token even when a server_token is configured.
  2. Future auth-bypass risk: Any new non-admin POST /shelves/{id}/... route added later would silently inherit this _check_token exemption. If such a handler forgot to call _check_admin_token, it would be fully unauthenticated.

Consider matching the exact admin routes (e.g. path == "/shelves" or rest.endswith("/archive")//unarchive) rather than the whole /shelves/ prefix, so the exemption tracks exactly the routes that self-enforce _check_admin_token.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
taosmd/http_server.py 672 _is_admin_route uses the broad path.startswith("/shelves/") prefix (matches every POST /shelves/*), exempting all of them from the data-plane _check_token gate. Only /shelves/{id}/archive and /shelves/{id}/unarchive reach a handler that enforces _check_admin_token; other POST /shelves/* paths now return 404 (unknown shelf action) instead of 401, weakening enumeration protection when a server_token is set, and creating a future auth-bypass risk for any new non-admin POST /shelves/{id}/... route that forgets to call _check_admin_token.
Files Reviewed (8 files)
  • CHANGELOG.md - 0 issues
  • taosmd/cli.py - 0 issues
  • taosmd/config.py - 0 issues
  • taosmd/docs/a2a-comms.md - 0 issues
  • taosmd/http_server.py - 1 issue (carried forward, re-verified against HEAD e78e476; still active, unfixed)
  • tests/test_admin_surface.py - 0 issues
  • tests/test_admin_token_separation.py - 0 issues
  • tests/test_config_admin_token.py - 0 issues

Notes: Full review (incremental base commit ebeb48d was no longer resolvable in the local repo, so the fallback full-diff path was used). The core token-separation logic is sound: _check_admin_token resolves expected = admin_token or server_token and fails closed (403) when neither is set; all six admin handlers (_handle_admin_shelf_create, _handle_admin_shelf_archive, _handle_admin_shelf_unarchive, _handle_admin_a2a_delete_channel, _handle_admin_a2a_rename_channel, _handle_admin_a2a_supersede_message) correctly call _check_admin_token, so the dispatch exemption opens no direct hole today. The set_admin_token empty-string guard, env-over-file precedence, and CLI set-admin-token wiring are correct. The single remaining concern is the over-broad /shelves/ prefix in _is_admin_route (see inline comment at line 672, still open).

Previous Review Summary (commit ebeb48d)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit ebeb48d)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
taosmd/http_server.py 672 _is_admin_route uses the broad path.startswith("/shelves/") prefix, exempting all POST /shelves/* from the data-plane token gate. Weakens enumeration protection (unauthenticated POST /shelves/<garbage> now returns 404 instead of 401) and creates a future auth-bypass risk for any new /shelves/{id}/... POST route.
Files Reviewed (8 files)
  • CHANGELOG.md - 0 issues
  • taosmd/cli.py - 0 issues
  • taosmd/config.py - 0 issues
  • taosmd/docs/a2a-comms.md - 0 issues
  • taosmd/http_server.py - 1 issue
  • tests/test_admin_surface.py - 0 issues
  • tests/test_admin_token_separation.py - 0 issues
  • tests/test_config_admin_token.py - 0 issues

Notes: The core token-separation logic (_check_admin_token preferring admin_token and falling back to server_token, fail-closed with 403) is sound, and all six admin handlers correctly enforce _check_admin_token, so the exemption opens no direct hole for the current admin routes. The behavior matrix and tests match the described intent.

Fix these issues in Kilo Cloud


Reviewed by hy3-20260706:free · Input: 67.8K · Output: 7.2K · Cached: 310.1K

Admin operations were gated by the same server_token that gates every data
and A2A endpoint, so on a token-less deployment the only way to authorize an
admin op was to set a server token, which locked out every agent on the data
plane for the duration of the admin window.

Add a dedicated admin_token (config key, TAOSMD_ADMIN_TOKEN env, and
taosmd config set-admin-token) that gates the admin surface independently.
_check_admin_token prefers admin_token and falls back to server_token, and the
admin write routes are exempt from the data-plane _check_token gate since they
enforce their own fail-closed check. Existing token-secured installs are
unchanged; setting only admin_token leaves data and A2A endpoints open; with
both set a data-plane-only caller cannot run admin ops; with neither set the
admin surface still fails closed.

Phase 2 (isolating admin ops from the single service loop) is deferred and
tracked separately.
@jaylfc
jaylfc force-pushed the feat/admin-token-separation branch from ebeb48d to e78e476 Compare July 11, 2026 16:13
@jaylfc
jaylfc merged commit cfb62e9 into master Jul 11, 2026
2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@taosmd/cli.py`:
- Around line 224-252: Update the config command dispatch to preserve and pass
the resolved args.data_dir into _config_set_admin_token and _config_show instead
of discarding it. Ensure both config writes and reads use that directory, while
retaining existing token validation and display behavior.

In `@tests/test_config_admin_token.py`:
- Around line 15-18: Update the data_dir fixture to also remove the TAOSMD_TOKEN
environment variable, alongside TAOSMD_ADMIN_TOKEN, before returning the
temporary directory path so test_independent_of_server_token is isolated from
ambient server-token configuration.
🪄 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 Plus

Run ID: 1dcd049a-a92b-4d6d-b381-81c5bbb80129

📥 Commits

Reviewing files that changed from the base of the PR and between 94a896c and e78e476.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • taosmd/cli.py
  • taosmd/config.py
  • taosmd/docs/a2a-comms.md
  • taosmd/http_server.py
  • tests/test_admin_surface.py
  • tests/test_admin_token_separation.py
  • tests/test_config_admin_token.py

Comment thread taosmd/cli.py
Comment on lines +224 to 252
def _config_set_admin_token(token: str | None, clear: bool) -> int:
from . import config # noqa: PLC0415

if not clear and not token:
print("error: provide <token> or --clear", file=sys.stderr)
return 2
try:
config.set_admin_token(token or "", clear=clear)
except ValueError as exc:
print(f"error: {exc}", file=sys.stderr)
return 2
if clear:
print("Admin token cleared.")
else:
print("Admin token stored.")
return 0


def _config_show() -> int:
from . import config # noqa: PLC0415

url = config.get_server_url()
token = config.get_server_token()
admin_token = config.get_admin_token()
model = config.get_memory_model()
print(f"server_url : {url or '(unset, local mode)'}")
print(f"server_token : {'(set)' if token else '(unset)'}")
print(f"admin_token : {'(set)' if admin_token else '(unset, falls back to server_token)'}")
print(f"memory_model : {model or '(default)'}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor --data-dir for admin-token config commands.

Lines 1712-1715 discard the already-resolved args.data_dir, so taosmd --data-dir DIR config set-admin-token ... persists to the default config location; config show reads that same wrong location.

Proposed fix
-def _config_set_admin_token(token: str | None, clear: bool) -> int:
+def _config_set_admin_token(
+    token: str | None, clear: bool, data_dir=None
+) -> int:
 ...
-        config.set_admin_token(token or "", clear=clear)
+        config.set_admin_token(token or "", clear=clear, data_dir=data_dir)

-def _config_show() -> int:
+def _config_show(data_dir=None) -> int:
 ...
-    url = config.get_server_url()
-    token = config.get_server_token()
-    admin_token = config.get_admin_token()
-    model = config.get_memory_model()
+    url = config.get_server_url(data_dir)
+    token = config.get_server_token(data_dir)
+    admin_token = config.get_admin_token(data_dir)
+    model = config.get_memory_model(data_dir)
 ...
-            return _config_set_admin_token(args.token, args.clear)
+            return _config_set_admin_token(args.token, args.clear, args.data_dir)
 ...
-            return _config_show()
+            return _config_show(args.data_dir)

Also applies to: 1712-1715

🤖 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 `@taosmd/cli.py` around lines 224 - 252, Update the config command dispatch to
preserve and pass the resolved args.data_dir into _config_set_admin_token and
_config_show instead of discarding it. Ensure both config writes and reads use
that directory, while retaining existing token validation and display behavior.

Comment on lines +15 to +18
@pytest.fixture
def data_dir(tmp_path, monkeypatch):
monkeypatch.delenv("TAOSMD_ADMIN_TOKEN", raising=False)
return str(tmp_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Isolate TAOSMD_TOKEN too.

test_independent_of_server_token expects no server token, but an ambient TAOSMD_TOKEN overrides the empty temp config and makes the test fail. Clear it in this fixture.

 def data_dir(tmp_path, monkeypatch):
     monkeypatch.delenv("TAOSMD_ADMIN_TOKEN", raising=False)
+    monkeypatch.delenv("TAOSMD_TOKEN", raising=False)
     return str(tmp_path)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@pytest.fixture
def data_dir(tmp_path, monkeypatch):
monkeypatch.delenv("TAOSMD_ADMIN_TOKEN", raising=False)
return str(tmp_path)
`@pytest.fixture`
def data_dir(tmp_path, monkeypatch):
monkeypatch.delenv("TAOSMD_ADMIN_TOKEN", raising=False)
monkeypatch.delenv("TAOSMD_TOKEN", raising=False)
return str(tmp_path)
🤖 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_config_admin_token.py` around lines 15 - 18, Update the data_dir
fixture to also remove the TAOSMD_TOKEN environment variable, alongside
TAOSMD_ADMIN_TOKEN, before returning the temporary directory path so
test_independent_of_server_token is isolated from ambient server-token
configuration.

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.

Separate admin token so admin operations never lock the data plane

1 participant