From 544420381ce7dd5629c75f0d98e5f1a43823bad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E5=85=AC?= Date: Wed, 26 Aug 2026 20:37:01 +0800 Subject: [PATCH 1/4] feat(serve): stop enforcing public-only extension policy --- .../workspace-extensions-controller.test.ts | 15 +++++++++++++++ .../routes/workspace-extensions-controller.ts | 1 - .../cli/src/serve/routes/workspace-extensions.ts | 8 ++------ .../routes/workspace-qualified-extensions.test.ts | 3 +++ packages/cli/src/serve/server.test.ts | 1 + 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/serve/routes/workspace-extensions-controller.test.ts b/packages/cli/src/serve/routes/workspace-extensions-controller.test.ts index b8cc2437ef9..9e553d480f1 100644 --- a/packages/cli/src/serve/routes/workspace-extensions-controller.test.ts +++ b/packages/cli/src/serve/routes/workspace-extensions-controller.test.ts @@ -49,6 +49,21 @@ describe('createExtensionsController', () => { vi.restoreAllMocks(); }); + it('does not impose a public-only extension network policy', () => { + const controller = createExtensionsController({ + boundWorkspace: '/work/bound', + bridge: {} as AcpSessionBridge, + workspace: {} as DaemonWorkspaceService, + isWorkspaceTrusted: () => true, + }); + + const manager = controller.createExtensionManager() as unknown as { + networkPolicy?: string; + }; + + expect(manager.networkPolicy).toBeUndefined(); + }); + it('releases the commit lane when a manual refresh times out', async () => { vi.useFakeTimers(); let refreshCalls = 0; diff --git a/packages/cli/src/serve/routes/workspace-extensions-controller.ts b/packages/cli/src/serve/routes/workspace-extensions-controller.ts index 6fd48a145c2..e8345d0cae2 100644 --- a/packages/cli/src/serve/routes/workspace-extensions-controller.ts +++ b/packages/cli/src/serve/routes/workspace-extensions-controller.ts @@ -309,7 +309,6 @@ export function createExtensionsController( getWorkspaceTrustStatus(loadSettings(workspaceDir).merged, workspaceDir) .effective.state === 'trusted', requestConsent: () => Promise.resolve(), - networkPolicy: 'public', requestSetting: interactions?.requestSetting ?? (async (setting: ExtensionSetting) => { diff --git a/packages/cli/src/serve/routes/workspace-extensions.ts b/packages/cli/src/serve/routes/workspace-extensions.ts index 241f9500971..10c03ac0f19 100644 --- a/packages/cli/src/serve/routes/workspace-extensions.ts +++ b/packages/cli/src/serve/routes/workspace-extensions.ts @@ -1140,9 +1140,7 @@ export function registerWorkspaceExtensionRoutes( res, async (extensionManager, _signal, context, operationId) => { const prepared = await context!.prepare(async (signal) => { - const installMetadata = await parseInstallSource(sourceValue, { - networkPolicy: 'public', - }); + const installMetadata = await parseInstallSource(sourceValue); if ( installMetadata.type !== 'git' && @@ -1948,9 +1946,7 @@ export function registerWorkspaceExtensionRoutes( gitCredential?.persistence === 'one_time' ? {} : { source: sourceValue }, async (extensionManager, _signal, context) => { const prepared = await context!.prepare(async (signal) => { - const metadata = await parseInstallSource(sourceValue, { - networkPolicy: 'public', - }); + const metadata = await parseInstallSource(sourceValue); if ( metadata.type !== 'git' && metadata.type !== 'github-release' && diff --git a/packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts b/packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts index 88ecd3e6f37..4cb27471582 100644 --- a/packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts +++ b/packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts @@ -1757,6 +1757,9 @@ describe('extension management v2 REST', () => { }, }), ); + expect( + prepareInstall.mock.calls.at(-1)?.[0].installMetadata, + ).not.toHaveProperty('networkPolicy'); expect(JSON.stringify(operation)).not.toContain('fine-grained-token'); expect(JSON.stringify(h.primary.bridge)).not.toContain( 'fine-grained-token', diff --git a/packages/cli/src/serve/server.test.ts b/packages/cli/src/serve/server.test.ts index 46e601221d0..c4cf9498805 100644 --- a/packages/cli/src/serve/server.test.ts +++ b/packages/cli/src/serve/server.test.ts @@ -7851,6 +7851,7 @@ describe('createServeApp', () => { persistence: expected, }, }); + expect(captured?.installMetadata).not.toHaveProperty('networkPolicy'); await vi.waitFor(() => expect(bridge.extensionEvents.at(-1)).toMatchObject({ status: 'installed', From 8d607798429dbe891f70f0da825bfd171c37f03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E5=85=AC?= Date: Wed, 26 Aug 2026 20:54:31 +0800 Subject: [PATCH 2/4] docs(serve): align extension network policy --- docs/design/extension-management-v2.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/extension-management-v2.md b/docs/design/extension-management-v2.md index 1b23ed8d2d6..5fbd94fdb9b 100644 --- a/docs/design/extension-management-v2.md +++ b/docs/design/extension-management-v2.md @@ -103,9 +103,9 @@ type InitialActivation = | { scope: 'workspace'; workspaceId: string }; ``` -The daemon install endpoint accepts HTTPS Git, GitHub Release, and npm sources -under the public network policy. SSH and local/link sources remain local CLI -features. Update preserves the extension id, +The daemon install endpoint accepts HTTPS Git, GitHub Release, and npm sources. +SSH and local/link sources remain local CLI features. Update preserves the +extension id, manifest name, settings, and activation policy. “Already current” is a successful `updated: false` result. Uninstall is idempotent and removes both the artifact and policy. From 32ff79469b709ab53f62cc0ad736bae82e937572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E5=85=AC?= Date: Thu, 27 Aug 2026 14:00:50 +0800 Subject: [PATCH 3/4] docs(serve): document daemon local extension sources --- docs/design/extension-management-v2.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/extension-management-v2.md b/docs/design/extension-management-v2.md index 5fbd94fdb9b..b68d1c5049d 100644 --- a/docs/design/extension-management-v2.md +++ b/docs/design/extension-management-v2.md @@ -103,9 +103,9 @@ type InitialActivation = | { scope: 'workspace'; workspaceId: string }; ``` -The daemon install endpoint accepts HTTPS Git, GitHub Release, and npm sources. -SSH and local/link sources remain local CLI features. Update preserves the -extension id, +The daemon install endpoint accepts HTTPS Git, GitHub Release, npm, and +absolute-path local sources. SSH and link sources remain local CLI features. +Update preserves the extension id, manifest name, settings, and activation policy. “Already current” is a successful `updated: false` result. Uninstall is idempotent and removes both the artifact and policy. From b692158e93188caa74aa75631bca42cf3bbff9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E5=85=AC?= Date: Thu, 27 Aug 2026 22:02:21 +0800 Subject: [PATCH 4/4] fix(serve): align extension source validation --- packages/cli/src/serve/routes/workspace-extensions.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/serve/routes/workspace-extensions.ts b/packages/cli/src/serve/routes/workspace-extensions.ts index d630852f931..b9e1095a967 100644 --- a/packages/cli/src/serve/routes/workspace-extensions.ts +++ b/packages/cli/src/serve/routes/workspace-extensions.ts @@ -341,9 +341,7 @@ const validateExtensionSourceMetadata = ( const parsed = parsePotentialSourceUrl(installMetadata.source); return ( !!parsed && - (installMetadata.networkPolicy === 'public' - ? parsed.protocol === 'https:' - : parsed.protocol === 'https:' || parsed.protocol === 'ssh:') && + parsed.protocol === 'https:' && !isBlockedAuthProviderHost(parsed.hostname) ); }; @@ -1108,9 +1106,7 @@ export function registerWorkspaceExtensionRoutes( isPortableAbsolutePath(sourceValue) || sourceValue.startsWith('.'); if (localSource) { try { - const metadata = await parseInstallSource(sourceValue, { - networkPolicy: 'public', - }); + const metadata = await parseInstallSource(sourceValue); assertDaemonExtensionInstallSource( metadata, sourceValue,