audit: fix onboarding payment_url XSS + forum comment tenant check + joinSpace rate limit

Diversified audit across public/API, XSS/rendering, and legacy authz
surfaces (three independent reviews). No P0. Fixes:

- Onboarding payment_url (P1 stored XSS): rendered into an href with no
  protocol check; an admin could store javascript:. Sanitize at the sink
  with sanitizeUrl (http(s) only). Same class as the dues-url fix.
- forum.addComment (P2): only forum_thread targets were space-verified;
  proposal/incident/policy entity_ids were not, so a member could attach
  a comment to another space's entity. Verify the target exists in the
  caller's space (RLS-scoped) for every entity type before insert.
- joinSpace (P2): add a per-user rate limit (10/hr) for parity with
  createSpace/signIn, bounding invite-code guessing.
This commit is contained in:
Moheeb Zara
2026-05-20 02:22:50 -07:00
parent 36afa7ea2c
commit c0d6d496f8
4 changed files with 64 additions and 17 deletions
+17 -10
View File
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'
import { toast } from 'sonner'
import { BrandMark } from '@/components/brand-mark'
import { SafeMarkdown } from '@/components/safe-markdown'
import { sanitizeUrl } from '@/lib/security'
import { FormRenderer } from '@/components/forms/form-renderer'
import type { FormField } from '@/lib/forms-schema'
import {
@@ -231,16 +232,22 @@ export function OnboardingFlow({ spaceName, steps, canSkip, profile: initialProf
) : step.step_type === 'payment' ? (
<div className="space-y-4">
{step.body && <SafeMarkdown>{step.body}</SafeMarkdown>}
{typeof step.config?.payment_url === 'string' && step.config.payment_url && (
<a
href={step.config.payment_url as string}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-primary text-white text-sm font-sans px-4 py-2 rounded hover:bg-primary/90 transition"
>
Set up payment
</a>
)}
{typeof step.config?.payment_url === 'string' &&
(() => {
// Admin-entered; sanitize to http(s) so a stored
// javascript:/data: URL can't XSS members in onboarding.
const url = sanitizeUrl(step.config.payment_url as string)
return url ? (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 bg-primary text-white text-sm font-sans px-4 py-2 rounded hover:bg-primary/90 transition"
>
Set up payment
</a>
) : null
})()}
</div>
) : step.step_type === 'form' ? (
<div className="space-y-4">