Skip to content

M1: Multi-tenant auth foundation (machine plane) #81

@viktor-shcherb

Description

@viktor-shcherb

Parent: #80 (M-phase epic)

Goal

Formalize Murmur's machine-plane auth so it supports multiple publishers cleanly. Today there's a single shared bearer (MURMUR_TOKEN) that does triple duty across registration, agent-shim auth, and webhook delivery. This blocks multi-tenant onboarding and conflates three different security concerns.

Behavior

Publisher namespace as first-class entity

Each publisher gets { id, slug, display_name, created_at }. Per-publisher state isolation across:

  • Pipeline registry
  • Skill registry (M5)
  • Run history
  • Webhook URL config
  • HITL review queue (M3)

Four machine-plane secrets, rotatable independently

Secret Direction Purpose
publisher_admin_token Publisher → Murmur Pipeline + skill registration; publisher config; rotate other tokens
publisher_runner_token Publisher → Murmur Triggering runs (e.g., from a public form)
webhook_signing_secret Murmur → Publisher (HMAC) Murmur signs webhook payloads; publisher verifies
subcommand_bearer Agent → Publisher shim (Murmur stores, injects on subcommand calls) Publisher-controlled bearer the publisher's shim verifies

The agent's MCP session token is per-claim and orthogonal to these (already exists).

API surface

POST   /publishers                       # create publisher (bootstrap; gated)
GET    /publishers/me                     # current publisher (token-scoped)
PATCH  /publishers/me                     # update display_name / webhook URL / subcommand_bearer
POST   /publishers/me/tokens/{kind}/rotate
DELETE /publishers/me/tokens/{kind}/{id}
GET    /publishers/me/audit               # admin-action audit log

Existing endpoints (POST /pipelines, POST /skills, POST /pipelines/{id}/runs, webhook delivery) become publisher-scoped via token claims — no explicit publisher_id in request bodies.

Webhook signature

Murmur signs every webhook delivery with webhook_signing_secret over (timestamp, body). Publishers verify via X-Murmur-Signature: t=<unix>,v1=<hex> header. Replay protection: publishers reject deltas > 5 min.

Backward compat for the existing demo publisher

Existing MURMUR_TOKEN continues to work as the runner token for the existing demo publisher record (jobseek). Migration: when M1 ships, the existing MURMUR_TOKEN is grandfathered in as the new four-token set's runner_token; admin/webhook secrets get generated; the publisher gradually migrates env vars + secrets across the four token roles. No flag day; no public-facing downtime.

Folds in

Verification

  • Two publisher records can co-exist; each has its own pipeline + skill namespace; cross-publisher reads return 404
  • Each token kind rotates without invalidating the others
  • Webhook signature validates against a sample publisher's verifier
  • Audit log records every machine-plane admin action with (publisher_id, token_kind, timestamp, action, actor_user_id_if_human)
  • Demo publisher migration documented and tested with a no-downtime run

Definition of done

  • Publisher record + four-token model in storage with migrations
  • All existing endpoints scoped via token claims
  • Token rotation API + audit log
  • Webhook HMAC signing + sample verification doc
  • Demo publisher (jobseek) migrated to new token model with no downtime
  • Documentation in docs/auth.md

Dependencies / blocks

  • Blocks M2 (human auth needs publisher context)
  • Blocks M3 (HITL needs publisher scope)
  • Blocks M5 (skill registry needs publisher scope)
  • Blocks colophon-group/jobseek's full transition

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions