From 7c6f15d3c02e086078ab6e0926ce662e039f43f7 Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sun, 30 Aug 2026 16:49:47 +0800 Subject: [PATCH] fix: allow HTTP for configurable service endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DeepSeek Harness and Weixin binding configuration rejected non-loopback HTTP endpoints even when users explicitly selected those internal services. Configurable endpoints now accept valid HTTP and HTTPS URLs without host-location restrictions, while retaining the required protocol and API-key checks. Release note: 模型连接、DeepSeek Harness 与微信绑定现在可直接使用局域网或公网 HTTP/HTTPS 服务地址,不再强制公网 HTTPS 或本机回环地址。 --- .../deepseek-harness-runtime-design.md | 5 ++-- docs/roadmap/long-term-assistant-roadmap.md | 2 +- src/main/agent/create-runtime.test.ts | 2 +- src/main/agent/create-runtime.ts | 2 +- .../deepseek-harness-utility-launcher.test.ts | 13 +++++----- .../deepseek-harness-utility-launcher.ts | 2 +- src/main/agent/runtime-selection.ts | 2 +- .../channels/channel-settings-store.test.ts | 2 +- src/main/channels/channel-settings-store.ts | 10 +++++--- src/main/deepseek-harness-host.test.ts | 2 +- src/main/deepseek-harness-host.ts | 2 +- src/main/runtime-settings-store.test.ts | 25 +++---------------- src/main/runtime-settings-store.ts | 2 +- .../src/i18n/locales/en-US/settings.ts | 2 +- .../src/i18n/locales/zh-CN/settings.ts | 2 +- src/shared/contracts.ts | 2 +- .../deepseek-harness-compatibility.test.ts | 15 ++++++----- src/shared/deepseek-harness-compatibility.ts | 25 ++----------------- 18 files changed, 42 insertions(+), 75 deletions(-) diff --git a/docs/architecture/deepseek-harness-runtime-design.md b/docs/architecture/deepseek-harness-runtime-design.md index e3a840db..af025c03 100644 --- a/docs/architecture/deepseek-harness-runtime-design.md +++ b/docs/architecture/deepseek-harness-runtime-design.md @@ -163,7 +163,7 @@ Electron utilityProcess ├─ 本地 Shell / Filesystem Provider ├─ 最小工具集与 Main 代理 MCP └─ Main 明确启用的第三方 Cordis 插件 - │ HTTPS + │ HTTP/HTTPS ▼ 用户选择的 OpenAI 兼容模型连接 ``` @@ -426,8 +426,7 @@ DeepSeek Harness 首版只使用符合下列边界的 GoodBuddy 模型连接: - 协议必须是 `openai-chat-completions`。 - 认证必须是 API Key。 -- 公网服务地址必须使用 HTTPS;`localhost`、`127.0.0.1` 和 `::1` 回环地址可以使用 HTTP。 -- 服务地址可以使用自定义主机、端口和部署路径,但不得包含用户名、密码、查询参数或片段。 +- 服务地址支持 HTTP 和 HTTPS,不根据主机位置、端口、凭据、查询参数或片段增加额外限制。 - 模型名称不限制为 DeepSeek 品牌,由所选 OpenAI 兼容服务决定。 - 模型名称和服务地址由 Main 传入受控 Host。 - 图片能力只读取所选 GoodBuddy 模型连接的 `supportsImageInput`;Main、Utility 启动配置、ACP 能力和 Pi-AI 模型输入模态必须使用同一个布尔值。 diff --git a/docs/roadmap/long-term-assistant-roadmap.md b/docs/roadmap/long-term-assistant-roadmap.md index aa6f2c82..cd0bcdac 100644 --- a/docs/roadmap/long-term-assistant-roadmap.md +++ b/docs/roadmap/long-term-assistant-roadmap.md @@ -266,7 +266,7 @@ GoodBuddy 应能够: 1. 所有新 IPC 继续执行 Zod 校验和可信主窗口校验。 2. 项目根目录、成果路径和上下文路径必须 canonicalize 并验证目录包含关系。 3. Child Runtime 环境变量改用最小 allowlist,避免继承无关密钥。 -4. 远程委派仅允许 HTTPS,开发环境只放行 loopback。 +4. 远程委派支持用户配置的 HTTP 和 HTTPS 地址,不按主机位置增加额外限制。 5. 语音、窗口捕获和浏览器上下文分别授权。 6. 自动化不得绕过工具审批和项目权限。 7. 记忆必须保留来源和作用域。 diff --git a/src/main/agent/create-runtime.test.ts b/src/main/agent/create-runtime.test.ts index 35aeb73b..8f28acdf 100644 --- a/src/main/agent/create-runtime.test.ts +++ b/src/main/agent/create-runtime.test.ts @@ -100,7 +100,7 @@ describe('createAgentRuntime model compatibility', () => { { deepseekHarnessLauncher: vi.fn() } ) ).toThrow( - 'DeepSeek Harness 需要使用 API Key 的安全 OpenAI 兼容 Chat Completions 模型连接' + 'DeepSeek Harness 需要使用 API Key 的 OpenAI 兼容 Chat Completions 模型连接' ) }) diff --git a/src/main/agent/create-runtime.ts b/src/main/agent/create-runtime.ts index 0e5fbcc1..38210dcc 100644 --- a/src/main/agent/create-runtime.ts +++ b/src/main/agent/create-runtime.ts @@ -190,7 +190,7 @@ export function createAgentRuntime( const profile = settings?.deepseekHarnessModelProfile if (!profile || !isDeepSeekHarnessModelProfile(profile)) { throw new Error( - 'DeepSeek Harness 需要使用 API Key 的安全 OpenAI 兼容 Chat Completions 模型连接' + 'DeepSeek Harness 需要使用 API Key 的 OpenAI 兼容 Chat Completions 模型连接' ) } if (!profile.apiKey) { diff --git a/src/main/agent/deepseek-harness-utility-launcher.test.ts b/src/main/agent/deepseek-harness-utility-launcher.test.ts index 12e6836b..659d8344 100644 --- a/src/main/agent/deepseek-harness-utility-launcher.test.ts +++ b/src/main/agent/deepseek-harness-utility-launcher.test.ts @@ -50,7 +50,7 @@ async function fixture() { launchOptions: { cwd: workspace, signal: new AbortController().signal, - baseUrl: 'https://gateway.example/openai/v1', + baseUrl: 'http://gateway.example/openai/v1?api-version=1', model: 'qwen-plus', supportsImageInput: false, credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF], @@ -102,7 +102,7 @@ describe('DeepSeek Harness utility launcher', () => { expect(utility.messages[0]).toMatchObject({ type: 'start', config: { - baseUrl: 'https://gateway.example/openai/v1', + baseUrl: 'http://gateway.example/openai/v1?api-version=1', model: 'qwen-plus', supportsImageInput: false, credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF] @@ -235,10 +235,9 @@ describe('DeepSeek Harness utility launcher', () => { }) it.each([ - 'http://gateway.example/v1', - 'https://user:secret@gateway.example/v1', - 'https://gateway.example/v1?api-version=2025-01-01' - ])('rejects unsafe endpoint %s before forking', async (baseUrl) => { + 'file:///private/config', + 'ftp://gateway.example/v1' + ])('rejects unsupported endpoint %s before forking', async (baseUrl) => { const { dshHome, hostPath, launchOptions } = await fixture() const fork = vi.fn() const launcher = createDeepSeekHarnessUtilityLauncher({ @@ -250,7 +249,7 @@ describe('DeepSeek Harness utility launcher', () => { await expect( launcher({ ...launchOptions, baseUrl }) - ).rejects.toThrow('HTTPS') + ).rejects.toThrow('HTTP 或 HTTPS') expect(fork).not.toHaveBeenCalled() }) }) diff --git a/src/main/agent/deepseek-harness-utility-launcher.ts b/src/main/agent/deepseek-harness-utility-launcher.ts index 9ba12739..3c79ab85 100644 --- a/src/main/agent/deepseek-harness-utility-launcher.ts +++ b/src/main/agent/deepseek-harness-utility-launcher.ts @@ -149,7 +149,7 @@ export function createDeepSeekHarnessUtilityLauncher( } if (!isDeepSeekHarnessCompatibleBaseUrl(options.baseUrl)) { throw new Error( - 'DeepSeek Harness 模型地址必须使用 HTTPS 或本机回环 HTTP,且不得包含凭据、查询参数或片段' + 'DeepSeek Harness 模型地址必须使用 HTTP 或 HTTPS' ) } if ( diff --git a/src/main/agent/runtime-selection.ts b/src/main/agent/runtime-selection.ts index 754c5c4e..c0a6a89b 100644 --- a/src/main/agent/runtime-selection.ts +++ b/src/main/agent/runtime-selection.ts @@ -139,7 +139,7 @@ export function applyRuntimeSelection( !isDeepSeekHarnessModelProfile(selectedProfile) ) { throw new Error( - 'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接' + 'DeepSeek Harness 仅支持使用 API Key 的 OpenAI 兼容 Chat Completions 连接' ) } return { diff --git a/src/main/channels/channel-settings-store.test.ts b/src/main/channels/channel-settings-store.test.ts index 6a13b1ed..27b08e21 100644 --- a/src/main/channels/channel-settings-store.test.ts +++ b/src/main/channels/channel-settings-store.test.ts @@ -231,7 +231,7 @@ describe('ChannelSettingsStore', () => { const bound = await store.saveWeixinBinding({ accountId: 'account-123456', userId: 'user-654321', - baseUrl: 'https://ilinkai.weixin.qq.com', + baseUrl: 'http://ilinkai.weixin.qq.com', token: 'weixin-private-token' }) expect(bound.weixin).toMatchObject({ diff --git a/src/main/channels/channel-settings-store.ts b/src/main/channels/channel-settings-store.ts index 80ac08d2..60587f8d 100644 --- a/src/main/channels/channel-settings-store.ts +++ b/src/main/channels/channel-settings-store.ts @@ -300,9 +300,13 @@ const weixinBindingSchema = z .string() .url() .max(2_048) - .refine((value) => new URL(value).protocol === 'https:', { - message: '微信服务地址必须使用 HTTPS' - }), + .refine( + (value) => + ['http:', 'https:'].includes(new URL(value).protocol), + { + message: '微信服务地址必须使用 HTTP 或 HTTPS' + } + ), token: z .string() .trim() diff --git a/src/main/deepseek-harness-host.test.ts b/src/main/deepseek-harness-host.test.ts index 1b9884d6..a9a4d062 100644 --- a/src/main/deepseek-harness-host.test.ts +++ b/src/main/deepseek-harness-host.test.ts @@ -39,7 +39,7 @@ describe('controlled DeepSeek Harness host', () => { dshHome: 'C:\\controlled-dsh-home', skillPackages: [] }) - ).rejects.toThrow('secure OpenAI-compatible') + ).rejects.toThrow('HTTP or HTTPS OpenAI-compatible') }) it('suppresses console payloads instead of contaminating stdout', () => { diff --git a/src/main/deepseek-harness-host.ts b/src/main/deepseek-harness-host.ts index 5c5f8b4c..deaa10df 100644 --- a/src/main/deepseek-harness-host.ts +++ b/src/main/deepseek-harness-host.ts @@ -93,7 +93,7 @@ function validateHostConfig( !isDeepSeekHarnessCompatibleBaseUrl(config.baseUrl) ) { throw new Error( - 'Controlled Harness requires a secure OpenAI-compatible Chat Completions endpoint' + 'Controlled Harness requires an HTTP or HTTPS OpenAI-compatible Chat Completions endpoint' ) } if (!config.credentialRefs.length) { diff --git a/src/main/runtime-settings-store.test.ts b/src/main/runtime-settings-store.test.ts index 740f4cf1..66168355 100644 --- a/src/main/runtime-settings-store.test.ts +++ b/src/main/runtime-settings-store.test.ts @@ -458,7 +458,7 @@ describe('RuntimeSettingsStore', () => { const { store } = await createStore({ GOODBUDDY_MODEL_API_KEY: apiKey, GOODBUDDY_MODEL_BASE_URL: - 'https://gateway.example/openai/v1', + 'http://gateway.example/openai/v1?api-version=1', GOODBUDDY_MODEL_NAME: 'qwen-plus' }) @@ -467,7 +467,7 @@ describe('RuntimeSettingsStore', () => { deepseekHarnessModelProfile: { id: 'goodbuddy-platform-harness', name: '管理员预置模型', - baseUrl: 'https://gateway.example/openai/v1', + baseUrl: 'http://gateway.example/openai/v1?api-version=1', modelName: 'qwen-plus', protocol: 'openai-chat-completions', authentication: 'api-key', @@ -486,28 +486,11 @@ describe('RuntimeSettingsStore', () => { it.each([ [ - 'an insecure public endpoint', - { - GOODBUDDY_MODEL_API_KEY: 'platform-key', - GOODBUDDY_MODEL_BASE_URL: 'http://gateway.example/v1', - GOODBUDDY_MODEL_NAME: 'qwen-plus' - } - ], - [ - 'an endpoint with embedded credentials', + 'an unsupported endpoint protocol', { GOODBUDDY_MODEL_API_KEY: 'platform-key', GOODBUDDY_MODEL_BASE_URL: - 'https://user:secret@gateway.example/v1', - GOODBUDDY_MODEL_NAME: 'qwen-plus' - } - ], - [ - 'an endpoint with a query string', - { - GOODBUDDY_MODEL_API_KEY: 'platform-key', - GOODBUDDY_MODEL_BASE_URL: - 'https://gateway.example/v1?api-version=1', + 'ftp://gateway.example/v1', GOODBUDDY_MODEL_NAME: 'qwen-plus' } ], diff --git a/src/main/runtime-settings-store.ts b/src/main/runtime-settings-store.ts index 44610357..bae77a36 100644 --- a/src/main/runtime-settings-store.ts +++ b/src/main/runtime-settings-store.ts @@ -2290,7 +2290,7 @@ export class RuntimeSettingsStore { } if (!isDeepSeekHarnessModelProfile(profile)) { throw new Error( - 'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接' + 'DeepSeek Harness 仅支持使用 API Key 的 OpenAI 兼容 Chat Completions 连接' ) } } diff --git a/src/renderer/src/i18n/locales/en-US/settings.ts b/src/renderer/src/i18n/locales/en-US/settings.ts index d13b5178..a1fd1950 100644 --- a/src/renderer/src/i18n/locales/en-US/settings.ts +++ b/src/renderer/src/i18n/locales/en-US/settings.ts @@ -742,7 +742,7 @@ export const settings = { incompatibleImageProtocol: 'Incompatible (image generation protocol is unsupported)', incompatibleHarnessProtocol: - 'Incompatible (requires Chat Completions, an API key, and a secure endpoint)', + 'Incompatible (requires Chat Completions, an API key, and an HTTP/HTTPS endpoint)', secureStorageWarning: 'Secure system key storage is unavailable. Use an environment variable to avoid storing an API Key in plaintext.' }, diff --git a/src/renderer/src/i18n/locales/zh-CN/settings.ts b/src/renderer/src/i18n/locales/zh-CN/settings.ts index ee6dffce..62d8d035 100644 --- a/src/renderer/src/i18n/locales/zh-CN/settings.ts +++ b/src/renderer/src/i18n/locales/zh-CN/settings.ts @@ -681,7 +681,7 @@ export const settings = { incompatible: '不兼容', incompatibleImageProtocol: '不兼容(不支持图像生成协议)', incompatibleHarnessProtocol: - '不兼容(需 Chat Completions、API Key 和安全地址)', + '不兼容(需 Chat Completions、API Key 和 HTTP/HTTPS 地址)', secureStorageWarning: '当前系统密钥服务不可用。为了避免明文落盘,请使用环境变量提供 API Key。' }, diff --git a/src/shared/contracts.ts b/src/shared/contracts.ts index 547efc26..e3877c63 100644 --- a/src/shared/contracts.ts +++ b/src/shared/contracts.ts @@ -830,7 +830,7 @@ export const runtimeSettingsInputSchema = z code: 'custom', path: ['deepseekHarnessModelSource'], message: - 'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接' + 'DeepSeek Harness 仅支持使用 API Key 的 OpenAI 兼容 Chat Completions 连接' }) } const compressionSource = settings.contextCompression?.modelSource diff --git a/src/shared/deepseek-harness-compatibility.test.ts b/src/shared/deepseek-harness-compatibility.test.ts index 0ac32629..3bf791aa 100644 --- a/src/shared/deepseek-harness-compatibility.test.ts +++ b/src/shared/deepseek-harness-compatibility.test.ts @@ -9,9 +9,14 @@ describe('DeepSeek Harness model compatibility', () => { 'https://api.deepseek.com', 'https://gateway.example/openai/v1', 'https://gateway.example:8443/v1', + 'http://gateway.example/v1', + 'http://192.168.1.50:11434/v1', 'http://127.0.0.1:11434/v1', 'http://localhost:1234/v1', - 'http://[::1]:8080/v1' + 'http://[::1]:8080/v1', + 'https://gateway.example/v1?api-version=2025-01-01', + 'https://gateway.example/v1#chat', + 'http://user:password@gateway.example/v1' ])('accepts OpenAI-compatible base URL %s', (baseUrl) => { expect(isDeepSeekHarnessCompatibleBaseUrl(baseUrl)).toBe(true) expect( @@ -24,12 +29,10 @@ describe('DeepSeek Harness model compatibility', () => { }) it.each([ - 'http://gateway.example/v1', 'file:///private/config', - 'https://user:secret@gateway.example/v1', - 'https://gateway.example/v1?api-version=2025-01-01', - 'https://gateway.example/v1#chat' - ])('rejects unsafe or unsupported base URL %s', (baseUrl) => { + 'ftp://gateway.example/v1', + 'not-a-url' + ])('rejects unsupported base URL %s', (baseUrl) => { expect(isDeepSeekHarnessCompatibleBaseUrl(baseUrl)).toBe(false) }) diff --git a/src/shared/deepseek-harness-compatibility.ts b/src/shared/deepseek-harness-compatibility.ts index 05a18e5b..9d0fc95b 100644 --- a/src/shared/deepseek-harness-compatibility.ts +++ b/src/shared/deepseek-harness-compatibility.ts @@ -4,35 +4,14 @@ type DeepSeekHarnessModelProfile = { authentication: string } -function isLoopbackHostname(hostname: string): boolean { - const normalized = hostname - .toLowerCase() - .replace(/^\[|\]$/gu, '') - return ( - normalized === 'localhost' || - normalized === '127.0.0.1' || - normalized === '::1' - ) -} - export function isDeepSeekHarnessCompatibleBaseUrl( value: string ): boolean { try { const url = new URL(value) - if ( - !url.hostname || - url.username || - url.password || - url.search || - url.hash - ) { - return false - } return ( - url.protocol === 'https:' || - (url.protocol === 'http:' && - isLoopbackHostname(url.hostname)) + Boolean(url.hostname) && + (url.protocol === 'http:' || url.protocol === 'https:') ) } catch { return false