feat: remove v2 compatibility paths - #155
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Security Review — feat: remove v2 compatibility paths
Overall this is a clean major-version break. No injection, SSRF, XSS, or supply-chain risks were found. Two findings are worth addressing before merge, one at medium and one at low-medium severity.
[MEDIUM] Silent authorization bypass — onBeforeSubmission returns false
form-builder/api/plugin.ts calls onBeforeSubmission directly (not via runHook) and previously had an explicit compat guard:
// Removed in this PR:
if (hookResult === false) {
originalError = new Error("Submission rejected");
}After this PR, if a consumer's onBeforeSubmission hook returns false to block a submission, originalError remains undefined, the submission proceeds normally, and onSubmissionError is never called. Only throwing works as a denial now. Unlike the runHookWithShim → runHook rename (which is consistently applied and documented), this specific guard is removed without a mention in breaking-changes.mdx.
Concrete risk: A consumer with:
onBeforeSubmission: async (formId, data, ctx) => {
if (!ctx.headers.get('x-user-id')) return false; // ← now silently allows
}will have their denial bypassed after upgrading to v3.
Recommendation: Add a note to the "Backend hooks deny by throwing" section in breaking-changes.mdx that explicitly calls out onBeforeSubmission, or reintroduce a guard at the call site that throws (or warns) when hookResult === false.
[LOW-MEDIUM] Client-side page guards removed — no auth.can replacement shown in routes
Three client-side render guards were removed from the Comments plugin (onBeforeModerationPageRendered, onBeforeResourceCommentsRendered, onBeforeUserCommentsPageRendered). The breaking-changes doc says to replace them with auth.can on StackProvider, but the moderation and user-comments route definitions in commentsClientPlugin do not add a ComposedRoute permission gate. SSR loader hooks remain and protect server-side prefetching, but there is no client-side navigation guard equivalent.
Concrete risk: A consumer relying on onBeforeModerationPageRendered to gate the admin dashboard loses that protection silently after upgrading unless they also wire auth.can.
Recommendation: Add a before/after migration example in breaking-changes.mdx showing the auth.can replacement, and/or add a ComposedRoute auth gate to the moderation and user-comments route definitions.
Informational — return false in all other onBefore* backend hooks
The runHookWithShim → runHook rename removes the boolean-return compat shim from all remaining backend hooks. This is documented in breaking-changes.mdx under "Backend hooks deny by throwing". The dev-mode console.warn that previously surfaced un-migrated hooks is also removed, so consumers get no runtime signal if they missed updating a hook. Acceptable for a major-version break, but worth emphasizing in the release notes (e.g., grep for return false in onBefore* hooks before upgrading).
Not flagged
headersprop removal fromCommentThread: causes server-side auth errors rather than bypasses; acceptable breaking change.apiBaseURL/apiBasePathfallback toundefined: causes obvious runtime fetch errors, not silent bypasses.- No new injection, SSRF, XSS, deserialization, or supply-chain risks introduced.
Sent by Cursor Automation: Find vulnerabilities
| @@ -412,7 +412,7 @@ export const formBuilderBackendPlugin = ( | |||
|
|
|||
There was a problem hiding this comment.
[MEDIUM] Silent allow when onBeforeSubmission returns false
The hookResult === false → originalError guard was removed here. A consumer's hook that returns false to reject a submission will now silently allow it — only throwing works after this change. Unlike every other onBefore* backend hook, this call site is not wrapped in runHook, so the change is not covered by the documented "Backend hooks deny by throwing" migration. Either add an explicit guard here or add a call-out for onBeforeSubmission in breaking-changes.mdx.
| <ModerationPageInternal localization={overrides.localization} /> | ||
| </PageWrapper> | ||
| ); | ||
| } |
There was a problem hiding this comment.
[LOW-MEDIUM] Client-side render guard removed; auth.can replacement not wired
onBeforeModerationPageRendered guarded client-side rendering. It is removed here without an auth.can gate in the route definition. The SSR loader hook still protects server-side prefetching, but a consumer who upgrades without also configuring auth.can will expose this page to unauthenticated client-side navigation. Please add a migration example in breaking-changes.mdx and consider adding a ComposedRoute permission gate to the route definition.
|
✅ Shadcn registry updated — registry JSON files were rebuilt and committed to this branch. |


Summary
StackProviderrouter, API, and auth configuration the only framework wiring pathValidation
pnpm buildpnpm typecheckpnpm lintpnpm test(594 tests)pnpm --filter @btst/stack run knip --reporter github-actionsCOREPACK_ENABLE_AUTO_PIN=0 pnpm --dir docs buildpnpm --filter @btst/stack test-registryrgsweeps andgit diff --checkCloses #151
Note
Medium Risk
This is a breaking consumer-facing API change around provider wiring, comments identity, and hook denial semantics. Risk is mainly migration/auth gating, not new security primitives.
Overview
v3 now has a single wiring path:
router,api, andauthlive onStackProvider. Pluginoverrideskeep only plugin-specific values (uploads, slots, localization). Per-pluginLink/Image/navigate/refresh/apiBaseURL/apiBasePathare gone from override types.Auth and hooks:
onBefore*PageRenderedcallbacks are removed; client gating usesauth.canonComposedRoutepermissions. Lifecycle hooks deny by throwing, notreturn false. Comments (CommentThread, counts, my-comments) read API/identity/login from the provider instead of manual props.Docs, agent skills, CLI templates, and comments e2e tests are updated to this contract (including dropping
currentUserIdquery spoofing in list tests). SSR plugin factories still takeapiBaseURL/apiBasePathbecause loaders run outside React context.Reviewed by Cursor Bugbot for commit 981f2a5. Bugbot is set up for automated code reviews on this repo. Configure here.