Skip to content

feat: custom plugin marketplace support - #3656

Merged
diegosouzapw merged 8 commits into
diegosouzapw:release/v3.8.24from
oyi77:feat/plugin-marketplace-custom-url
Jun 13, 2026
Merged

diegosouzapw merged 8 commits into
diegosouzapw:release/v3.8.24from
oyi77:feat/plugin-marketplace-custom-url

Conversation

@oyi77

@oyi77 oyi77 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

This PR adds support for Custom Plugin Marketplaces as requested, extending the current local-seed-only implementation.

Changes:

  • Registry Backend: Updated `src/lib/plugins/marketplace.ts` to dynamically fetch from a custom URL if one is set in the system settings, falling back to the local `SEED_REGISTRY`.
  • API Endpoint: Created `GET /api/plugins/marketplace` to expose the registry to the frontend.
  • Settings Schema: Added `pluginMarketplaceUrl` to the global `updateSettingsSchema`.
  • Marketplace UI: Revamped `src/app/(dashboard)/dashboard/plugins/page.tsx` to include a dual-tab layout ("Installed" and "Marketplace"). The "Marketplace" tab allows users to browse available plugins and configure their custom marketplace URL directly.

- Updated Plugin Marketplace to support custom registry URLs instead of just local seed
- Added custom marketplace URL configuration in the UI
- Added a full marketplace browser tab in the Plugins dashboard
@oyi77
oyi77 requested a review from diegosouzapw as a code owner June 11, 2026 14:01

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a new Marketplace tab to the plugins dashboard, allowing users to configure a custom marketplace URL and browse or install plugins. The backend is updated to fetch marketplace entries asynchronously from the configured URL with a fallback to a local seed registry. The code review feedback highlights several critical improvements: adding validation for external JSON data fetched from the custom marketplace to prevent runtime crashes, checking the res.ok status on fetch requests to handle API errors gracefully, reverting a potential breaking change that made isMarketplaceAvailable asynchronous, and replacing hardcoded English strings in the UI with the t translation hook to support internationalization.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +68 to +83
const handleSaveUrl = async () => {
setSavingUrl(true);
try {
await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pluginMarketplaceUrl: marketplaceUrl || null }),
});
addNotification({ type: "success", message: "Marketplace URL updated" });
await fetchMarketplace();
} catch {
addNotification({ type: "error", message: "Failed to update URL" });
} finally {
setSavingUrl(false);
}
};

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.

high

The fetch API does not throw an error on non-2xx HTTP status codes (like 400 or 500). If the PATCH request to /api/settings fails (e.g., due to validation errors), the code will still display a success notification and attempt to reload the marketplace. You should check res.ok and throw an error to trigger the catch block.

  const handleSaveUrl = async () => {
    setSavingUrl(true);
    try {
      const res = await fetch("/api/settings", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ pluginMarketplaceUrl: marketplaceUrl || null }),
      });
      if (!res.ok) {
        throw new Error("Failed to update settings");
      }
      addNotification({ type: "success", message: "Marketplace URL updated" });
      await fetchMarketplace();
    } catch {
      addNotification({ type: "error", message: "Failed to update URL" });
    } finally {
      setSavingUrl(false);
    }
  };

Comment on lines +92 to 107
export async function listMarketplacePlugins(): Promise<MarketplaceEntry[]> {
try {
const settings = await getSettings();
const url = typeof settings.pluginMarketplaceUrl === "string" ? settings.pluginMarketplaceUrl : null;
if (url) {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (res.ok) {
const data = await res.json();
return Array.isArray(data) ? data : (data.plugins || []);
}
}
} catch (err) {
console.error("Failed to fetch from custom plugin marketplace:", err);
}
return [...SEED_REGISTRY];
}

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.

high

Fetching external JSON data from a user-configured URL and casting it directly to MarketplaceEntry[] without validation is risky. If the custom marketplace returns malformed data or is missing required fields (like name, version, description, or tags), it can cause runtime crashes (e.g., TypeError: Cannot read properties of undefined) in search, filtering, or UI rendering. \n\nAdditionally, if the response is not successful (res.ok is false), the error is silently ignored and falls back to the seed registry. It would be highly beneficial to log a warning with the status code for better observability.

