feat(ai-chat): complete phase 2 client sweep - #149
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Security Review — feat(ai-chat): complete phase 2 client sweep
Summary
No high-confidence vulnerabilities were found. The PR refactors the AI Chat client plugin to use a new resource-factory pattern, adds i18n/localization support, and introduces client-side permission gating (useCan / CanAccess). One low-severity observation is noted below.
Threat-area findings
| Area | Finding | Severity |
|---|---|---|
| Authn/authz boundary | New canWrite / CanAccess / ComposedRoute permission= guards are UI-only — server enforcement must exist independently |
ℹ️ Note (existing arch) |
| Info disclosure | Raw error.message from API calls surfaced to the user in two places |
|
| XSS / injection | toolName from AI response interpolated into UI strings |
✅ Safe (JSX text, not HTML) |
| Secrets / logging | No credentials or secrets added or logged | ✅ Safe |
| Dependencies | Only new export paths added to package.json; no new third-party packages |
✅ Safe |
| Deserialization / SSRF | No new network calls or deserialization paths introduced | ✅ Safe |
[LOW] Raw API error messages surfaced to end users
Two call sites pass error.message directly from a caught API response into a user-visible notification:
1. chat-sidebar.tsx — delete failure
catch (error) {
notify.error(
error instanceof Error
? error.message // ← raw server error string
: tr("CONVERSATION_DELETE_FAILURE", ...)
);
}2. chat-hooks.tsx — rename form
errorMessage: (error) =>
error.message || // ← raw server error string
localization?.CONVERSATION_RENAME_FAILURE ||
t("aiChat.toasts.renameFailure", ...),Risk: If the backend ever emits verbose error messages (database query details, stack traces, internal resource IDs), those strings will be shown verbatim in the toast to the authenticated user who made the request. There is no XSS risk because React renders the string as text content. Impact is limited to the session owner seeing their own server-side error details.
Recommendation: Sanitize or normalise API errors before surfacing them. A simple guard is sufficient:
// Only use error.message when it looks like a safe user-facing string
const safeMessage = isUserFacingError(error)
? error.message
: t("aiChat.toasts.deleteFailure", "Failed to delete conversation");Alternatively, standardise the backend to return structured { userMessage: string } error shapes and only read that field on the client.
Note on client-side permission gating
The new useCan / CanAccess / ComposedRoute permission= wrappers are UI-only; they do not replace server-side enforcement. This is the standard pattern for this codebase and the backend plugin endpoints (unchanged here) must independently validate ownership before executing mutations. The canWrite expression correctly defaults to false while the permission check is pending (fail-closed), which is the correct secure default.
Sent by Cursor Automation: Find vulnerabilities
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fce2b2f. Configure here.
|
✅ Shadcn registry updated — registry JSON files were rebuilt and committed to this branch. |



Summary
Verification
pnpm buildpnpm typecheckpnpm test(591 tests)pnpm -F @btst/stack test-registryThe new streaming rename/delete E2E requires a real
OPENAI_API_KEY; it is committed for keyed CI coverage. The existing localpnpm-lock.yamlchange was intentionally excluded.Note
Medium Risk
Touches conversation cache, client permission gates, and public-mode bypass behavior. Backward-compatible when no auth provider is set, but auth and cache invalidation need review.
Overview
Finishes the AI Chat phase-2 client sweep: conversations use a shared
aiChatResources/createAiChatQueryKeysdeclaration (stableconversations.list/detailkeys), rename merges into the detail cache, and delete removes that cache entry. Drops@lukemorales/query-key-factoryand exports@btst/stack/plugins/ai-chat/query-keys.Authenticated UI now gates
ai-chat:conversationread/create/update/delete viaStackProviderauth; public mode stays permissive. Copy goes through thei18ncatalog (aiChat.*), with legacylocalizationstill winning. Rename/delete/upload feedback usesnotify. AddsuseRenameConversationForm(trim, field errors, toasts).Docs list the catalog keys,
/chat/conversationsAPIs, permission map, and query-key import. E2E adds sidebar rename/delete coverage and more reliable chat navigation.Reviewed by Cursor Bugbot for commit 18167b8. Bugbot is set up for automated code reviews on this repo. Configure here.