security: enforce https on dues_payment_methods.url at the data layer

The url renders to members as a clickable href. https was enforced only
in the admin Zod action, but RLS lets an admin/board write the row
directly via PostgREST, bypassing Zod, with no DB constraint -- a
malicious admin could store a javascript: URL and XSS members who click.

- Migration 050: CHECK (url ~* '^https://') so the DB is the source of
  truth; integration-tested that the service path cannot store
  javascript:/http:.
- Member-read action filters with isSafeDuesUrl (defense-in-depth).
This commit is contained in:
Moheeb Zara
2026-05-19 21:35:49 -07:00
parent 5ce6b29d4a
commit f2534323da
7 changed files with 61 additions and 2 deletions
+5 -1
View File
@@ -2811,6 +2811,9 @@ CREATE TRIGGER trg_notification_prefs_touch
-- Link configuration only, no automated payment record. RLS: SELECT = any
-- space member (they render the buttons); writes admin/board. url is validated
-- https-only at the server-action boundary.
-- url must be an absolute https URL: it is rendered to members as a clickable
-- href, and RLS lets admins write the row directly (bypassing the app's Zod
-- check), so the data layer is the source of truth (migration 050).
CREATE TABLE IF NOT EXISTS public.dues_payment_methods (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
space_id uuid NOT NULL REFERENCES public.spaces(id) ON DELETE CASCADE,
@@ -2821,7 +2824,8 @@ CREATE TABLE IF NOT EXISTS public.dues_payment_methods (
sort_order integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (space_id, platform)
UNIQUE (space_id, platform),
CONSTRAINT dues_payment_methods_url_https CHECK (url ~* '^https://')
);
CREATE INDEX IF NOT EXISTS idx_dues_payment_methods_space
ON public.dues_payment_methods (space_id);