Skip to content

feat: remove v2 compatibility paths - #155

Merged
olliethedev merged 5 commits into
v3from
feat/151-remove-v2-compat
Aug 21, 2026
Merged

feat: remove v2 compatibility paths#155
olliethedev merged 5 commits into
v3from
feat/151-remove-v2-compat

Conversation

@olliethedev

@olliethedev olliethedev commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • make top-level StackProvider router, API, and auth configuration the only framework wiring path
  • remove legacy plugin override fields, page-render guard callbacks, boolean-return hook shims, and manual comments identity props
  • update tests, codegen templates, docs, agent guidance, and rebuilt shadcn registry artifacts

Validation

  • pnpm build
  • pnpm typecheck
  • pnpm lint
  • pnpm test (594 tests)
  • pnpm --filter @btst/stack run knip --reporter github-actions
  • COREPACK_ENABLE_AUTO_PIN=0 pnpm --dir docs build
  • pnpm --filter @btst/stack test-registry
  • scoped rg sweeps and git diff --check

Closes #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, and auth live on StackProvider. Plugin overrides keep only plugin-specific values (uploads, slots, localization). Per-plugin Link/Image/navigate/refresh/apiBaseURL/apiBasePath are gone from override types.

Auth and hooks: onBefore*PageRendered callbacks are removed; client gating uses auth.can on ComposedRoute permissions. Lifecycle hooks deny by throwing, not return 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 currentUserId query spoofing in list tests). SSR plugin factories still take apiBaseURL/apiBasePath because 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.

@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
better-stack-docs Ready Ready Preview Aug 20, 2026 11:44pm
better-stack-playground Ready Ready Preview Aug 20, 2026 11:44pm

Request Review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 runHookWithShimrunHook 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 runHookWithShimrunHook 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

  • headers prop removal from CommentThread: causes server-side auth errors rather than bypasses; acceptable breaking change.
  • apiBaseURL/apiBasePath fallback to undefined: causes obvious runtime fetch errors, not silent bypasses.
  • No new injection, SSRF, XSS, deserialization, or supply-chain risks introduced.
Open in Web View Automation 

Sent by Cursor Automation: Find vulnerabilities

@@ -412,7 +412,7 @@ export const formBuilderBackendPlugin = (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Silent allow when onBeforeSubmission returns false

The hookResult === falseoriginalError 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>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Shadcn registry updated — registry JSON files were rebuilt and committed to this branch.

@olliethedev
olliethedev merged commit 6d3fdec into v3 Aug 21, 2026
3 checks passed
@olliethedev
olliethedev deleted the feat/151-remove-v2-compat branch August 21, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant