Files
supabase/apps/www/lib/go.ts
Alan Daniel 47d85e5235 fix(marketing/forms): resolve CRM config server-side, not from client (#46239)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix (security hardening).

## What is the current behavior?


[PRODSEC-120](https://linear.app/supabase/issue/PRODSEC-120/mythos-ant-2026-btrnt5a3-server-action-accepts-client-controlled-crm)
— the marketing form server action accepts the full \`crm\` config
(Notion \`database_id\`, HubSpot \`formGuid\`, Customer.io \`event\`,
\`staticProperties\`, etc.) from the client, so a crafted submission can
write to any Notion database the integration token reaches, post to any
HubSpot form in the portal, or trigger arbitrary Customer.io events.

## What is the new behavior?

The client now posts only \`{ slug, formId }\` plus the field values;
\`submitFormAction\` validates the ref with Zod, looks the trusted CRM
config up from the in-process \`_go/**\` page registry via a resolver
wired up in \`instrumentation.ts\`, and fails closed if the form isn't
found. \`SectionRenderer\` also strips \`crm\` from the section before
it crosses into the client bundle (so \`database_id\` / \`formGuid\` no
longer ship in page HTML), \`getAllGoPages\` rejects any form section
with \`crm\` but no stable \`id\`, and per-submission size/character
limits were tightened.

## Additional context

Separate follow-ups (not in this PR): confirm \`NOTION_FORMS_API_KEY\`
is write-only and scoped to the forms subtree, and chase down the
\`NOTION_EVENTS_API_KEY\` validity issue raised on the Linear ticket.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
- Forms now support unique identifiers for enhanced tracking and
management
  - Server-side form configuration management for improved reliability

* **Improvements**
- Enhanced form validation during page initialization to catch
configuration issues
- Improved form submission handling with better error detection and
reporting
  - Strengthened form operations with fail-safe configuration resolution

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46239?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 17:37:09 +01:00

44 lines
1.2 KiB
TypeScript

import { validateGoPageInvariants } from 'marketing'
import rawPages from '@/_go'
import { goPageSchema, type GoPage } from '@/types/go'
export function getAllGoPages(): GoPage[] {
const pages: GoPage[] = []
const seenSlugs = new Set<string>()
for (const raw of rawPages) {
const result = goPageSchema.safeParse(raw)
if (!result.success) {
throw new Error(
`Invalid go page definition (slug: "${(raw as any).slug ?? 'unknown'}"):\n${result.error.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\n')}`
)
}
const invariantErrors = validateGoPageInvariants(result.data)
if (invariantErrors.length > 0) {
throw new Error(
`Invalid go page definition (slug: "${result.data.slug}"):\n${invariantErrors.map((m) => ` - ${m}`).join('\n')}`
)
}
if (seenSlugs.has(result.data.slug)) {
throw new Error(`Duplicate slug "${result.data.slug}" in _go registry`)
}
seenSlugs.add(result.data.slug)
pages.push(result.data)
}
return pages
}
export function getAllGoSlugs(): string[] {
return getAllGoPages().map((p) => p.slug)
}
export function getGoPageBySlug(slug: string): GoPage | undefined {
return getAllGoPages().find((p) => p.slug === slug)
}