feat: top-level router/api props on StackProvider with framework presets#134
Conversation
…presets (#127) Promotes the repeated per-plugin Link/navigate/refresh/Image and apiBaseURL/apiBasePath wiring to top-level `router` and `api` props on StackProvider, with shipped presets at @btst/stack/next, @btst/stack/react-router, and @btst/stack/tanstack. Resolution order is plugin override -> top-level router/api -> plugin defaults, so existing per-plugin overrides keep working as an escape hatch (non-breaking). The StackRouter contract already includes the getSearchParams/ setSearchParams fields required by #133. 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
Reviewed the full diff for PR #134 (feat/127-router-adapter). The changes introduce three new framework router presets (nextRouter, reactRouter, tanstackRouter) and a top-level router/api prop on StackProvider. Here is a threat-by-threat breakdown:
✅ Injection (SQL / Command / Path Traversal / Template)
No injection risks identified. The only URL construction in the new code is in setSearchParams, where the query string is built with URLSearchParams.toString(), which properly percent-encodes all key-value pairs. The pathname is sourced from trusted browser APIs (window.location.pathname or router.state.location.pathname), not from user input.
✅ Authentication / Authorization Bypasses
Not in scope for this PR. The changes are purely client-side routing abstractions. No auth gates, session tokens, or permission checks are touched.
✅ Secrets Handling / Token Leakage
No secrets are introduced, logged, or exposed. The api.baseURL / api.basePath values are plain configuration strings provided by the app developer. The documented process.env.BASE_URL pattern is conventional SSR config — not a secret and not logged.
✅ Unsafe Deserialization / SSRF
No deserialization occurs. The StackApiConfig.baseURL is developer-controlled (set in StackProvider props), not derived from user input, so there is no SSRF surface introduced by this PR.
✅ XSS / Open Redirect
All three link wrapper components spread extra props through to framework-native Link components (Next.js, React Router, TanStack). React's own attribute sanitization guards against javascript: hrefs in the rendered DOM. Framework Link components apply their own validation on top. No new XSS surface is opened.
The navigate function accepts an arbitrary string path and delegates to the framework router — the same trust model that existed before this PR. Plugins calling navigate(userInput) with unvalidated user data would be an application-level concern, not one introduced here.
✅ Supply-Chain / Dependency Risk
The new optional peer dependencies (next ≥15, react-router ≥7, @tanstack/react-router ≥1) are all widely-used, actively maintained packages with established security track records. Lock file entries use pinned integrity hashes. No suspicious or unknown packages are added.
Low-Severity Observations (not blocking)
href || "#"fallback inNextLinkWrapper— if a plugin renders a link without anhref, it silently navigates to#instead of failing visibly. No security impact, but could mask missing-href bugs during development.process.env.BASE_URLundocumented production requirement — the docs example falls back tohttp://localhost:3000on the server whenBASE_URLis unset. In a misconfigured production deploy, SSR-side API calls would silently target localhost. Worth a callout in the docs example, but not a vulnerability.
No action required from a security standpoint. The implementation is clean.
Sent by Cursor Automation: Find vulnerabilities
|
✅ Shadcn registry validated — no registry changes detected. |
Co-authored-by: Cursor <cursoragent@cursor.com>


Closes #127
Summary
StackProvidernow accepts optionalrouterandapiprops. Resolution order inusePluginOverridesis per-plugin override → top-levelrouter/api→ hook defaults, so existing per-pluginLink/navigate/etc. overrides keep working as an escape hatch (fully non-breaking;overridesis now optional).nextRouter()from@btst/stack/next,reactRouter()from@btst/stack/react-router,tanstackRouter()from@btst/stack/tanstack. Presets are plain objects; fields needing framework hooks are produced by auseRouterhook the provider evaluates internally via aRouterBridgechild (no conditional hook calls). The frameworks are optional peer dependencies and externalized in the build.StackRoutercontract already includesgetSearchParams/setSearchParamsas required by URL-synced list state: filters, tabs, and pagination survive refresh, back button, and sharing #133, so the three framework bindings only need to be touched once. The Next.jsgetSearchParamsreadswindow.location.searchinstead ofuseSearchParams()to avoid a Suspense/CSR bailout during SSG.pageslayouts (Next.js layout: 424 → ~200 lines, remaining bulk is genuinely plugin-specific demo config) plus all satellitedirectory/*andcms-exampleproviders to use the presets.Test plan
pnpm build,pnpm typecheck,pnpm lint,pnpm knip(stack) pass locallypnpm vitest runinpackages/stack— 235 tests pass, including newrouter-overrides.test.tsxMade with Cursor