Skip to content

refactor: use getClientApiBaseUrl in chat libs#1633

Merged
arpitgupta1214 merged 10 commits into
testfrom
codex/use-client-api-base-url-in-libs
Apr 3, 2026
Merged

refactor: use getClientApiBaseUrl in chat libs#1633
arpitgupta1214 merged 10 commits into
testfrom
codex/use-client-api-base-url-in-libs

Conversation

@arpitgupta1214
Copy link
Copy Markdown
Collaborator

@arpitgupta1214 arpitgupta1214 commented Apr 2, 2026

Summary

  • replace manual baseUrl threading with getClientApiBaseUrl() in chat lib helpers
  • update hook callsites to stop passing manual base URLs
  • keep override behavior for chat transport unchanged

Validation

  • pnpm exec eslint hooks/useArtistFromRoom.ts hooks/useChatSegment.ts hooks/useCreateArtistTool.ts hooks/useDeleteChat.ts hooks/useMessageLoader.ts hooks/useVercelChat.ts lib/messages/copyMessages.ts lib/messages/getChatMessages.ts lib/chats/getChatArtist.ts lib/chats/getChatSegment.ts lib/chats/deleteChat.ts

Summary by CodeRabbit

  • Refactor
    • Standardized client-side API base URL resolution across the app for consistent runtime behavior.
    • Removed the in-app manual API override mechanism persisted across sessions; manual base-URL overrides are no longer supported.
    • Simplified chat and messaging flows to rely on centralized base URL resolution without changing visible app behavior.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 2, 2026

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

Project Deployment Actions Updated (UTC)
recoup-chat Ready Ready Preview Apr 3, 2026 2:46pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR removes the useApiOverride hook and caller-provided baseUrl params, centralizes runtime API base URL resolution via getClientApiBaseUrl(), adds ApiOverrideSync to persist query-param overrides into sessionStorage, and updates affected hooks and helpers (including useMessageLoader signature). (33 words)

Changes