export async function listMarketplacePlugins(): Promise<MarketplaceEntry[]> {
  try {
    const settings = await getSettings();
    const url = typeof settings.pluginMarketplaceUrl === "string" ? settings.pluginMarketplaceUrl : null;
    if (url) {
      const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
      if (res.ok) {
        const data = await res.json();
        const rawPlugins = Array.isArray(data) ? data : (data.plugins || []);
        return rawPlugins
          .filter((p: any) => p && typeof p === "object" && typeof p.name === "string" && typeof p.version === "string")
          .map((p: any) => ({
            name: p.name,
            version: p.version,
            description: typeof p.description === "string" ? p.description : "",
            author: typeof p.author === "string" ? p.author : "Unknown",
            license: typeof p.license === "string" ? p.license : "Unknown",
            downloadUrl: typeof p.downloadUrl === "string" ? p.downloadUrl : "",
            repository: typeof p.repository === "string" ? p.repository : undefined,
            tags: Array.isArray(p.tags) ? p.tags.filter((t: any) => typeof t === "string") : [],
            downloads: typeof p.downloads === "number" ? p.downloads : 0,
            rating: typeof p.rating === "number" ? p.rating : 0,
            verified: Boolean(p.verified),
            lastUpdated: typeof p.lastUpdated === "string" ? p.lastUpdated : "",
          }));
      } else {
        console.warn(`Custom plugin marketplace returned status ${res.status}: ${res.statusText}`);
      }
    }
  } catch (err) {
    console.error("Failed to fetch from custom plugin marketplace:", err);
  }
  return [...SEED_REGISTRY];
}

Comment on lines +45 to +49
fetch("/api/settings")
.then((res) => res.json())
.then((data) => {
if (data.pluginMarketplaceUrl) setMarketplaceUrl(data.pluginMarketplaceUrl);
});

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.

medium

The fetch call to /api/settings does not check if the response is successful (res.ok) before parsing it as JSON. If the request fails (e.g., 401 Unauthorized), res.json() might fail or return an error object, which could lead to unexpected behavior. It is safer to check res.ok first.

    fetch("/api/settings")
      .then((res) => res.ok ? res.json() : null)
      .then((data) => {
        if (data?.pluginMarketplaceUrl) setMarketplaceUrl(data.pluginMarketplaceUrl);
      })
      .catch(() => {});

Comment thread src/lib/plugins/marketplace.ts Outdated
Comment on lines 134 to 141
export async function isMarketplaceAvailable(): Promise<boolean> {
try {
const settings = await getSettings();
return true; // Always available (falls back to seed)
} catch {
return false;
}
}

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.

medium

Changing isMarketplaceAvailable to be an async function returning a Promise<boolean> is a potential breaking change for any existing synchronous callers in the codebase. Since the function always returns true (falling back to the seed registry) and does not actually use any values from settings, it is safer to keep it synchronous to maintain backward compatibility.

export function isMarketplaceAvailable(): boolean {
  return true; // Local seed always available
}

Comment on lines +134 to +141
<div className="flex gap-2">
<Button variant={activeTab === "installed" ? "primary" : "secondary"} onClick={() => setActiveTab("installed")}>
Installed
</Button>
<Button variant={activeTab === "marketplace" ? "primary" : "secondary"} onClick={() => setActiveTab("marketplace")}>
Marketplace
</Button>
</div>

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.

medium

The tab labels 'Installed' and 'Marketplace' are hardcoded in English. To support internationalization (i18n) and maintain consistency with the rest of the page, please use the t translation hook.

Suggested change
<div className="flex gap-2">
<Button variant={activeTab === "installed" ? "primary" : "secondary"} onClick={() => setActiveTab("installed")}>
Installed
</Button>
<Button variant={activeTab === "marketplace" ? "primary" : "secondary"} onClick={() => setActiveTab("marketplace")}>
Marketplace
</Button>
</div>
<div className="flex gap-2">
<Button variant={activeTab === "installed" ? "primary" : "secondary"} onClick={() => setActiveTab("installed")}>
{t("installed")}
</Button>
<Button variant={activeTab === "marketplace" ? "primary" : "secondary"} onClick={() => setActiveTab("marketplace")}>
{t("marketplace")}
</Button>
</div>

Comment on lines +144 to +160
{activeTab === "marketplace" && (
<Card className="p-4 flex gap-4 items-end bg-gray-50">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">Custom Marketplace URL</label>
<input
type="text"
className="w-full rounded border-gray-300 p-2"
placeholder="Leave empty for official Omniroute registry"
value={marketplaceUrl}
onChange={(e) => setMarketplaceUrl(e.target.value)}
/>
</div>
<Button onClick={handleSaveUrl} disabled={savingUrl}>
Save & Reload
</Button>
</Card>
)}

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.

medium

The form labels, placeholders, and button text are hardcoded in English. Please use the t translation hook to support internationalization (i18n).

Suggested change
{activeTab === "marketplace" && (
<Card className="p-4 flex gap-4 items-end bg-gray-50">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">Custom Marketplace URL</label>
<input
type="text"
className="w-full rounded border-gray-300 p-2"
placeholder="Leave empty for official Omniroute registry"
value={marketplaceUrl}
onChange={(e) => setMarketplaceUrl(e.target.value)}
/>
</div>
<Button onClick={handleSaveUrl} disabled={savingUrl}>
Save & Reload
</Button>
</Card>
)}
{activeTab === "marketplace" && (
<Card className="p-4 flex gap-4 items-end bg-gray-50">
<div className="flex-1">
<label className="block text-sm font-medium text-gray-700 mb-1">{t("customMarketplaceUrl")}</label>
<input
type="text"
className="w-full rounded border-gray-300 p-2"
placeholder={t("customMarketplaceUrlPlaceholder")}
value={marketplaceUrl}
onChange={(e) => setMarketplaceUrl(e.target.value)}
/>
</div>
<Button onClick={handleSaveUrl} disabled={savingUrl}>
{t("saveAndReload")}
</Button>
</Card>
)}

