Skip to content

feat(web-search): allow custom Exa-compatible endpoints - #1699

Merged
Astro-Han merged 3 commits into
mainfrom
feat/1694-custom-search-endpoint
Oct 2, 2026
Merged

Astro-Han merged 3 commits into
mainfrom
feat/1694-custom-search-endpoint

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Users running an Exa-compatible proxy can set an optional Base URL under Plugins → Websearch. Saving it sends the next search to that endpoint’s /search, including keyless proxies; leaving it blank preserves the built-in route. Reuse DSH’s Exa provider for HTTP requests and result normalization.

Reject malformed endpoint URLs before saving and at the search boundary. Credential-bearing remote endpoints require HTTPS; loopback HTTP remains supported. Replacement Exa keys use fresh credential references, activated together with the endpoint in one revision-fenced settings mutation. A failed save preserves the previous endpoint/key pair and retains drafts for retry. In-flight searches continue resolving the original reference; previous references are retained for those readers.

Validation: 588 Vitest tests (including 65 focused web-search tests), 122 Node tests and 10 label checks; typecheck, lint and build; real macOS Electron raw CDP smoke. The focused Electron scenario exercised malformed/insecure URL feedback, a real Host revision-conflict refusal, safe failure/retry, four model-driven web_search requests and restart persistence. The complete suite passes through the worktree’s pnpm-generated launcher; direct invocation without its NODE_PATH cannot resolve four transitive-package fixtures. Windows packaging and smoke run in CI.

Refresh only Astro’s transitive devalue lock from 5.9.0 to 5.9.4 to clear three newly published high audit advisories. Astro’s existing dependency range is unchanged. High audit and site check/build pass.

Closes #1694.

Summary by CodeRabbit

  • New Features
    • Exa search now supports an optional custom service URL. Leave it blank to use the default endpoint; clearing a custom URL restores the default.
    • Added guidance and validation for custom URLs. They must be absolute HTTP(S) URLs without credentials, query parameters, or fragments. Remote HTTP endpoints are not allowed when an API key is configured.
  • Bug Fixes
    • Failed saves retain your changes so you can correct the issue and retry.

Read the optional Base URL on each search and reuse the upstream Exa provider for requests and result normalization. Custom endpoints can work without a key; clearing the URL restores the existing built-in routing.

Stage the URL in the existing settings card and verify settings writes before clearing drafts. Hide Exa URL drafts while editing DeepSeek.

Validated with full tests, typecheck, lint, raw Electron CDP smoke, and four model-driven web_search calls against a local fixture including keyless access, endpoint switching, a configured key, and restart persistence.
@Astro-Han Astro-Han added enhancement New feature or request P2 Medium priority harness Model harness, prompts, tool descriptions, and session mechanics labels Oct 1, 2026
@github-actions github-actions Bot added app Application behavior and product flows platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions labels Oct 1, 2026

@github-actions github-actions 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.

Suggested priority: P2 (includes user-path files (packages/desktop-electron/resources/dsh/web-search/lib/client.js, packages/desktop-electron/resources/dsh/web-search/lib/index.js, packages/desktop-electron/src/main/dsh-web-search-client.test.ts, packages/desktop-electron/src/main/dsh-web-search-plugin.test.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: Astro-Han/pawwork/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: ebab081b-4735-42c2-bc68-b732c4f34347

📥 Commits

Reviewing files that changed from the base of the PR and between 8217c0c and fc71230.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • packages/desktop-electron/resources/dsh/web-search/lib/client.js
  • packages/desktop-electron/resources/dsh/web-search/lib/index.js
  • packages/desktop-electron/src/main/dsh-web-search-client.test.ts
  • packages/desktop-electron/src/main/dsh-web-search-plugin.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

The web-search settings card accepts an optional Exa-compatible base URL. The provider validates and uses that URL for search requests. When the URL and API key are blank, the keyless MCP path remains available.

Changes

Custom Exa-compatible endpoint

Layer / File(s) Summary
Provider configuration and routing
packages/desktop-electron/resources/dsh/web-search/lib/index.js, packages/desktop-electron/src/main/dsh-web-search-plugin.test.ts
Config stores the Exa base URL and passes it to the provider. The provider validates the URL and uses it for Exa-compatible requests when a key or URL is configured. Tests cover invalid URLs, HTTP restrictions, request construction, and endpoint changes between searches.
Endpoint draft, card, and settings save
packages/desktop-electron/resources/dsh/web-search/lib/client.js, packages/desktop-electron/src/main/dsh-web-search-client.test.ts
The card stages and validates an Exa URL, shows the URL field only for Exa, and provides localized endpoint and key guidance. Saving stores Exa keys under generated references and applies settings with revision checks. Tests cover draft behavior, validation, stale revisions, failed writes, and retries.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant SettingsCard
  participant Config
  participant searchExa
  participant ExaEndpoint
  SettingsCard->>Config: Save exaBaseURL
  Config->>searchExa: Pass exaBaseURL
  searchExa->>ExaEndpoint: Send normalized /search request
Loading

Merge Risk: ⚪ Minimal · up to fc712

This change adds an optional custom Exa-compatible endpoint to web search. Built-in Exa routing and keyless search are preserved when the field is blank. No merge-blocking risk was identified in the reviewed changes.

Security Architecture Review

Security architecture risk: 🔵 Low · up to fc712

Custom destinations can receive search queries and the selected API key. Transport validation and redirect rejection constrain credential exposure, while fresh key references protect ongoing searches during replacement. Remaining uncertainty concerns failed-save recovery and unused stored keys, rather than a demonstrated credential leak.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is the configured desktop search provider's queries and selected Exa credential at the user-selected destination. This expands destination choice beyond the built-in service, but the traced search request does not itself select the endpoint.

Security Findings and Attack Paths

  • observed — The supplied exact-revision verification rejects the redirect-based credential-exposure candidate because the inspected locked provider sets redirect:error on the fetch carrying Authorization. That is the strongest counterevidence; the provider source was unavailable for independent reinspection in this pass.

Trust Boundaries and Controls

  • observed — Endpoint validation runs both before card saves and after authoritative credential resolution at search time. Secret values are written through the credentials domain; the settings mutation contains the credential reference rather than its value.

Resilience and Maintainability Implications

  • inferred — During asynchronous credential inspection, the card can temporarily treat a selected reference as unconfigured and accept remote HTTP settings. The production search boundary independently resolves the key and rejects that combination before issuing a request, containing disclosure despite possible persisted policy inconsistency.

Hardening Proposals

  • proposed — Define ownership and reconciliation for generated credential references that never activate after failure or interruption. Cleanup should distinguish those references from intentionally retained credentials still needed by in-flight readers; absent external lifecycle evidence does not establish a credential leak.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #1694 requires an optional Base URL field, saved custom endpoint routing, usable Exa-compatible results, and unchanged built-in routing when the field is empty. The PR adds the Exa-only Base URL…
Out of Scope Changes check ✅ Passed The reviewable changes stay within issue #1694. URL validation, credential-reference handling, revision-checked atomic saves, draft retention, localization, provider routing, and regression tests supp…
Title check ✅ Passed The title clearly and concisely describes the primary change: support for custom Exa-compatible endpoints.
Description check ✅ Passed The description provides substantial summary, rationale, verification results, and risk-related details. It does not use the required section headings, but the required information is mostly present.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Allow custom Exa-compatible web search endpoints

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add an optional Exa Base URL so searches can use compatible proxies, including keyless ones.
• Preserve built-in routing when the URL is blank and apply endpoint changes on the next search.
• Extend staged settings and tests for saving, discarding, switching engines, and failed writes.
Diagram

graph TD
  Card["Settings card"] --> Config["Live config"] --> Provider["Search provider"] --> Engine{"Exa selected?"} -->|Yes| Route{"URL or key?"} -->|Yes| Exa["Exa provider"]
  Route -->|No| MCP["Hosted MCP"]
  Engine -->|No| DeepSeek["DeepSeek provider"]
Loading
High-Level Assessment

Reuse the existing Exa provider and single registered search provider. A separate provider would not fit the current user-selectable engine setup, while a custom HTTP implementation would duplicate request and result-normalization behavior.

Files changed (4) +133 / -16

Enhancement (2) +57 / -12
client.jsStage and save an Exa Base URL in the web search card +52/-10

Stage and save an Exa Base URL in the web search card

• Adds a localized, Exa-only URL field with discard and backend-specific draft behavior. Saves trimmed values through the existing settings flow, reads back writes before clearing drafts, and adjusts key guidance for custom endpoints.

packages/desktop-electron/resources/dsh/web-search/lib/client.js

index.jsRoute searches to a configured Exa-compatible endpoint +5/-2

Route searches to a configured Exa-compatible endpoint

• Adds a volatile Base URL read on every search. A configured URL uses the existing Exa provider and its result normalization even without a key; a blank URL preserves keyed Exa search or the keyless MCP fallback.

packages/desktop-electron/resources/dsh/web-search/lib/index.js

Tests (2) +76 / -4
dsh-web-search-client.test.tsCover Exa URL drafts and settings-write failures +37/-4

Cover Exa URL drafts and settings-write failures

• Tests staging, discarding, saving, and clearing the URL, plus hiding its draft while DeepSeek is selected. Extends failed-write coverage to confirm a rejected URL write leaves the card usable and its draft intact.

packages/desktop-electron/src/main/dsh-web-search-client.test.ts

dsh-web-search-plugin.test.tsCover custom endpoint requests and live routing +39/-0

Cover custom endpoint requests and live routing

• Tests keyed and keyless requests to a custom /search endpoint and normalization of its results. Verifies that changing or clearing the URL affects the next search and restores the hosted keyless route.

packages/desktop-electron/src/main/dsh-web-search-plugin.test.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Remote proxy traffic exposes search keys ✓ Resolved
Description
searchExa passes the resolved Exa key to the custom endpoint without restricting plaintext HTTP
for non-local addresses. When a user configures a remote http:// proxy and already has an Exa key,
search requests send that key in the Authorization header without transport encryption.
Code

packages/desktop-electron/resources/dsh/web-search/lib/index.js[R103-107]

+  const baseURL = (config.exaBaseURL ?? '').trim().replace(/\/+$/, '');
+  if (baseURL.length > 0 || apiKey.length > 0) {
 return new ExaSearchProvider({
   apiKey,
-      baseURL: 'https://api.exa.ai',
+      baseURL: baseURL || 'https://api.exa.ai',
Evidence
The new route pairs the configured URL with the resolved key, and the integration test confirms that
the provider sends that key as a bearer header to an HTTP custom endpoint. The repository applies an
HTTPS-or-loopback rule to another credential-bearing server URL, but this route has no such check.

packages/desktop-electron/resources/dsh/web-search/lib/index.js[100-110]
packages/desktop-electron/src/main/dsh-web-search-plugin.test.ts[173-188]
packages/desktop-electron/resources/dsh/mcp-oauth/lib/client.js[103-113]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A custom remote HTTP endpoint can receive a configured Exa key in a plaintext request.
## Fix Focus Areas
- packages/desktop-electron/resources/dsh/web-search/lib/index.js[101-110]
- packages/desktop-electron/src/main/dsh-web-search-plugin.test.ts[173-190]
## Recommended Fix
Before passing a resolved key to the provider, reject non-loopback HTTP endpoints when the key is nonempty; retain loopback HTTP for local proxies. Reuse the repository's existing HTTPS/loopback policy rather than adding a separate credential or transport. A runtime check is necessary because settings can be hand-edited and UI validation alone cannot protect the request; test remote HTTP with a key and loopback HTTP with and without one.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Searches fail after saving an invalid URL ✓ Resolved
Description
pendingWrites accepts any nonempty Base URL draft after trimming, while the backend schema also
accepts any string. Because Save is not a form submission, the URL input's browser constraint does
not stop a value such as not-a-url from being stored and used on the next search.
Code

packages/desktop-electron/resources/dsh/web-search/lib/client.js[R281-283]

+        if (this.backend() === "exa" && this.baseURLDraft !== undefined &&
+          this.baseURLDraft.trim() !== (this.scope.getSnapshot().value?.exaBaseURL ?? "")) {
+          writes.push({ field: "exaBaseURL", value: this.baseURLDraft.trim() })
Evidence
The card schedules every changed trimmed string for persistence, and its standalone Save button does
not invoke native URL constraint validation. The schema imposes no URL constraint, and the search
path passes any nonempty stored value to the provider.

packages/desktop-electron/resources/dsh/web-search/lib/client.js[276-284]
packages/desktop-electron/resources/dsh/web-search/lib/client.js[643-656]
packages/desktop-electron/resources/dsh/web-search/lib/client.js[681-694]
packages/desktop-electron/resources/dsh/web-search/lib/index.js[54-56]
packages/desktop-electron/resources/dsh/web-search/lib/index.js[100-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The settings card saves malformed endpoint strings, which are then used as search addresses.
## Fix Focus Areas
- packages/desktop-electron/resources/dsh/web-search/lib/client.js[276-284]
- packages/desktop-electron/resources/dsh/web-search/lib/index.js[100-107]
- packages/desktop-electron/src/main/dsh-web-search-client.test.ts[194-209]
## Recommended Fix
Validate a nonblank endpoint as an absolute HTTP or HTTPS base URL before permitting the settings write, and apply the same validity check at the search boundary for hand-edited settings. Reuse one endpoint rule where practical; the backend check is necessary because the card is not the only way settings can be written. Keep the empty value as the existing built-in route, and test malformed input in both paths.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Reject malformed Exa destinations and credential-bearing remote HTTP searches while retaining loopback and keyless proxies. Report endpoint errors before saving a credential.

Store each replacement Exa key under a new reference, then activate the reference and endpoint with one revision-fenced settings mutation. Failed saves retain the previous pair and drafts; in-flight searches keep their original credential. Retain previous references because searches may still be resolving them.

Verified 65 focused tests, 122 Node tests, typecheck, lint, build, raw Electron CDP smoke, and real Host refusal/retry/restart tests with model-driven web_search requests. The broader local Vitest run had 583 passes and four existing transitive dsh-web-app resolution failures, also present on main.
@Astro-Han

Copy link
Copy Markdown
Owner Author

Addressed the Qodo and CodeRabbit security findings in b3053cd:

  • Reject malformed URLs and credential-bearing non-loopback HTTP requests. The UI reports endpoint errors before either store is written; the runtime check also protects hand-edited settings.
  • Never overwrite an active Exa reference when rotating a key. Store the replacement separately and activate the destination/reference together through DSH’s atomic, revision-fenced configForms.mutate. The previous pair survives refusal; retries activate the new pair. Previous references remain available to in-flight readers.

The original failed-save leakage was reproduced through the real provider in a regression test. Real Electron CDP also induced an actual Host revision-conflict refusal: searches continued sending old-secret to the old destination; retry sent new-secret to the new destination, including after restart. All credentials were fixture values.

65 focused tests, 122 Node tests, typecheck/lint/build and raw CDP smoke passed. The broad local suite retained four existing dsh-web-app direct-resolution failures (583 passed), also present on main. The CodeRabbit docstring coverage warning does not justify adding redundant comments; its ESLint timeout is covered by the passing local lint check.

Update only the Astro transitive lockfile resolution from devalue 5.9.0 to 5.9.4 within its existing ^5.8.1 range. This clears GHSA-j22f-vq7h-c4qm, GHSA-mcm9-63f2-9j32, and GHSA-x5rw-q4pp-hg5g, which newly blocked the PR audit check.

Frozen install, high audit, site check and site build pass. The complete desktop suite passes through the pnpm-generated launcher: 588 Vitest, 122 Node and 10 label tests. Earlier direct-JS Vitest invocation omitted that launcher NODE_PATH; its four transitive-resolution failures were a test invocation issue, not repository defects.
@github-actions github-actions Bot added the ci Continuous integration / GitHub Actions label Oct 2, 2026
@Astro-Han

Copy link
Copy Markdown
Owner Author

Validation update: the complete local suite passes: 588 Vitest, 122 Node and 10 label checks. The earlier four transitive dsh-web-app failures came from directly invoking Vitest without the NODE_PATH supplied by pnpm’s generated launcher; they are not repository test defects.

The new CI audit failure is from three newly published advisories in site → astro → devalue@5.9.0, unrelated to search code. Refreshing only its compatible lockfile resolution to 5.9.4 clears high audit; the site check/build also pass.

@Astro-Han
Astro-Han merged commit 7738a23 into main Oct 2, 2026
13 checks passed
@Astro-Han
Astro-Han deleted the feat/1694-custom-search-endpoint branch October 2, 2026 01:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows ci Continuous integration / GitHub Actions enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Allow custom web_search endpoint instead of hardcoded Exa

1 participant