Cohort / File(s) Summary
Removed hook & API-override callers
hooks/useApiOverride.ts, hooks/useArtistFromRoom.ts, hooks/useChatSegment.ts, hooks/useCreateArtistTool.ts, hooks/useDeleteChat.ts, hooks/useVercelChat.ts
Deleted useApiOverride; callers stop importing/using it and no longer pass apiOverride ?? undefined into API calls; minor dependency-array adjustments where applicable.
Message loader / chat hooks
hooks/useMessageLoader.ts, hooks/useVercelChat.ts
useMessageLoader signature no longer accepts apiOverride; call sites updated to getChatMessages(roomId, accessToken) and effect deps adjusted (apiOverride removed, setMessages added where applicable).
Dropped baseUrl from chat/message helpers
lib/chats/deleteChat.ts, lib/chats/getChatArtist.ts, lib/chats/getChatSegment.ts, lib/messages/copyMessages.ts, lib/messages/getChatMessages.ts
Removed optional baseUrl?: string from function signatures; each helper now resolves base URL internally via getClientApiBaseUrl() and no longer accepts caller overrides.
Global switch to runtime base URL
lib/api/getClientApiBaseUrl.ts, many hooks and libs (e.g., hooks/useChatTransport.ts, hooks/useAccountOrganizations.ts, hooks/useCreateChat.tsx, lib/* files listed in summary)
Replaced NEW_API_BASE_URL (and other static URL constants) with getClientApiBaseUrl() across many modules so base URL is resolved at runtime.
Client sync provider
providers/ApiOverrideSync.tsx, providers/Providers.tsx
Added ApiOverrideSync component that reads the api search param and synchronizes it into sessionStorage under API_OVERRIDE_STORAGE_KEY; rendered at app root inside Providers.
Const addition
lib/consts.ts
Added API_OVERRIDE_STORAGE_KEY constant ("recoup_api_override").
Pulse and related changes
lib/pulse/*, other small modules
Replaced compile-time/exported pulse URL constant with runtime calls to getClientApiBaseUrl(); similar runtime URL updates applied to various other utility modules.
Signature change
hooks/useMessageLoader.ts
Removed apiOverride parameter from exported hook signature. Attention: callers must match new signature.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ApiOverrideSync
  participant SessionStorage
  participant getClientApiBaseUrl
  participant API

  Browser->>ApiOverrideSync: mount (reads searchParams)
  ApiOverrideSync->>SessionStorage: set / remove API_OVERRIDE_STORAGE_KEY
  Browser->>getClientApiBaseUrl: called by client code
  getClientApiBaseUrl->>SessionStorage: read API_OVERRIDE_STORAGE_KEY (if window)
  getClientApiBaseUrl-->>Browser: return resolved base URL
  Browser->>API: fetch(`${base}/api/...`, Authorization)
  API-->>Browser: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • sweetmantech

Poem

🌐 A query param whispered "change",
Session storage rearranged,
Hooks trimmed their extra weight,
Base URLs now find their fate,
Cleaner paths, a quieter range.

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request violates SOLID principles (SRP, DRY), uses bare try/catch blocks, and contains unvalidated URL parsing security vulnerability. Extract validation/normalization functions, implement host allowlisting with protocol checks, create buildApiUrl factory function, and replace bare catch blocks with explicit error handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/use-client-api-base-url-in-libs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a25fb87b5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread hooks/useDeleteChat.ts
throw new Error("Authentication token is missing. Please refresh and try again.");
}
return deleteChat(roomId, accessToken, apiOverride ?? undefined);
return deleteChat(roomId, accessToken);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve API override when deleting chats outside chat view

useDeleteChat now calls deleteChat(roomId, accessToken) without reading useApiOverride, so this mutation only honors an override if sessionStorage was already populated elsewhere. On routes that render the sidebar/Recent Chats but do not mount useChatTransport (for example non-chat pages), visiting with ?api=<url> no longer redirects delete requests to the override backend and will hit NEW_API_BASE_URL instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/getConversations.tsx (1)

2-27: ⚠️ Potential issue | 🟠 Major

Prevent Bearer-token exfiltration via URL override

At Line 27, getClientApiBaseUrl() can honor ?api=... overrides (per lib/api/getClientApiBaseUrl.ts), and this request still sends Authorization: Bearer ... (Line 34). That allows token-bearing requests to be redirected to an arbitrary origin via a crafted URL.

Please restrict override behavior for authenticated requests (e.g., allowlist/internal env only, or a non-overridable resolver for auth-protected endpoints).

As per coding guidelines, "Implement built-in security practices for authentication and data handling."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/getConversations.tsx` around lines 2 - 27, getConversations currently
calls getClientApiBaseUrl() (which honors ?api overrides) and then sends
Authorization: Bearer ..., enabling token exfiltration if a malicious base URL
is supplied; fix it by ensuring authenticated endpoints use a non-overridable
base resolver or validate the resolved URL domain before attaching the
Authorization header: change getConversations to call a secure resolver (e.g.,
getServerApiBaseUrl or add a noOverride flag to getClientApiBaseUrl) or perform
an allowlist check on the returned url string and abort/return [] if it’s not in
the trusted domains, and only attach the Authorization header to fetch if the
url passes that validation.
🧹 Nitpick comments (3)
lib/artists/fetchArtists.ts (1)

31-31: Optional: avoid emitting a trailing ? when no query params are set.

Line 31 always appends ?, even when orgId is absent. It’s harmless in most cases, but building the URL conditionally keeps cache keys/logs cleaner.

Optional cleanup diff
-  const response = await fetch(`${getClientApiBaseUrl()}/api/artists?${params.toString()}`, {
+  const query = params.toString();
+  const url = `${getClientApiBaseUrl()}/api/artists${query ? `?${query}` : ""}`;
+  const response = await fetch(url, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/artists/fetchArtists.ts` at line 31, The request URL in fetchArtists.ts
always appends a trailing '?' even when params is empty; update the code that
builds the fetch URL (the call using getClientApiBaseUrl() and
params.toString()) to only append `?${params.toString()}` when params.toString()
is non-empty (e.g., compute a baseUrl via getClientApiBaseUrl() and
conditionally concat the query string) so response fetching logic and cache
keys/logs don't include a stray '?'.
lib/composio/api/disconnectConnectorApi.ts (1)

28-30: Consider enriching the error message with response context.

The current error is generic, which can make debugging harder when failures occur. Including the HTTP status or a snippet of the response body would improve diagnosability without exposing sensitive details.

♻️ Suggested improvement
   if (!response.ok) {
-    throw new Error("Failed to disconnect connector");
+    throw new Error(`Failed to disconnect connector (status: ${response.status})`);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/composio/api/disconnectConnectorApi.ts` around lines 28 - 30, The error
thrown in disconnectConnectorApi (the branch that checks if !response.ok) is too
generic; update the error to include response context such as HTTP status and a
short response body or statusText to aid debugging. Modify the error
construction in disconnectConnectorApi to await and include a safe snippet of
response.text() or response.json() (truncated to a reasonable length) alongside
response.status/response.statusText, and ensure no sensitive data is logged;
keep the message concise and descriptive (e.g., "Failed to disconnect connector:
status X - snippet...").
hooks/useAddArtistToOrganization.ts (1)

15-37: Narrow useCallback dependency to be more explicit.

The addArtistToOrganization callback depends on the entire options object, but only uses onSuccess from it. Extract onSuccess at the hook level and depend on it directly for a clearer, more precise dependency that documents the actual dependency and prevents unnecessary recreations if callers ever pass options without memoization.

♻️ Proposed refactor
 const useAddArtistToOrganization = (options?: UseAddArtistToOrganizationOptions) => {
   const [addingToOrgId, setAddingToOrgId] = useState<string | null>(null);
+  const onSuccess = options?.onSuccess;

   const addArtistToOrganization = useCallback(
     async (artistId: string, organizationId: string) => {
       setAddingToOrgId(organizationId);
       try {
         const response = await fetch(`${getClientApiBaseUrl()}/api/organizations/artists`, {
           method: "POST",
           body: JSON.stringify({
             artistId,
             organizationId,
           }),
           headers: { "Content-Type": "application/json" },
         });

         if (response.ok) {
-          options?.onSuccess?.(organizationId);
+          onSuccess?.(organizationId);
           return true;
         }
         return false;
       } finally {
         setAddingToOrgId(null);
       }
     },
-    [options]
+    [onSuccess]
   );

Per coding guidelines: "hooks/**/*.ts: ... Use proper dependency arrays."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useAddArtistToOrganization.ts` around lines 15 - 37, The useCallback
for addArtistToOrganization currently depends on the whole options object;
destructure options to pull out onSuccess at the hook level (e.g., const {
onSuccess } = options) and then change the dependency array to depend explicitly
on onSuccess (and any other direct dependencies like getClientApiBaseUrl or
setAddingToOrgId if they are not stable) so the callback only re-creates when
needed; update the body of addArtistToOrganization to call onSuccess directly
instead of options?.onSuccess?. and keep setAddingToOrgId and any stable helpers
out of the array if they are stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/sandboxes/deleteSandbox.ts`:
- Line 4: The fetch in deleteSandbox.ts uses getClientApiBaseUrl() which honors
?api= overrides and can redirect bearer-authenticated requests to attacker
origins; update the code that issues the authenticated fetch (the call building
the Authorization-bearing request in deleteSandbox.ts and similarly the fetch in
getSandboxes.ts) to first validate or replace the base URL: either call a
trusted server-side base URL helper that ignores query overrides or implement an
allowlist check that parses getClientApiBaseUrl() output and rejects/throws if
the origin is not an approved trusted origin (or strip any ?api override before
concatenation); ensure the code that builds the Authorization header (the
authenticated fetch) only runs after validation and add a clear error path when
the origin is invalid.

---

Outside diff comments:
In `@lib/getConversations.tsx`:
- Around line 2-27: getConversations currently calls getClientApiBaseUrl()
(which honors ?api overrides) and then sends Authorization: Bearer ..., enabling
token exfiltration if a malicious base URL is supplied; fix it by ensuring
authenticated endpoints use a non-overridable base resolver or validate the
resolved URL domain before attaching the Authorization header: change
getConversations to call a secure resolver (e.g., getServerApiBaseUrl or add a
noOverride flag to getClientApiBaseUrl) or perform an allowlist check on the
returned url string and abort/return [] if it’s not in the trusted domains, and
only attach the Authorization header to fetch if the url passes that validation.

---

Nitpick comments:
In `@hooks/useAddArtistToOrganization.ts`:
- Around line 15-37: The useCallback for addArtistToOrganization currently
depends on the whole options object; destructure options to pull out onSuccess
at the hook level (e.g., const { onSuccess } = options) and then change the
dependency array to depend explicitly on onSuccess (and any other direct
dependencies like getClientApiBaseUrl or setAddingToOrgId if they are not
stable) so the callback only re-creates when needed; update the body of
addArtistToOrganization to call onSuccess directly instead of
options?.onSuccess?. and keep setAddingToOrgId and any stable helpers out of the
array if they are stable.

In `@lib/artists/fetchArtists.ts`:
- Line 31: The request URL in fetchArtists.ts always appends a trailing '?' even
when params is empty; update the code that builds the fetch URL (the call using
getClientApiBaseUrl() and params.toString()) to only append
`?${params.toString()}` when params.toString() is non-empty (e.g., compute a
baseUrl via getClientApiBaseUrl() and conditionally concat the query string) so
response fetching logic and cache keys/logs don't include a stray '?'.

In `@lib/composio/api/disconnectConnectorApi.ts`:
- Around line 28-30: The error thrown in disconnectConnectorApi (the branch that
checks if !response.ok) is too generic; update the error to include response
context such as HTTP status and a short response body or statusText to aid
debugging. Modify the error construction in disconnectConnectorApi to await and
include a safe snippet of response.text() or response.json() (truncated to a
reasonable length) alongside response.status/response.statusText, and ensure no
sensitive data is logged; keep the message concise and descriptive (e.g.,
"Failed to disconnect connector: status X - snippet...").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 143448b0-ad15-4696-9a28-5fbf21b332b7

📥 Commits

Reviewing files that changed from the base of the PR and between 0141536 and 43e1a5b.

📒 Files selected for processing (27)
  • hooks/useAccountOrganizations.ts
  • hooks/useAddArtistToOrganization.ts
  • hooks/useCreateChat.tsx
  • hooks/useCreateOrganization.ts
  • hooks/useOrgSettings.ts
  • lib/api/artist/getArtistSocials.ts
  • lib/artists/fetchArtists.ts
  • lib/catalog/getCatalogSongs.ts
  • lib/catalog/postCatalogSongs.ts
  • lib/chat/validateHeaders.ts
  • lib/chats/updateChat.ts
  • lib/composio/api/authorizeConnectorApi.ts
  • lib/composio/api/disconnectConnectorApi.ts
  • lib/composio/api/fetchConnectorsApi.ts
  • lib/getConversations.tsx
  • lib/keys/createApiKey.ts
  • lib/keys/deleteApiKey.ts
  • lib/keys/fetchApiKeys.ts
  • lib/pulse/getPulse.ts
  • lib/pulse/updatePulse.ts
  • lib/sandboxes/deleteSandbox.ts
  • lib/sandboxes/getFileContents.ts
  • lib/sandboxes/getSandboxes.ts
  • lib/sandboxes/uploadSandboxFiles.ts
  • lib/spotify/getSpotifyFollowers.ts
  • lib/tasks/getTaskRuns.ts
  • lib/tools/getArtistSegments.ts
✅ Files skipped from review due to trivial changes (2)
  • hooks/useOrgSettings.ts
  • lib/keys/fetchApiKeys.ts


export async function deleteSandbox(accessToken: string): Promise<void> {
const response = await fetch(`${NEW_API_BASE_URL}/api/sandboxes`, {
const response = await fetch(`${getClientApiBaseUrl()}/api/sandboxes`, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Block untrusted API overrides for bearer-authenticated requests

Line 4 now resolves the base URL from getClientApiBaseUrl(), which accepts ?api= overrides (lib/api/getClientApiBaseUrl.ts:8-18). That allows redirecting this Authorization header to an attacker-controlled origin and leaking access tokens. Enforce a trusted-origin check (or disable query-param override) before issuing authenticated fetch calls. This same risk also applies to lib/sandboxes/getSandboxes.ts Line 20.

Suggested hardening (example)
 import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl";
+import { NEW_API_BASE_URL } from "@/lib/consts";

 export async function deleteSandbox(accessToken: string): Promise<void> {
-  const response = await fetch(`${getClientApiBaseUrl()}/api/sandboxes`, {
+  const apiBaseUrl = getClientApiBaseUrl();
+  if (new URL(apiBaseUrl).origin !== new URL(NEW_API_BASE_URL).origin) {
+    throw new Error("Untrusted API base URL for authenticated request");
+  }
+
+  const response = await fetch(`${apiBaseUrl}/api/sandboxes`, {
     method: "DELETE",
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   });

As per coding guidelines, "Implement built-in security practices for authentication and data handling."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sandboxes/deleteSandbox.ts` at line 4, The fetch in deleteSandbox.ts uses
getClientApiBaseUrl() which honors ?api= overrides and can redirect
bearer-authenticated requests to attacker origins; update the code that issues
the authenticated fetch (the call building the Authorization-bearing request in
deleteSandbox.ts and similarly the fetch in getSandboxes.ts) to first validate
or replace the base URL: either call a trusted server-side base URL helper that
ignores query overrides or implement an allowlist check that parses
getClientApiBaseUrl() output and rejects/throws if the origin is not an approved
trusted origin (or strip any ?api override before concatenation); ensure the
code that builds the Authorization header (the authenticated fetch) only runs
after validation and add a clear error path when the origin is invalid.

Comment thread lib/api/getClientApiBaseUrl.ts Outdated
import { NEW_API_BASE_URL } from "@/lib/consts";

const API_OVERRIDE_STORAGE_KEY = "recoup_api_override";
function normalizeApiBaseUrl(url: string): string {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SRP - new lib function file for normalizeApiBaseUrl.

Comment thread lib/pulse/getPulse.ts Outdated
import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl";

export const PULSE_API_URL = `${NEW_API_BASE_URL}/api/pulses`;
export function getPulseApiUrl(): string {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SRP - new lib file for getPulseApiUrl.

@sweetmantech
Copy link
Copy Markdown
Collaborator

Browser Spot-Check: ?api= Override Behavior (10 Endpoints)

Tested on preview deployment: recoup-chat-git-codex-use-client-api-d2d3a5-recoupable-ad724970.vercel.app
Override value: https://recoup-api-git-codex-arpit-migrate-m-29f770-recoupable-ad724970.vercel.app/

Method

Used Chrome DevTools MCP to navigate the deployed preview, capture network requests, and verify which host each API call targets — both with and without the ?api= query parameter.

Results

# Endpoint Lib / Caller No override (default host) With override (?api=...) Status
1 GET /api/chats lib/getConversations.tsx test-recoup-api.vercel.app Override host ✅ PASS
2 GET /api/chats/:id/artist lib/chats/getChatArtist.ts test-recoup-api.vercel.app Override host ✅ PASS
3 GET /api/chats/:id/messages lib/messages/getChatMessages.ts test-recoup-api.vercel.app Override host ✅ PASS
4 GET /api/organizations hooks/useAccountOrganizations.ts test-recoup-api.vercel.app Override host ✅ PASS
5 GET /api/artists lib/artists/fetchArtists.ts test-recoup-api.vercel.app Override host ✅ PASS
6 GET /api/tasks lib/tasks/getTasks.ts test-recoup-api.vercel.app Override host ✅ PASS
7 GET /api/tasks/runs lib/tasks/getTaskRuns.ts test-recoup-api.vercel.app Override host ✅ PASS
8 GET /api/connectors lib/composio/api/fetchConnectorsApi.ts test-recoup-api.vercel.app Override host ✅ PASS
9 GET /api/keys lib/keys/fetchApiKeys.ts test-recoup-api.vercel.app Override host ✅ PASS
10 GET /api/chats (sidebar) lib/getConversations.tsx test-recoup-api.vercel.app Override host ✅ PASS

Notes

  • 10/10 endpoints pass — all correctly use the default API host when ?api= is absent, and the override host when ?api= is present.
  • The ?api= query param is read from window.location.search at call time (via getClientApiBaseUrl()), so it works on any page as long as the param is in the URL.
  • Important caveat: Client-side navigations (e.g., clicking sidebar links) strip the ?api= param from the URL, causing subsequent API calls to fall back to the default host. This is expected behavior since the old useApiOverride hook persisted to sessionStorage, while the new implementation reads directly from the URL. For testing, navigating directly with ?api= in the URL works correctly.
  • 404s on /api/chats/:id/artist and /api/chats/:id/messages appear in both override and non-override — this is a data mismatch (chat IDs differ between backends), not an override bug.

🤖 Generated with Claude Code using Chrome DevTools MCP

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@providers/ApiOverrideSync.tsx`:
- Around line 10-27: The useEffect currently accepts any parseable URL for the
?api= override (variables: searchParams.get("api"), normalizedApiParam,
API_OVERRIDE_STORAGE_KEY), which is a security risk; update the handler so after
parsing the URL you validate its origin against a whitelist (e.g., require
protocol === "https:" and host === window.location.host or a configured
ALLOWED_API_HOSTS list) and only persist normalizedApiParam when it matches
those constraints; on any validation failure or exception
removeItem(API_OVERRIDE_STORAGE_KEY) (fail closed) instead of silently ignoring,
and ensure the try/catch logs or triggers the same clear behavior so a crafted
or invalid value cannot remain in session storage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dbb8f3db-8aaf-43f3-8a81-06d350d4b7e5

📥 Commits

Reviewing files that changed from the base of the PR and between d1d9640 and 0b9c8df.

📒 Files selected for processing (6)
  • lib/api/getClientApiBaseUrl.ts
  • lib/consts.ts
  • lib/pulse/getPulse.ts
  • lib/pulse/updatePulse.ts
  • providers/ApiOverrideSync.tsx
  • providers/Providers.tsx
✅ Files skipped from review due to trivial changes (1)
  • lib/consts.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/pulse/updatePulse.ts
  • lib/pulse/getPulse.ts
  • lib/api/getClientApiBaseUrl.ts

Comment on lines +10 to +27
useEffect(() => {
const apiParam = searchParams.get("api");

try {
if (apiParam === "clear") {
window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
return;
}

if (!apiParam) {
return;
}

const normalizedApiParam = new URL(apiParam).toString().replace(/\/+$/, "");
window.sessionStorage.setItem(API_OVERRIDE_STORAGE_KEY, normalizedApiParam);
} catch {
// Ignore invalid URLs and storage failures.
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Constrain ?api= overrides to trusted targets (and fail closed).

Any parseable URL can currently become the session API base. A crafted link can persist a malicious origin for subsequent client calls. Restrict protocol/host and clear override on invalid input.

Suggested hardening
 export default function ApiOverrideSync() {
   const searchParams = useSearchParams();

   useEffect(() => {
     const apiParam = searchParams.get("api");
+    const allowedHosts = (process.env.NEXT_PUBLIC_API_OVERRIDE_ALLOWLIST ?? "")
+      .split(",")
+      .map((host) => host.trim())
+      .filter(Boolean);

     try {
       if (apiParam === "clear") {
         window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
         return;
       }

       if (!apiParam) {
         return;
       }

-      const normalizedApiParam = new URL(apiParam).toString().replace(/\/+$/, "");
+      const parsedApi = new URL(apiParam);
+      const isHttp = parsedApi.protocol === "http:" || parsedApi.protocol === "https:";
+      const isAllowed =
+        process.env.NODE_ENV !== "production" ||
+        allowedHosts.includes(parsedApi.host);
+
+      if (!isHttp || !isAllowed) {
+        window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
+        return;
+      }
+
+      const normalizedApiParam = parsedApi.toString().replace(/\/+$/, "");
       window.sessionStorage.setItem(API_OVERRIDE_STORAGE_KEY, normalizedApiParam);
     } catch {
-      // Ignore invalid URLs and storage failures.
+      window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
     }
   }, [searchParams]);

   return null;
 }

As per coding guidelines, "Implement built-in security practices for authentication and data handling".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const apiParam = searchParams.get("api");
try {
if (apiParam === "clear") {
window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
return;
}
if (!apiParam) {
return;
}
const normalizedApiParam = new URL(apiParam).toString().replace(/\/+$/, "");
window.sessionStorage.setItem(API_OVERRIDE_STORAGE_KEY, normalizedApiParam);
} catch {
// Ignore invalid URLs and storage failures.
}
useEffect(() => {
const apiParam = searchParams.get("api");
const allowedHosts = (process.env.NEXT_PUBLIC_API_OVERRIDE_ALLOWLIST ?? "")
.split(",")
.map((host) => host.trim())
.filter(Boolean);
try {
if (apiParam === "clear") {
window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
return;
}
if (!apiParam) {
return;
}
const parsedApi = new URL(apiParam);
const isHttp = parsedApi.protocol === "http:" || parsedApi.protocol === "https:";
const isAllowed =
process.env.NODE_ENV !== "production" ||
allowedHosts.includes(parsedApi.host);
if (!isHttp || !isAllowed) {
window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
return;
}
const normalizedApiParam = parsedApi.toString().replace(/\/+$/, "");
window.sessionStorage.setItem(API_OVERRIDE_STORAGE_KEY, normalizedApiParam);
} catch {
window.sessionStorage.removeItem(API_OVERRIDE_STORAGE_KEY);
}
}, [searchParams]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@providers/ApiOverrideSync.tsx` around lines 10 - 27, The useEffect currently
accepts any parseable URL for the ?api= override (variables:
searchParams.get("api"), normalizedApiParam, API_OVERRIDE_STORAGE_KEY), which is
a security risk; update the handler so after parsing the URL you validate its
origin against a whitelist (e.g., require protocol === "https:" and host ===
window.location.host or a configured ALLOWED_API_HOSTS list) and only persist
normalizedApiParam when it matches those constraints; on any validation failure
or exception removeItem(API_OVERRIDE_STORAGE_KEY) (fail closed) instead of
silently ignoring, and ensure the try/catch logs or triggers the same clear
behavior so a crafted or invalid value cannot remain in session storage.

@sweetmantech
Copy link
Copy Markdown
Collaborator

Retest: Session Storage Persistence (commit 0b9c8dfa)

Retested after Arpit's latest changes adding ApiOverrideSync provider + sessionStorage persistence.

Test Matrix

# Test Case URL after navigation Expected API host Actual API host Result
1 /?api=... initial load /?api=... Override Override (/api/chats, /api/organizations, /api/artists, /api/chats/:id/artist, /api/chats/:id/messages) PASS
2 Client-side nav to /tasks (no ?api= in URL) /tasks Override (persisted) Override (/api/tasks, /api/tasks/runs, /api/artists) PASS
3 ?api=clear clears override /?api=clear Default Default (test-recoup-api.vercel.app for all endpoints) PASS

Key Improvement Verified

The previous version lost the override on client-side navigation (clicking sidebar links stripped ?api= from URL). The new ApiOverrideSync provider writes the override to sessionStorage on mount, so getClientApiBaseUrl() reads from storage and persists across all client-side navigations. This is confirmed working in test #2 above.

Code Review Notes

  • ApiOverrideSync is a clean "use client" provider that syncs ?api= param → sessionStorage
  • getClientApiBaseUrl() now reads from sessionStorage (was reading from window.location.search)
  • ?api=clear properly removes the sessionStorage key
  • getPulseApiUrl was inlined back into getPulse.ts (simpler than separate file)
  • API_OVERRIDE_STORAGE_KEY extracted to lib/consts.ts (shared between provider and getter)

All tests pass. Session storage persistence is working correctly.

🤖 Generated with Claude Code using Chrome DevTools MCP

@arpitgupta1214
Copy link
Copy Markdown
Collaborator Author

Browser interaction retest on deployed preview (https://recoup-chat-git-codex-use-client-api-d2d3a5-recoupable-ad724970.vercel.app) using override https://recoup-api-git-codex-arpit-migrate-m-29f770-recoupable-ad724970.vercel.app/.

Lib Endpoint No override (?api=clear) With override (?api=...) Result
lib/getConversations.tsx GET /api/chats Hit default host test-recoup-api.vercel.app Hit override host Pass
lib/chats/getChatArtist.ts GET /api/chats/:id/artist Hit default host Hit override host Pass
lib/messages/getChatMessages.ts GET /api/chats/:id/messages Hit default host Hit override host Pass
hooks/useChatTransport.ts POST /api/chat Hit default host Still hit default host test-recoup-api.vercel.app Fail
hooks/useAccountOrganizations.ts GET /api/organizations Hit default host Hit override host Pass
lib/artists/fetchArtists.ts GET /api/artists Hit default host Hit override host Pass
lib/tasks/getTasks.ts GET /api/tasks Hit default host Hit override host Pass
lib/tasks/getTaskRuns.ts GET /api/tasks/runs Hit default host Hit override host Pass
lib/composio/api/fetchConnectorsApi.ts GET /api/connectors Hit default host Hit override host Pass
lib/sandboxes/getSandboxes.ts GET /api/sandboxes Hit default host Hit override host Pass
lib/keys/fetchApiKeys.ts GET /api/keys Hit default host Hit override host Pass
lib/keys/createApiKey.ts POST /api/keys Hit default host and key created successfully Hit override host and key created successfully Pass
lib/keys/deleteApiKey.ts DELETE /api/keys Hit default host and key deleted successfully Hit override host and key deleted successfully Pass

@arpitgupta1214 arpitgupta1214 merged commit 02287fa into test Apr 3, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 3, 2026
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.

2 participants