Skip to content

feat(relay): OPERATOR/MODERATOR role model for relay admin API with NIP-98 auth - #3777

Open
wpfleger96 wants to merge 17 commits into
mainfrom
wpfleger/admin-api-bearer-auth
Open

feat(relay): OPERATOR/MODERATOR role model for relay admin API with NIP-98 auth#3777
wpfleger96 wants to merge 17 commits into
mainfrom
wpfleger/admin-api-bearer-auth

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 30, 2026

Copy link
Copy Markdown
Member

Replaces the BUZZ_ADMIN_INSECURE_NO_AUTH / BUZZ_ADMIN_PUBKEYS model with a two-tier principal model backed by config union + DB, gated by NIP-98 HTTP auth. Phases 1 and 2 of Plan v4 (Thufir-reviewed spec).

What changed

Authentication (BUZZ_ADMIN_AUTH)

Replaces the deleted BUZZ_ADMIN_INSECURE_NO_AUTH=true variable with BUZZ_ADMIN_AUTH accepting token (default), disabled, or nip98. Startup fails closed on missing/malformed configuration.

Principal resolution (NIP-98 mode)

resolve_admin_principal() returns AdminPrincipal { pubkey, role, source }:

  • Operator/Config — pubkey ∈ RELAY_OPERATOR_PUBKEYS
  • Operator/OwnerFallback — pubkey == RELAY_OWNER_PUBKEY and RELAY_OPERATOR_PUBKEYS is empty (config-evaluated, never from runtime DB rows)
  • Operator or Moderator / Db — row in the relay_operators table
  • No match → 403

Config always outranks DB. None never falls through as a role.

Token/disabled mode

Read routes work in all modes. Mutation and staffing routes require nip98; token/disabled modes receive 403 from require_mutation_principal.

Phase 2: report resolution

POST /reports/{id}/resolve — enforcement state machine with idempotency:

  • Decision-only: dismiss/escalate — CAS open→terminal + audit row in one transaction.
  • Enforcement: delete/kick/ban/timeout — claims report (open→processing), runs durable mutation, finalizes to resolved. Crash-safe: re-drive picks up at the step marker and converges to exactly-one enforcement.

PATCH /feedback/{id} — update product_feedback.status (new|reviewed|archived). Requires nip98.

Phase 2: staffing endpoints

GET/PUT/DELETE /operators/{pubkey} — Operator-only. PUT/DELETE against a config-backed pubkey returns 409 Conflict. GET /operators returns the union of config and DB with per-entry source attribution.

Probe endpoint

GET /probe — auth-mode, role, source, canAct, canStaff discovery for the desktop console.

Migrations

  • 0029_relay_operators.sqlrelay_operators table (deployment-global; registered in _operator_global_tables), actor_authority column on moderation_actions, processing status + active_action_id on moderation_reports, status column on product_feedback.
  • 0030_relay_admin_actions.sqlrelay_admin_actions enforcement-action table with request_id idempotency key and step_marker for crash recovery.

Documentation

docs/admin/README.md rewritten to document the full principal model, NIP-98 event requirements (including query string, method tag, payload tag for body mutations), owner fallback B semantics, role/source table, capabilities by role, roster management, and startup error matrix.

Tests

915 unit tests pass. 10 ignored (requires Postgres). 7 pre-existing failures (6 media tests + 1 telemetry flake) that also fail on origin/main without a local DB.

@wpfleger96
wpfleger96 requested a review from a team as a code owner July 30, 2026 17:30
@cameronhotchkies cameronhotchkies added the triage-ready Appropriate for agentic review label Jul 30, 2026
@wpfleger96 wpfleger96 changed the title feat(relay): require a bearer token on the admin moderation API feat(relay): add authenticated admin API with bearer-token and network-layer modes Jul 30, 2026
@wpfleger96
wpfleger96 force-pushed the wpfleger/admin-api-bearer-auth branch 3 times, most recently from 3fcbdc0 to d014e40 Compare July 31, 2026 19:17
kalvinnchau
kalvinnchau previously approved these changes Jul 31, 2026

@kalvinnchau kalvinnchau 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.

Re-reviewed at d014e40. The fail-closed config contract, constant-time bearer validation, host/origin ordering, insecure network-boundary mode, dashboard token lifecycle, authenticated attachment fetches, and CSP/static routing are coherent and covered. Deployment dependency is external: land bb-public#339 and wait for Argo rollout before deploying this relay image.

npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 8 commits August 3, 2026 14:35
The deployment-admin API at /api/admin/v1 exposed every moderation report,
product feedback submission, submitter pubkey, and attachment blob to anyone
who could reach the listener with the right Host header. Host and Origin
matching is a routing constraint, not authentication.

BUZZ_ADMIN_HOST now requires BUZZ_ADMIN_TOKEN and the relay fails closed at
startup without it, so no deployment can be upgraded into an unauthenticated
state. The credential is checked before Host and Origin so an unauthenticated
caller cannot use the response code as an oracle for the expected admin host.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The dashboard keeps the operator bearer token in sessionStorage, but its
documents and assets were served with no Content-Security-Policy: the
existing policy is middleware on /api/admin/v1 only, which the SPA
fallback bypasses. A header is used rather than a meta tag because
frame-ancestors is ignored in meta. The policy is scoped to the admin
host so the public bundle is unaffected, and allows blob: images because
attachments are fetched with the token and rendered from object URLs.

The browser suite now proves the attachment object URLs are revoked on
both races (replacement/unmount and completion after unmount) and that
concurrent 401s collapse into a single re-prompt.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The dashboard's security posture section covered the bearer token and the
Host/Origin defense-in-depth but not the response-header CSP that now ships
with every admin-host SPA response, leaving operators without the frame and
script-origin guarantees they are relying on.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The admin document links /favicon.svg, but the admin-host fallback routed
only /assets/*, so the icon 404'd on the one host that serves the dashboard.
Vite already emits the file at the bundle root; the fallback now admits that
exact path and nothing else, so the bundle directory stays unbrowsable.

The CSP paragraph also claimed the policy restricts every network
destination. CSP does not constrain top-level navigation, so an executing
same-origin script can still navigate with the token in a URL.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
… deployments

Operators whose admin API is protected at the network layer (VPN, firewall,
private ingress) can now set BUZZ_ADMIN_INSECURE_NO_AUTH=true to disable
bearer auth while keeping Host/Origin checks as defense-in-depth. This is
the intended mode for Block's bb-public relay, which runs behind WARP+Okta.

Config semantics:
- Only the exact value "true" enables disabled mode; any other non-empty
  value is a startup ConfigError (no silent typo-coercion).
- BUZZ_ADMIN_TOKEN and BUZZ_ADMIN_INSECURE_NO_AUTH=true both set is a
  startup ConfigError (ambiguous intent).
- BUZZ_ADMIN_HOST with neither remains the existing fail-closed error.
- A prominent WARN is logged on every startup in disabled mode.

SPA probe: the dashboard now probes auth mode on first load (one
unauthenticated GET to /api/admin/v1/reports). 200 → skip prompt (relay
is in insecure_no_auth mode). Anything else → token prompt (existing
behavior). This makes the dashboard work without user interaction for
network-layer-protected deployments.

Tests added:
- Rust (config): insecure_no_auth activates, both-set fails, junk values
  fail, empty string treated as unset (4 tests)
- Rust (api::admin): disabled mode passes all routes, still rejects wrong
  host and mismatched origin (3 tests)
- Playwright (auth): probe 200 skips prompt, probe non-200 shows prompt (2 tests)

Docs: README, both CHANGELOGs, and both .env.examples rewritten to
describe the final two-mode shape once.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The JSDoc stated true = relay accepted (no auth needed) and false = 401.
The code returns true when auth is required (non-200 response) and false
when the relay returned 200 (insecure_no_auth mode, no token needed).
Correct the comment to match the implementation and function name.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
AdminConfig previously modelled the auth mode as token: Option<AdminToken>
plus insecure_no_auth: bool, leaving two invalid states representable and
bridging the gap with an .expect() on the hot request path. Replace with
an AdminAuth::Token(AdminToken) | AdminAuth::InsecureNoAuth enum so the
type system rules out the invalid states and the .expect() is deleted.

Config-parse behaviour is byte-identical: same error strings, same WARN
messages, same fail-closed matrix, same env var names. Existing tests are
updated mechanically (constructor call sites and one pattern-match assertion).

Also adds a one-sentence note to docs/admin/README.md that token-mode issues
one by-design 401 probe per fresh browser session so operators do not alert
on it as an attack.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…llowlist

Deletes BUZZ_ADMIN_INSECURE_NO_AUTH. Replaces it with BUZZ_ADMIN_AUTH, a
mode enum with three exact values:

- unset / "token"  — bearer token via BUZZ_ADMIN_TOKEN (unchanged default)
- "disabled"       — no auth; boot WARN retained (replaces INSECURE_NO_AUTH)
- "nip98"          — NIP-98 HTTP Auth via BUZZ_ADMIN_PUBKEYS allowlist

All existing token/disabled invariants carry 1:1 under the new name.
Junk values fail closed at startup with a ConfigError.

NIP-98 mode (admin-moderation scope):
- Parses BUZZ_ADMIN_PUBKEYS as comma-separated 64-hex pubkeys (deduped).
- BUZZ_ADMIN_PUBKEYS required non-empty in nip98 mode; warn-and-ignore in
  token/disabled modes. BUZZ_ADMIN_TOKEN + nip98 = ConfigError.
- authorize_nip98(): single Authorization: Nostr <base64 event> header,
  verify_nip98_event, deployment-scoped replay guard (admin-moderation),
  allowlist membership check. Uniform 401 on all failures; no oracle.
- WWW-Authenticate: Nostr on 401 (Bearer stays in token mode) — the SPA
  uses this header to discover the auth mode.
- Canonical URL: https://<BUZZ_ADMIN_HOST>/api/admin/v1<stripped-path>;
  http:// for loopback hosts (local dev). Axum strips the prefix before
  handlers; ADMIN_API_PREFIX constant re-adds it for NIP-98 verification.

SPA (admin-web):
- probeAuthMode() reads WWW-Authenticate: Bearer/Nostr/absent to return
  "token" | "nip98" | "disabled".
- nip98 mode: signNip98() helper builds kind-27235 events via window.nostr
  (NIP-07); send() attaches Authorization: Nostr per request.
- Nip07Screen: shown when nip98 mode is detected but window.nostr is absent;
  instructs operator to install nos2x or Alby.
- 401 in nip98 mode re-signs once then surfaces the error (no infinite loop).

Docs/config:
- docs/admin/README.md rewritten to three-mode shape; migration note from
  BUZZ_ADMIN_INSECURE_NO_AUTH.
- Both .env.example files updated (root and deploy/compose/).
- CHANGELOG.md Unreleased sections updated with three-mode description.

Tests:
- Config matrix: all mode values, junk, every mutual exclusion,
  missing/invalid BUZZ_ADMIN_PUBKEYS.
- NIP-98 route tests: wrong pubkey → 401, replay → 401, wrong u/method →
  401, duplicate Authorization → 401, valid → 200, valid + wrong host → 403.
- Regression pins: token-mode and disabled-mode behavior unchanged.
- Playwright: nip98 mode without NIP-07 → instruction screen; mocked
  window.nostr happy path signs requests and renders dashboard.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96
wpfleger96 force-pushed the wpfleger/admin-api-bearer-auth branch from d014e40 to e93d5be Compare August 3, 2026 19:40
@wpfleger96 wpfleger96 changed the title feat(relay): add authenticated admin API with bearer-token and network-layer modes feat(relay): add authenticated admin API — bearer token, NIP-98 pubkey allowlist, and disabled modes Aug 3, 2026
@wpfleger96
wpfleger96 force-pushed the wpfleger/admin-api-bearer-auth branch from cd6c06f to 9d54f68 Compare August 3, 2026 20:08
…x crate changelog

Three issues from Thufir pass-1:

1. Query-bearing requests now authenticate correctly. All five handlers now
   pass uri.path_and_query() to authorize() instead of uri.path(), so the
   canonical URL includes the query string (e.g. /api/admin/v1/reports?
   status=open&limit=100) and verify_nip98_event() compares the right URL.
   Two new route tests: query-bearing request with matching full-URL event
   -> 200; path-only event for a query-bearing request -> 401.

2. Bounded NIP-98 retry is now implemented. send() in nip98 mode re-signs
   and retries exactly once on the first 401 (handles clock skew / key
   rotation); a second 401 surfaces the error. Two new Playwright tests:
   first-401-then-200 asserts two distinct auth calls and eventual render;
   persistent-401 asserts the error state (role=alert / 'Could not load
   data') after exactly two attempts.

3. crates/buzz-relay/CHANGELOG.md Unreleased section rewritten to the
   three-mode shape (BUZZ_ADMIN_AUTH=token|disabled|nip98, BUZZ_ADMIN_
   PUBKEYS). BUZZ_ADMIN_INSECURE_NO_AUTH retained only as a migration note.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Hayt and others added 3 commits August 5, 2026 17:03
…arer-auth

* origin/main: (65 commits)
  fix(desktop): route macos notification clicks (#4799)
  feat(mobile): sync themes per community (#3767)
  feat(desktop): sync themes per community (#3653)
  feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)
  fix(agent): recover from unsupported image input instead of poisoning the turn (#4896)
  Define private managed agent wire protocol (#4593)
  fix(mobile): serialize channel sections sync (#3165)
  fix(desktop): make missing-command error actionable for released builds (#4802)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	CHANGELOG.md
… model

Replace BUZZ_ADMIN_PUBKEYS allowlist with a typed principal model backed by
config union + DB. Implement Phase 1 of Plan v4 (Thufir-cleared spec).

## Principal resolution (fallback B)

resolve_admin_principal() returns AdminPrincipal { pubkey, role, source }:
- Operator/Config    if pubkey ∈ RELAY_OPERATOR_PUBKEYS
- Operator/OwnerFallback  if pubkey == RELAY_OWNER_PUBKEY AND
                          RELAY_OPERATOR_PUBKEYS is empty (config-evaluated only)
- Operator|Moderator/Db  from relay_operators table otherwise
- None → 403, never a fall-through role

Config outranks DB: a DB row for a config-backed Operator is ignored.
RELAY_OWNER_PUBKEY malformed is a startup ConfigError (was warn-and-ignore).
BUZZ_ADMIN_PUBKEYS deleted. Role vocabulary is operator|moderator end-to-end.

## NIP-98 method/body binding

authorize() takes method + raw_body, returns Option<AdminPrincipal>:
- Body-bearing mutations (POST/PUT/PATCH/DELETE) require the NIP-98 payload
  sha256 tag; requests without it are rejected 401 before any DB access.
- u tag built from config-derived host + full path-and-query (not inbound Host).
- Replay guard consumed only after cryptographic verification; Redis-down
  fails closed.
- token/disabled modes: no principal returned, probe advertises no capabilities.

## Schema (migration 0028)

- relay_operators table (global, no community_id): pubkey BYTEA PK, role TEXT
  CHECK(operator|moderator), added_by, created_at. Registered in
  _operator_global_tables and in the hardcoded parser list in migration.rs.
- moderation_actions.actor_authority: TEXT NOT NULL DEFAULT 'community'
  CHECK(community|relay_operator|relay_moderator). Backfills existing rows.
- moderation_reports: status CHECK extended with 'processing' for HTTP
  enforcement claim (Phase 2); active_action_id UUID column added.
- product_feedback.status: TEXT NOT NULL DEFAULT 'new'
  CHECK(new|reviewed|archived).

## Routes and probe

/probe endpoint: returns authMode, role, source, canAct, canStaff.
token/disabled modes: role=null, canAct=false, canStaff=false.
nip98 mode: role+source from resolved AdminPrincipal; canStaff=true for Operator only.

## buzz-db

relay_operators CRUD module: get, list, upsert, remove. Db methods added.
Migration count assertions updated to 28.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Renumber migration 0028_relay_operators → 0029_relay_operators to avoid
collision with 0028_long_reaction_payloads that landed on main.

Update migration count assertions in tests (28 → 29) and applied_versions
check (Some(28) → Some(29)). CHANGELOG conflict resolved: keep Unreleased
section above v0.5.6 release notes.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 changed the title feat(relay): add authenticated admin API — bearer token, NIP-98 pubkey allowlist, and disabled modes feat(relay): OPERATOR/MODERATOR role model for relay admin API with NIP-98 auth Aug 7, 2026
Duncan and others added 2 commits August 7, 2026 17:25
…rchestrations, staffing endpoints

Add HTTP report-resolution executor, durable enforcement state machine,
staffing API, and corrected admin documentation.

Relay (crates/buzz-relay):
- api/admin/auth.rs: fix payload tag check to condition on raw_body.is_some()
  rather than method name — DELETE carries no body in the admin API;
  update method_has_body doc/suppress dead_code for prod path
- api/admin/mod.rs: add POST /reports/{id}/resolve, PATCH /feedback/{id},
  GET/PUT/DELETE /operators routes; ResolveReportBody with request_id
  idempotency key; source-aware operator listing; 409 on config-backed upsert/
  delete; test fixes — switch DB-dependent success-assertion tests to /probe
  (no DB dependency); add mutation route in-process tests (token mode →403,
  method/body substitution →401, missing payload tag →401, config-backed
  PUT/DELETE →409, owner-fallback B upsert →409); 5 ignored e2e acceptance
  test stubs (racing moderators, same-request_id retry, community 9044 vs
  processing, cancel after mutation, worker crash re-drive)
- handlers/report_resolution.rs: two orchestrations — resolve_report_decision_only
  (HTTP dismiss/escalate + 9044 adapter, single-transaction CAS open→terminal
  with audit row) and resolve_report_with_enforcement (HTTP delete/kick/ban/
  timeout, full claim/enforce/finalize state machine)
- handlers/mod.rs: register report_resolution module

buzz-db (crates/buzz-db):
- relay_admin_actions.rs: claim_report (CAS open→processing with decision audit
  row + action record in one transaction, idempotent by request_id), begin_
  enforcing, commit_mutation_step, finalize_success (action→succeeded +
  report→resolved atomically), record_failure (pre-mutation only, step_marker IS
  NULL guard), cancel_action (pre-mutation only, clears claim → report back to
  open), deploy_kick_member (deployment-authority primitive; distinguishes
  Removed from AlreadyGone, never blanket-converts MemberNotFound to success),
  update_feedback_status, outbox enqueue/mark_delivered/list_pending
- lib.rs: expose all relay_admin_actions functions on Db

migrations:
- 0030_relay_admin_actions.sql: relay_admin_actions + relay_admin_outbox tables,
  both registered in _operator_global_tables

docs:
- docs/admin/README.md: rewrite NIP-98 section to document actual OPERATOR/
  MODERATOR principal model; remove deleted BUZZ_ADMIN_PUBKEYS allowlist
  references; add principal resolution table, capabilities table, staffing
  section, complete Routes section with mutation/staffing endpoints

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
report_detail_rejects_unknown_report and
feedback_attachment_rejects_unknown_feedback both issue SQL queries
against Postgres which is unavailable in unit tests.  Without a DB
the relay returns 500 instead of 404, causing false failures in CI.
Mark both with #[ignore = "requires Postgres"] to match the
established pattern for DB-dependent admin tests on this branch.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Duncan and others added 3 commits August 7, 2026 18:12
…rker, CAS fences, tests

- Outbox rows now inserted inside claim_report transaction (atomicity guarantee:
  reporter_notice/tombstone/system_message rows exist even on process crash before
  request path re-enqueues them)
- Add claim_pending_outbox_batch (SELECT FOR UPDATE SKIP LOCKED, lease-based),
  fail_outbox_row DB functions; wire Db wrapper methods
- Implement admin_outbox_worker: background task polls relay_admin_outbox with
  DB-level leases (held_by/lease_expires_at), drives tombstone/system_message/
  reporter_notice delivery; spawned from main.rs unconditionally
- drive_enforcement rewritten as loop (eliminates recursive async fn compile error;
  CAS contention reloads + continues rather than recursing)
- Fix remaining clippy lints: type_complexity on decode_report_target (module-level
  TargetPair alias), single_match in config.rs test (-> if let)
- Replace 5 todo!() acceptance test placeholders with real Postgres-backed
  implementations; add 8 additional DB-layer tests in relay_admin_actions::tests
  covering: racing moderators, idempotent retry, cancel-post-mutation rejection,
  crash re-drive from step_marker, decision-only no-orphan-audit, outbox-in-claim,
  finalize-without-marker rejection, kick Removed vs AlreadyGone
- Purge BUZZ_ADMIN_PUBKEYS from .env.example, deploy/compose/.env.example,
  CHANGELOG.md, crates/buzz-relay/CHANGELOG.md; replace with RELAY_OPERATOR_PUBKEYS
  + fallback B + relay_operators table description
- Revert unrelated mobile/pubspec.lock drift

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ues, relay_operator authority

Test setup used 16-byte UUID slices for report_event_id (constraint requires 32),
'public_group' for channel_type (not a valid enum value; use 'stream'), 'public'
for channel visibility (use 'open'), and 'config' for actor_authority in
moderation_actions inserts (constraint allows 'community'|'relay_operator'|
'relay_moderator'; 'config' is a source label, not an authority level).

All 9 relay_admin_actions DB-layer tests now pass on a fresh Postgres instance.
The 5 acceptance tests in api::admin also pass (requires DATABASE_URL set to
the test DB). 6 pre-existing bridge test failures (Redis-gated) unchanged from
origin/main baseline.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Incorporates desktop v0.5.7 release notes. Resolves CHANGELOG.md conflict:
Unreleased relay auth section preserved above new v0.5.7 entry.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-ready Appropriate for agentic review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants