Skip to content

Split bearer auth into worker and maintainer token tiers #1111

Description

@joryirving

Problem

Dispatch accepts a single bearer token (DISPATCH_AGENT_TOKEN, src/lib/dispatch-env.ts getAcceptedAgentTokens), and every bearer caller gets full rights. The caller name for bearer auth is the self-reported x-agent-name header (src/lib/auth.ts resolveBearerActor). Courier's courier-dispatch secret and the MCP bridge's toolhive-dispatch secret hold the same token. So an autonomous worker can do everything an operator's agent can:

Proposal: two tiers of bearer token

  • worker: autonomous executors (Courier). An explicit allowlist, failing closed:
    • agents/[agentName]/next-task, tasks/report, heartbeat, active-work, queue, work-summary
    • agent-work/*
    • issues/claim without force
    • issues/unclaim for the caller's own claim only
    • issues/state, issues/status
    • issue and PR-fix reads (issues GET, pr-fix-queue/queued, pr-fix-queue/history)
    • pr-fix-queue/mark with FIXED, BLOCKED or STALE (generation required, as today); QUEUED and IGNORED are refused
  • maintainer: operator agents (Claude Code via the MCP bridge, openclaw agents) and the web UI (OIDC/basic). Everything, including pr-fix-queue/requeue, mark to QUEUED or IGNORED, force claims, groomer, lanes, admission overrides, automation/* and syncs.

A worker token calling a maintainer route gets a 403 that names the required tier, with an audit row.

Rollout without breakage

  • DISPATCH_AGENT_TOKEN keeps maintainer rights, so deploying changes nothing for anyone.
  • Add DISPATCH_WORKER_TOKEN, accepted with worker rights.
  • Then in home-ops, point Courier's courier-dispatch secret at the worker token. The MCP bridge and openclaw keep the maintainer token.
  • Later, optionally, rename DISPATCH_AGENT_TOKEN to DISPATCH_MAINTAINER_TOKEN, keeping the old name as an alias.

Implementation notes

  • getAcceptedAgentTokens becomes a token→tier lookup, and authorizeRequest returns tier alongside type/actor (OIDC/basic/disabled resolve to maintainer).
  • Enforce the tier centrally, from one route→tier table, so a new route defaults to maintainer and the worker list stays explicit and reviewable.
  • Split pr-fix-queue/mark and issues/claim by body (status, force) inside the route, since the same path serves both tiers.
  • Tests: a table-driven test that every worker-allowlisted route accepts a worker token, and that a sample of maintainer routes returns 403 for it.

Files

  • src/lib/dispatch-env.ts, src/lib/auth.ts
  • src/app/api/pr-fix-queue/mark/route.ts, src/app/api/pr-fix-queue/requeue/route.ts, src/app/api/issues/claim/route.ts, src/app/api/issues/unclaim/route.ts
  • .env.example, docs/ (auth section)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions