Skip to content

Implement dual-secret webhook rotation with an admin-facing rotation flow #266

Description

@Lakes41

Difficulty: Expert
Type: Security

Background
WEBHOOK_SECRET is a single environment variable used by @guildpass/webhook-utils's verifySignature to validate the x-guildpass-signature header on every incoming webhook. There is currently exactly one active secret at any time, configured purely via environment variable — there is no in-app mechanism to rotate it.

Problem
Rotating WEBHOOK_SECRET today requires updating the environment variable and restarting the dashboard, with a hard cutover: any webhook signed with the old secret and delivered after the new secret takes effect will be rejected, and there is no window to update the secret on the GuildPass core side and the dashboard side atomically. This makes routine or incident-driven (e.g. suspected leak) secret rotation operationally risky — a real gap given SECURITY.md's explicit in-scope concern about "exposure of ... API keys" and "webhook verification bypass."

Expected outcome
The dashboard can hold two active webhook secrets simultaneously (current + previous) during a rotation window, accepting signatures verified by either, with a clear admin-facing flow (e.g. in /settings) to: generate a new secret, enter the "dual-verification" rotation window, and later retire the old secret — without ever silently rejecting legitimately-signed webhooks mid-rotation, and without ever displaying a secret value once it has been generated and stored (write-once display, matching common secret-management UX conventions).

Suggested implementation

  • Extend the webhook verification call site (not necessarily @guildpass/webhook-utils itself, which is a generic, secret-agnostic library) to try verification against a list of currently-valid secrets (current + optionally previous) rather than a single hardcoded value, short-circuiting on the first match.
  • Add secret storage (requires durable mode, Issue Add a server-wide role sync command for admins #6, since secrets must persist and be admin-manageable — document this dependency clearly) with fields for the active secret, an optional "previous" secret, and a rotation-window expiry timestamp after which the previous secret is no longer accepted.
  • Build a /settings UI flow: "Rotate webhook secret" generates a new secret, shows it exactly once (with a copy-to-clipboard control and an explicit warning that it won't be shown again), and starts a configurable rotation window (e.g. default 24-48 hours) during which both secrets verify successfully.
  • Add a background or on-access check that automatically retires the previous secret once its rotation window expires.
  • Update SECURITY.md and packages/webhook-utils/README.md to describe the rotation flow and recommend it as the safe way to respond to a suspected secret leak.

Acceptance criteria

  • During an active rotation window, webhooks signed with either the current or the previous secret are accepted.
  • Outside the rotation window (expired), only the current secret is accepted, and the previous one is correctly rejected.
  • The generated secret is displayed exactly once in the UI and is not retrievable afterward (only re-rotatable).
  • A test simulates the full rotation lifecycle: dual-valid window → expiry → old-secret rejection.
  • Documentation (SECURITY.md, packages/webhook-utils/README.md) is updated to describe the rotation feature and recommend its use after a suspected leak.
  • pnpm typecheck, pnpm lint, and relevant tests pass.

Likely affected files/directories
Webhook handler/verification call site, apps/dashboard/app/settings/, new secret-storage schema (building on Issue #6), SECURITY.md, packages/webhook-utils/README.md.

Metadata

Metadata

Labels

GrantFox OSSGrantFox Open Source Sponsorship program tagMaybe RewardedIssue may qualify for a reward upon successful completion per campaign rulesOfficial Campaign | FWC26Official FWC26 campaign issue — eligible for campaign scoring and rewardsdashboardAutomatically createdfeatureNew feature, enhancement, or functional additionhelp wantedExtra attention is neededsecuritySecurity-related fix, hardening, audit, or vulnerability remediation

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions