feat(form-builder): phase-2 sweep onto core primitives - #144
Conversation
Migrate the form-builder plugin to the v3 core primitives, mirroring the CMS sweep: - Declare formBuilderResources and generate all hooks via createResource; form-builder-hooks.tsx is now a thin public wrapper layer - Move the editor save flow onto the resource useForm (server field errors inline, success/error toasts, create->edit redirect) - Add a bounded search param to the forms list API (schema max(200), DEFAULT_MAX_PAGE_SIZE scan cap) and a URL-synced search box on the list page via useListState (debounce + external re-seed) - Replace sonner with useNotify and route all UI strings through useTranslate with localization overrides (new renderer catalog) - Wrap pages in ComposedRoute with permission props and CanAccess around New/Edit/Delete/Submissions controls - Simplify SSR loaders to prefetch(Infinite)Query on factory entries - Tests: query-key parity guard, getters search coverage, client-sweep jsdom suite; E2E search spec; registry regenerated Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Security Review — No High-Confidence Vulnerabilities Found
I reviewed the full diff against the threat checklist. No high-confidence vulnerabilities were introduced. Details of every area examined are below.
1. Injection risks (SQL / command / template / path traversal)
Result: Not vulnerable.
The new search query parameter is validated at the API boundary by the existing Zod schema (z.string().max(200).optional()). Critically, the search term is never injected into a database query. The implementation fetches up to DEFAULT_MAX_PAGE_SIZE (1000) rows with only equality-filter whereConditions, then performs the free-text match in-memory using String.prototype.includes() on already-serialized data (form.name, form.slug). No adapter-level filter, no template interpolation, no command execution.
2. Auth/authz bypasses and permission boundary mistakes
Result: Not bypassed.
The onBeforeListForms hook (the caller-supplied authorization check) is invoked in plugin.ts before getAllForms is called with the search parameter. The search value does not affect the authorization decision; it only filters results after access is granted. The route handler sequence is:
1. Validate query params (Zod)
2. createContext(ctx.headers) ← auth context
3. onBeforeListForms(context) ← authorization gate (unchanged)
4. getAllForms(adapter, { ..., search }) ← guarded read
The new CanAccess wrappers in the list and submissions pages are UI affordance guards only (they hide action buttons). Server-side authorization is provided entirely by the backend hooks, which are unchanged.
The new ComposedRoute permission props on FormBuilderPageComponent and SubmissionsPageComponent add explicit client-side route-level access checks—a positive improvement.
3. Secrets handling, token leakage, insecure logging
Result: No issues.
No secrets, credentials, or sensitive tokens are introduced or logged. The localization refactor replaces static string constants with useTranslate(); nothing in the i18n strings constitutes sensitive data.
4. Unsafe deserialization, SSRF, XSS, request forgery
Result: No regressions introduced.
- Submission data is displayed via
JSON.stringify(viewSubmission.parsedData, null, 2)inside a<pre>element rendered by React—React escapes text node content by default, so no XSS is possible here. error.messageis rendered in JSX text nodes (auto-escaped). This pattern was present in the code before this PR.- The
searchvalue is reflected into the page URL as?q=…throughuseListState, but is never rendered as raw HTML. - No new network requests to caller-supplied URLs (no SSRF surface added).
5. Dependency / supply-chain risk
Result: None.
No new external npm packages are introduced. All changes are first-party code within the monorepo.
Informational: documented limitation, not a vulnerability
When search is active, getAllForms scans at most DEFAULT_MAX_PAGE_SIZE (1000) rows from the DB (sorted createdAt desc). Forms beyond that position are silently excluded from search results. This is a correctness/UX limitation that is correctly documented in a code comment; it does not create a security boundary breach.
Summary: The changes are safe to merge from a security standpoint. The search feature is properly bounded (Zod validation + in-memory filter), authorization hooks are unaffected, and the new client-side access guards follow established patterns in the codebase.
Sent by Cursor Automation: Find vulnerabilities
|
✅ Shadcn registry validated — no registry changes detected. |
…submit client-side The resource mutation hook calls the plugin `refresh` override after every successful mutation. On public form pages that override is a full page reload, which remounted FormRenderer and wiped the client-side success screen right after submission (caught by the codegen E2E public-submission spec). Add `refresh?: boolean` to ResourceMutationDef (default true) and declare `refresh: false` on the public submit mutation. Co-authored-by: Cursor <cursoragent@cursor.com>


Summary
Phase-2 adoption sweep for the form-builder plugin (part of #129), mirroring the CMS sweep in #143:
formBuilderResources(queries + mutations with invalidation/setData) and generates all hooks viacreateResource;form-builder-hooks.tsxis now a thin public wrapper layer with unchanged signatures. SSR loaders prefetch directly on the factory query entries.useForm: save flow moved touseFormBuilderForm— server field errors render inline under the name/slug inputs, success/error toasts are localized, and create redirects to the edit page.searchparam on the forms list API (max(200)in the schema,DEFAULT_MAX_PAGE_SIZEDB scan cap, filtered totals) plus a URL-synced search box on the list page viauseListState(300ms debounce, replace history, re-seeds from back/forward navigation). SSG discriminator updated in lockstep.sonnerreplaced withuseNotify(); every UI string now flows throughuseTranslate()withlocalizationoverrides taking precedence (newform-builder-renderercatalog for the public renderer).ComposedRoutewithpermissionprops;CanAccessaround New/Edit/Delete/Submissions controls.SHARED_QUERY_CONFIG, and hand-wired query plumbing deleted; registry JSON regenerated.Test plan
pnpm typecheck,pnpm lint,knip --strictall green@btst/stackunit suite: 417 tests / 36 files passing, including new query-key parity guard, getters search coverage, and a jsdom client-sweep suite (CanAccess, useListState search sync, notify, i18n precedence)Made with Cursor