Comment on lines +219 to 251
{marketplacePlugins.length === 0 ? (
<div className="text-gray-500 py-4">No plugins found in marketplace.</div>
) : (
marketplacePlugins.map((plugin) => (
<Card key={plugin.name} className="p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold flex items-center gap-2">
{plugin.name}
{plugin.verified && <Badge variant="success">Verified</Badge>}
</h3>
<p className="text-sm text-gray-500">
v{plugin.version} by {plugin.author} — {plugin.description}
</p>
<div className="mt-1 flex gap-1">
{plugin.tags?.map((tag: string) => (
<span key={tag} className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
{tag}
</span>
))}
</div>
</div>
<div className="flex gap-2">
<Button
variant="primary"
onClick={async () => {
addNotification({ type: "success", message: `Plugin ${plugin.name} installed! (Mock)` });
}}
>
Install
</Button>
</div>
</div>

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.

medium

The marketplace list empty state, badge text, and install button text are hardcoded in English. Please use the t translation hook to support internationalization (i18n).

          {marketplacePlugins.length === 0 ? (
             <div className="text-gray-500 py-4">{t("noMarketplacePlugins")}</div>
          ) : (
            marketplacePlugins.map((plugin) => (
              <Card key={plugin.name} className="p-4">
                <div className="flex items-center justify-between">
                  <div>
                    <h3 className="font-semibold flex items-center gap-2">
                      {plugin.name}
                      {plugin.verified && <Badge variant="success">{t("verified")}</Badge>}
                    </h3>
                    <p className="text-sm text-gray-500">
                      v{plugin.version} by {plugin.author} — {plugin.description}
                    </p>
                    <div className="mt-1 flex gap-1">
                      {plugin.tags?.map((tag: string) => (
                        <span key={tag} className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
                          {tag}
                        </span>
                      ))}
                    </div>
                  </div>
                  <div className="flex gap-2">
                    <Button
                      variant="primary"
                      onClick={async () => {
                        addNotification({ type: "success", message: t("pluginInstalledMock", { name: plugin.name }) });
                      }}
                    >
                      {t("install")}
                    </Button>
                  </div>
                </div>


useEffect(() => {
fetchPlugins();
fetch("/api/settings")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: /api/settings GET has no error handling.

fetch can reject on network failures and this promise chain has no .catch(), causing unhandled promise rejections. Add .catch(() => {}) or an async effect to keep page render stable.


const fetchMarketplace = useCallback(async () => {
try {
const res = await fetch("/api/plugins/marketplace");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Missing marketplace API endpoint.

fetch("/api/plugins/marketplace") references a route that does not exist under src/app/api/plugins/. The marketplace tab will always fail with 404 until this route is added or an existing endpoint is used.

const res = await fetch("/api/plugins/marketplace");
if (res.ok) {
const data = await res.json();
setMarketplacePlugins(data.plugins || []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Marketplace response shape is inconsistent.

listMarketplacePlugins() returns MarketplaceEntry[], but this UI expects data.plugins. If the endpoint returns the helper's array directly, the marketplace list will render empty. Align the response shape or normalize it here.

const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pluginMarketplaceUrl: marketplaceUrl || null }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Marketplace URL setting is not persisted.

pluginMarketplaceUrl is not present in updateSettingsSchema and is not defaulted in getSettings(). Zod strips unknown keys, so PATCH /api/settings can return success while silently ignoring this field, making the save notification misleading.

const settings = await getSettings();
const url = typeof settings.pluginMarketplaceUrl === "string" ? settings.pluginMarketplaceUrl : null;
if (url) {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Arbitrary custom marketplace URL enables SSRF.

Server-side fetch() uses the configured URL without validating scheme, hostname, or length. A management user or compromised setting could make the server request internal metadata endpoints or private network resources. Add allowlisted http/https URL validation and host/IP restrictions before fetch.

<div className="flex gap-2">
<Button
variant="primary"
onClick={async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Marketplace install button is a mock.

The button only shows a success notification and never calls an install API or updates installed plugins. Users will see "installed" even though no plugin was installed; either wire up the install flow or label this as unavailable.

@kilo-code-bot

kilo-code-bot Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 6 New Issues Found (7 existing active comments remain) | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 4
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
src/app/(dashboard)/dashboard/plugins/page.tsx 54 Missing /api/plugins/marketplace route; marketplace fetch will 404.
src/app/(dashboard)/dashboard/plugins/page.tsx 74 pluginMarketplaceUrl is not in updateSettingsSchema, so PATCH can succeed while silently stripping the setting.
src/lib/plugins/marketplace.ts 97 Server-side fetch of arbitrary custom URL enables SSRF without URL/host validation.
src/app/(dashboard)/dashboard/plugins/page.tsx 249 Marketplace "Install" button only shows a success notification and never installs anything.

WARNING

File Line Issue
src/app/(dashboard)/dashboard/plugins/page.tsx 45 /api/settings GET fetch has no .catch() and can produce unhandled promise rejections.
src/app/(dashboard)/dashboard/plugins/page.tsx 57 UI expects { plugins }, but listMarketplacePlugins() returns an array, risking an empty marketplace list.
Other Observations (not in diff)

Existing active comments were already present and were not duplicated. Seven active comments remain at src/app/(dashboard)/dashboard/plugins/page.tsx:88, src/lib/plugins/marketplace.ts:119, src/app/(dashboard)/dashboard/plugins/page.tsx:49, src/lib/plugins/marketplace.ts:153, src/app/(dashboard)/dashboard/plugins/page.tsx:146, src/app/(dashboard)/dashboard/plugins/page.tsx:165, and src/app/(dashboard)/dashboard/plugins/page.tsx:256.

Files Reviewed (3 files)
  • src/app/(dashboard)/dashboard/plugins/page.tsx - 5 new issues
  • src/lib/plugins/marketplace.ts - 1 new issue
  • src/i18n/messages/en.json - 0 new issues

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash-20260528 · 715,840 tokens

@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.8.23 June 12, 2026 01:18
@diegosouzapw

Copy link
Copy Markdown
Owner

Obrigado pela contribuição, @oyi77! Não dá pra mergear como está — segue o que falta:

  1. Botão "Install" é um no-op — ele só dispara addNotification, não instala nada. Implemente a instalação real (download + registro do plugin) ou remova o botão até o backend existir.
  2. isMarketplaceAvailable tem query morta — adicionou getSettings() + try/catch mas sempre retorna true; ou use o resultado, ou remova.
  3. Sem testes (Hard Rule fix(ci): add environment for npm token access #18) — a rota /api/plugins/marketplace e o fetch em marketplace.ts precisam de testes (sucesso, timeout, URL inválida).
  4. Valide o schema/domínio da URL do marketplace antes de salvar.

Deixo aberta — quando ajustar, reavalio.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.23 to release/v3.8.24 June 13, 2026 02:52
oyi77 added 3 commits June 13, 2026 16:08
…able sync

- Add isSafeMarketplaceUrl() helper that validates http/https scheme and
  rejects private/loopback IPs via DNS resolution
- Insert SSRF guard in listMarketplacePlugins() before fetch call
- Replace async isMarketplaceAvailable() with simple sync function
  that no longer calls getSettings()
- Add SSRF validation (isSafeMarketplaceUrl) to reject private/loopback IPs
- Clean up isMarketplaceAvailable — remove dead getSettings() call
- Create GET /api/plugins/marketplace route returning { plugins: [...] }
- Add .catch() to settings fetch in plugins page
- Replace mock install button with proper 'coming soon' message
- Add tests for marketplace route, list/search/get/isAvailable
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @oyi77 — clean, self-contained feature and a welcome one (it's the real backend behind the marketplace surface that was only documented before). Validated: the GET /api/plugins/marketplace route gates on requireManagementAuth, errors route through buildErrorBody (Rule #12), and marketplace.ts ships a proper SSRF guard for custom registry URLs (http/https only; blocks 127.0.0.0/8, 10/8, 192.168/16, 169.254/16) with a 5s fetch timeout — defaulting to a local seed registry. 7/7 tests pass on the merged state, lint clean, check:docs-symbols green (route recognized), and it has zero dependency on the plugin-IPC (#3668) or sandbox (#3657) PRs. Merging into release/v3.8.24. 🙌

@diegosouzapw
diegosouzapw merged commit 7efd407 into diegosouzapw:release/v3.8.24 Jun 13, 2026
2 checks passed
diegosouzapw added a commit that referenced this pull request Jun 13, 2026
…ding) (#3774)

Harden marketplace SSRF guard (IPv6/AAAA + redirect-block + fail-closed resolve). Follow-up to #3656. Integrated into release/v3.8.24.
diegosouzapw added a commit that referenced this pull request Jun 13, 2026
…contributor credits

- Restructure [3.8.24] into ✨ Features / 🔒 Security / 🐛 Fixed / 📝 Maintenance
- Add bullets for every PR landed since v3.8.23 that was missing:
  marketplace (#3656), strict-mode CC defaults (#3776), emergency-fallback flag (#3752),
  xhigh effort (#3756), Codex memory WS (#3749), IPv6 egress (#3777),
  marketplace SSRF (#3774), CodeQL/Dependabot (#3778), anthropic sampling (#3780),
  thinking passthrough (#3775), mcp dist entry (#3765), streamed tool args (#3762),
  logs light-mode (#3760), clean-history purge (#3751), quality-gates (#3757),
  docs gaps (#3453), file-size re-baseline (#3770), E415 publish guard, i18n prune
- Move misplaced #3775 bullet out of [Unreleased] into [3.8.24]
- Date [3.8.23] header (TBD -> 2026-06-12, the release tag date)
@diegosouzapw diegosouzapw mentioned this pull request Jun 13, 2026
diegosouzapw added a commit that referenced this pull request Jun 13, 2026
…n stale codex/plugins tests

- Date the [3.8.24] section (2026-06-13) and add the missing CHANGELOG bullet
  for the combo + quota-shared deep audit (#3779).
- Align two unit tests left stale by intentional behavior changes this cycle:
  executor-codex now asserts gpt-5.4-mini xhigh passthrough (#3756 default),
  and plugins-route-error-sanitization now covers the /api/plugins/marketplace
  route added by #3656 (verified Hard Rule #12 compliant). No production change.
diegosouzapw added a commit that referenced this pull request Jun 13, 2026
…leanup (#3800)

Surfaces the Plugins page (marketplace, #3656) in the sidebar; adds the proxy IP-family selector (auto/ipv4/ipv6) completing #3777's UI; clears the remaining CodeQL URL-substring alerts; covers #3799 with tests and fixes the costs-section count.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…leanup (diegosouzapw#3800)

Surfaces the Plugins page (marketplace, diegosouzapw#3656) in the sidebar; adds the proxy IP-family selector (auto/ipv4/ipv6) completing diegosouzapw#3777's UI; clears the remaining CodeQL URL-substring alerts; covers diegosouzapw#3799 with tests and fixes the costs-section count.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…leanup (diegosouzapw#3800)

Surfaces the Plugins page (marketplace, diegosouzapw#3656) in the sidebar; adds the proxy IP-family selector (auto/ipv4/ipv6) completing diegosouzapw#3777's UI; clears the remaining CodeQL URL-substring alerts; covers diegosouzapw#3799 with tests and fixes the costs-section count.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
feat: custom plugin marketplace (GET /api/plugins/marketplace + SSRF-guarded registry). Integrated into release/v3.8.24.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…ding) (diegosouzapw#3774)

Harden marketplace SSRF guard (IPv6/AAAA + redirect-block + fail-closed resolve). Follow-up to diegosouzapw#3656. Integrated into release/v3.8.24.
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…contributor credits

- Restructure [3.8.24] into ✨ Features / 🔒 Security / 🐛 Fixed / 📝 Maintenance
- Add bullets for every PR landed since v3.8.23 that was missing:
  marketplace (diegosouzapw#3656), strict-mode CC defaults (diegosouzapw#3776), emergency-fallback flag (diegosouzapw#3752),
  xhigh effort (diegosouzapw#3756), Codex memory WS (diegosouzapw#3749), IPv6 egress (diegosouzapw#3777),
  marketplace SSRF (diegosouzapw#3774), CodeQL/Dependabot (diegosouzapw#3778), anthropic sampling (diegosouzapw#3780),
  thinking passthrough (diegosouzapw#3775), mcp dist entry (diegosouzapw#3765), streamed tool args (diegosouzapw#3762),
  logs light-mode (diegosouzapw#3760), clean-history purge (diegosouzapw#3751), quality-gates (diegosouzapw#3757),
  docs gaps (diegosouzapw#3453), file-size re-baseline (diegosouzapw#3770), E415 publish guard, i18n prune
- Move misplaced diegosouzapw#3775 bullet out of [Unreleased] into [3.8.24]
- Date [3.8.23] header (TBD -> 2026-06-12, the release tag date)
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…bullet, align stale codex/plugins tests

- Date the [3.8.24] section (2026-06-13) and add the missing CHANGELOG bullet
  for the combo + quota-shared deep audit (diegosouzapw#3779).
- Align two unit tests left stale by intentional behavior changes this cycle:
  executor-codex now asserts gpt-5.4-mini xhigh passthrough (diegosouzapw#3756 default),
  and plugins-route-error-sanitization now covers the /api/plugins/marketplace
  route added by diegosouzapw#3656 (verified Hard Rule diegosouzapw#12 compliant). No production change.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…leanup (diegosouzapw#3800)

Surfaces the Plugins page (marketplace, diegosouzapw#3656) in the sidebar; adds the proxy IP-family selector (auto/ipv4/ipv6) completing diegosouzapw#3777's UI; clears the remaining CodeQL URL-substring alerts; covers diegosouzapw#3799 with tests and fixes the costs-section count.
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