From 2a22eb68aafd251b71778464373b8ce5cee096dc Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 23:30:45 -0700 Subject: [PATCH] fix(plugins): require provenance for official npm trust Require OpenClaw-owned install provenance before granting official npm plugin scanner trust. Direct npm package names now scan normally; catalog, onboarding, and doctor paths pass explicit provenance.\n\nValidation:\n- pnpm test:serial src/plugins/install.npm-spec.test.ts src/cli/plugins-cli.install.test.ts src/commands/onboarding-plugin-install.test.ts src/commands/doctor/shared/missing-configured-plugin-install.test.ts src/channels/plugins/contracts/channel-catalog.contract.test.ts src/commands/auth-choice.apply.plugin-provider.test.ts\n- pnpm test:serial src/plugins/install.test.ts src/plugins/provider-auth-choices.test.ts src/plugins/provider-install-catalog.test.ts src/commands/channel-setup/plugin-install.test.ts\n- pnpm exec oxfmt --check --threads=1 ...\n- node scripts/run-oxlint.mjs ...\n- Crabbox cbx_6157440c9bbe / run_cbd813956eed: pnpm check:changed passed\n\nThanks @fede-kamel and @vincentkoc. --- CHANGELOG.md | 1 + src/channels/plugins/catalog.ts | 15 +++++- .../test-helpers/channel-catalog-contract.ts | 2 + src/cli/plugins-cli.install.test.ts | 12 +++++ src/cli/plugins-install-command.ts | 5 ++ src/commands/channel-setup/plugin-install.ts | 3 ++ .../missing-configured-plugin-install.test.ts | 18 +++++++ .../missing-configured-plugin-install.ts | 11 +++++ .../onboarding-plugin-install.test.ts | 3 ++ src/commands/onboarding-plugin-install.ts | 5 ++ src/plugins/install.npm-spec.test.ts | 47 +++++++++++++++++-- src/plugins/install.ts | 36 +------------- src/plugins/provider-auth-choice.ts | 3 ++ 13 files changed, 122 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d07b1ad38..62d28a97b0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai - Channels/WhatsApp: attach native outbound mention metadata for group text and media captions by resolving `@+` and `@` tokens against WhatsApp participant data, including LID groups. Fixes #39879; carries forward #56863. Thanks @kengi1437, @joe2643, and @fridayck. - Plugins/uninstall: remove empty managed git install parent directories after deleting cloned plugin repos and cover npm/git uninstall residue in Docker plugin lifecycle tests. Thanks @vincentkoc. - Plugins/install: resolve bare official external plugin IDs such as `brave` through the official catalog when no bundled source is available, so packaged installs fetch the intended scoped npm package instead of an unrelated unscoped package. Fixes #76373. Thanks @bek91 and @vincentkoc. +- Plugins/install: require OpenClaw-owned install provenance before granting official npm plugin scanner trust, so direct npm package names no longer bypass launch-code scanning while catalog, onboarding, and doctor installs stay trusted. Thanks @fede-kamel and @vincentkoc. - Gateway/sessions: keep async `sessions.list` title and preview hydration bounded to transcript head/tail reads so Control UI polling cannot full-scan large session transcripts every refresh. Thanks @vincentkoc. - Gateway/sessions: keep agent runtime metadata on lightweight `sessions.list` rows so model-only session patches do not make Control UI lose runtime identity. Thanks @vincentkoc. - Gateway/sessions: keep bulk `sessions.list` rows lightweight by skipping per-row transcript usage fallback, display model inference, and plugin projection, avoiding event-loop stalls in large session stores. Thanks @Marvinthebored and @vincentkoc. diff --git a/src/channels/plugins/catalog.ts b/src/channels/plugins/catalog.ts index 3dfc228b44e..a9696acd21b 100644 --- a/src/channels/plugins/catalog.ts +++ b/src/channels/plugins/catalog.ts @@ -39,6 +39,7 @@ export type ChannelPluginCatalogEntry = { id: string; pluginId?: string; origin?: PluginOrigin; + trustedSourceLinkedOfficialInstall?: boolean; meta: ChannelMeta; install: ChannelPluginCatalogInstall; installSource?: PluginInstallSourceInfo; @@ -203,7 +204,7 @@ function loadOfficialCatalogEntries(options: CatalogOptions): ChannelPluginCatal ? loadCatalogEntriesFromPaths(officialPaths) : loadOfficialCatalogEntriesFromPaths(officialPaths); return [...builtInEntries, ...fileEntries] - .map((entry) => buildExternalCatalogEntry(entry)) + .map((entry) => buildExternalCatalogEntry(entry, { trustedSourceLinkedOfficialInstall: true })) .filter((entry): entry is ChannelPluginCatalogEntry => Boolean(entry)); } @@ -297,6 +298,7 @@ function buildCatalogEntryFromManifest(params: { packageName?: string; packageDir?: string; origin?: PluginOrigin; + trustedSourceLinkedOfficialInstall?: boolean; workspaceDir?: string; channel?: PluginPackageChannel; install?: PluginPackageInstall; @@ -326,6 +328,9 @@ function buildCatalogEntryFromManifest(params: { id, ...(pluginId ? { pluginId } : {}), ...(params.origin ? { origin: params.origin } : {}), + ...(params.trustedSourceLinkedOfficialInstall + ? { trustedSourceLinkedOfficialInstall: true } + : {}), meta, install, installSource: describePluginInstallSource(install, { @@ -334,10 +339,16 @@ function buildCatalogEntryFromManifest(params: { }; } -function buildExternalCatalogEntry(entry: ExternalCatalogEntry): ChannelPluginCatalogEntry | null { +function buildExternalCatalogEntry( + entry: ExternalCatalogEntry, + options?: { + trustedSourceLinkedOfficialInstall?: boolean; + }, +): ChannelPluginCatalogEntry | null { const manifest = entry[MANIFEST_KEY]; return buildCatalogEntryFromManifest({ packageName: entry.name, + trustedSourceLinkedOfficialInstall: options?.trustedSourceLinkedOfficialInstall, channel: manifest?.channel, install: manifest?.install, }); diff --git a/src/channels/plugins/contracts/test-helpers/channel-catalog-contract.ts b/src/channels/plugins/contracts/test-helpers/channel-catalog-contract.ts index 430b4d7c3d5..27d45e5b02f 100644 --- a/src/channels/plugins/contracts/test-helpers/channel-catalog-contract.ts +++ b/src/channels/plugins/contracts/test-helpers/channel-catalog-contract.ts @@ -141,6 +141,7 @@ export function describeOfficialFallbackChannelCatalogContract(params: { expect(entry?.install.npmSpec).toBe(params.npmSpec); expect(entry?.pluginId).toBeUndefined(); + expect(entry?.trustedSourceLinkedOfficialInstall).toBe(true); }); it("lets external catalogs override shipped fallback channel metadata", () => { @@ -217,6 +218,7 @@ export function describeOfficialFallbackChannelCatalogContract(params: { expect(entry?.install.npmSpec).toBe(params.externalNpmSpec); expect(entry?.meta.label).toBe(params.externalLabel); expect(entry?.pluginId).toBeUndefined(); + expect(entry?.trustedSourceLinkedOfficialInstall).toBeUndefined(); }); it("surfaces package-name drift in external channel catalog install metadata", () => { diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 5ce11dee4df..8ca839cfc7c 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -675,6 +675,7 @@ describe("plugins cli install", () => { expect.objectContaining({ spec: "@openclaw/brave-plugin", expectedPluginId: "brave", + trustedSourceLinkedOfficialInstall: true, }), ); expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({ @@ -708,6 +709,7 @@ describe("plugins cli install", () => { expectedPluginId: "wecom", expectedIntegrity: "sha512-bnzfdIEEu1/LFvcdyjaTkyxt27w6c7dqhkPezU62OWaqmcdFsUGR3T55USK/O9pIKsNcnL1Tnu1pqKYCWHFgWQ==", + trustedSourceLinkedOfficialInstall: true, }), ); }); @@ -728,6 +730,11 @@ describe("plugins cli install", () => { await expect(runPluginsCommand(["plugins", "install", "wecom"])).rejects.toThrow("__exit__:1"); + expect(installPluginFromNpmSpec).toHaveBeenCalledWith( + expect.objectContaining({ + trustedSourceLinkedOfficialInstall: true, + }), + ); expect(installHooksFromNpmSpec).toHaveBeenCalledWith( expect.objectContaining({ spec: "@wecom/wecom-openclaw-plugin@2026.4.23", @@ -845,6 +852,11 @@ describe("plugins cli install", () => { expectedPluginId: "brave", }), ); + expect(installPluginFromNpmSpec).toHaveBeenCalledWith( + expect.not.objectContaining({ + trustedSourceLinkedOfficialInstall: true, + }), + ); expect(installPluginFromClawHub).not.toHaveBeenCalled(); }); diff --git a/src/cli/plugins-install-command.ts b/src/cli/plugins-install-command.ts index 060d0c29648..89b12bab717 100644 --- a/src/cli/plugins-install-command.ts +++ b/src/cli/plugins-install-command.ts @@ -279,6 +279,7 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: { extensionsDir: string; expectedPluginId?: string; expectedIntegrity?: string; + trustedSourceLinkedOfficialInstall?: boolean; runtime?: RuntimeEnv; }): Promise<{ ok: true } | { ok: false }> { const result = await installPluginFromNpmSpec({ @@ -287,6 +288,9 @@ async function tryInstallPluginOrHookPackFromNpmSpec(params: { spec: params.spec, ...(params.expectedPluginId ? { expectedPluginId: params.expectedPluginId } : {}), ...(params.expectedIntegrity ? { expectedIntegrity: params.expectedIntegrity } : {}), + ...(params.trustedSourceLinkedOfficialInstall + ? { trustedSourceLinkedOfficialInstall: true } + : {}), extensionsDir: params.extensionsDir, logger: createPluginInstallLogger(params.runtime), }); @@ -787,6 +791,7 @@ export async function runPluginInstallCommand(params: { extensionsDir, expectedPluginId: officialExternalPlan.pluginId, expectedIntegrity: officialExternalPlan.expectedIntegrity, + trustedSourceLinkedOfficialInstall: true, runtime, }); if (!npmResult.ok) { diff --git a/src/commands/channel-setup/plugin-install.ts b/src/commands/channel-setup/plugin-install.ts index 6e1051a1d53..35ad6743229 100644 --- a/src/commands/channel-setup/plugin-install.ts +++ b/src/commands/channel-setup/plugin-install.ts @@ -33,6 +33,9 @@ function toOnboardingPluginInstallEntry( pluginId: entry.pluginId ?? entry.id, label: entry.meta.label, install: entry.install, + ...(entry.trustedSourceLinkedOfficialInstall + ? { trustedSourceLinkedOfficialInstall: true } + : {}), }; } diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts index db503396cee..8b317a9a8ca 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts @@ -125,6 +125,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { npmSpec: "@openclaw/plugin-matrix@1.2.3", expectedIntegrity: "sha512-test", }, + trustedSourceLinkedOfficialInstall: true, }, ]); @@ -146,6 +147,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { extensionsDir: "/tmp/openclaw-plugins", expectedPluginId: "matrix", expectedIntegrity: "sha512-test", + trustedSourceLinkedOfficialInstall: true, }), ); expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( @@ -224,6 +226,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { install: { npmSpec: "@openclaw/plugin-matrix@1.2.3", }, + trustedSourceLinkedOfficialInstall: true, }, ]); @@ -240,6 +243,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { spec: "@openclaw/plugin-matrix@1.2.3", extensionsDir: "/tmp/openclaw-plugins", expectedPluginId: "matrix", + trustedSourceLinkedOfficialInstall: true, }), ); expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( @@ -273,6 +277,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { clawhubSpec: "clawhub:@openclaw/plugin-matrix@stable", npmSpec: "@openclaw/plugin-matrix@1.2.3", }, + trustedSourceLinkedOfficialInstall: true, }, ]); @@ -289,6 +294,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { expect.objectContaining({ spec: "@openclaw/plugin-matrix@1.2.3", expectedPluginId: "matrix", + trustedSourceLinkedOfficialInstall: true, }), ); expect(result.changes).toEqual([ @@ -321,6 +327,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { npmSpec: "@openclaw/twitch", defaultChoice: "npm", }, + trustedSourceLinkedOfficialInstall: true, }, ]); @@ -338,6 +345,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { expect.objectContaining({ spec: "@openclaw/twitch", expectedPluginId: "twitch", + trustedSourceLinkedOfficialInstall: true, }), ); expect(result.changes).toEqual([ @@ -813,6 +821,11 @@ describe("repairMissingConfiguredPluginInstalls", () => { expectedPluginId: "wecom", }), ); + expect(mocks.installPluginFromNpmSpec).toHaveBeenCalledWith( + expect.not.objectContaining({ + trustedSourceLinkedOfficialInstall: true, + }), + ); expect(result.changes).toEqual([ 'Installed missing configured plugin "wecom" from @wecom/wecom-openclaw-plugin@2026.4.23.', ]); @@ -863,6 +876,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { expect.objectContaining({ spec: "@openclaw/codex", expectedPluginId: "codex", + trustedSourceLinkedOfficialInstall: true, }), ); expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( @@ -940,6 +954,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { expect.objectContaining({ spec: "@openclaw/codex", expectedPluginId: "codex", + trustedSourceLinkedOfficialInstall: true, }), ); expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( @@ -1073,6 +1088,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { install: { npmSpec: "@openclaw/discord", }, + trustedSourceLinkedOfficialInstall: true, }, ]); mocks.installPluginFromNpmSpec.mockResolvedValueOnce({ @@ -1132,6 +1148,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { expect.objectContaining({ spec: "@openclaw/discord", expectedPluginId: "discord", + trustedSourceLinkedOfficialInstall: true, }), ); expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith( @@ -1476,6 +1493,7 @@ describe("repairMissingConfiguredPluginInstalls", () => { expect.objectContaining({ spec: "@openclaw/brave-plugin", expectedPluginId: "brave", + trustedSourceLinkedOfficialInstall: true, }), ); expect(result.changes).toEqual([ diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index bd1dad1c382..0f1ac03d66f 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -36,6 +36,7 @@ type DownloadableInstallCandidate = { npmSpec?: string; clawhubSpec?: string; expectedIntegrity?: string; + trustedSourceLinkedOfficialInstall?: boolean; defaultChoice?: PluginPackageInstall["defaultChoice"]; }; @@ -44,12 +45,14 @@ const RUNTIME_PLUGIN_INSTALL_CANDIDATES: readonly DownloadableInstallCandidate[] pluginId: "acpx", label: "ACPX Runtime", npmSpec: "@openclaw/acpx", + trustedSourceLinkedOfficialInstall: true, }, // Runtime-only configs do not have a provider/channel integration catalog entry. { pluginId: "codex", label: "Codex", npmSpec: "@openclaw/codex", + trustedSourceLinkedOfficialInstall: true, }, ]; @@ -201,6 +204,9 @@ function collectDownloadableInstallCandidates(params: { ...(entry.install.expectedIntegrity ? { expectedIntegrity: entry.install.expectedIntegrity } : {}), + ...(entry.trustedSourceLinkedOfficialInstall + ? { trustedSourceLinkedOfficialInstall: true } + : {}), ...(entry.install.defaultChoice ? { defaultChoice: entry.install.defaultChoice } : {}), }); } @@ -229,6 +235,7 @@ function collectDownloadableInstallCandidates(params: { ...(entry.install.expectedIntegrity ? { expectedIntegrity: entry.install.expectedIntegrity } : {}), + ...(entry.origin === "bundled" ? { trustedSourceLinkedOfficialInstall: true } : {}), ...(entry.install.defaultChoice ? { defaultChoice: entry.install.defaultChoice } : {}), }); } @@ -256,6 +263,7 @@ function collectDownloadableInstallCandidates(params: { ...(npmSpec ? { npmSpec } : {}), ...(clawhubSpec ? { clawhubSpec } : {}), ...(install.expectedIntegrity ? { expectedIntegrity: install.expectedIntegrity } : {}), + trustedSourceLinkedOfficialInstall: true, ...(install.defaultChoice ? { defaultChoice: install.defaultChoice } : {}), }); } @@ -419,6 +427,9 @@ async function installCandidate(params: { extensionsDir, expectedPluginId: candidate.pluginId, expectedIntegrity: candidate.expectedIntegrity, + ...(candidate.trustedSourceLinkedOfficialInstall + ? { trustedSourceLinkedOfficialInstall: true } + : {}), mode: "install", }); if (!result.ok) { diff --git a/src/commands/onboarding-plugin-install.test.ts b/src/commands/onboarding-plugin-install.test.ts index b592416e151..5e456a2bb0d 100644 --- a/src/commands/onboarding-plugin-install.test.ts +++ b/src/commands/onboarding-plugin-install.test.ts @@ -200,6 +200,7 @@ describe("ensureOnboardingPluginInstalled", () => { npmSpec: "@wecom/wecom-openclaw-plugin@1.2.3", expectedIntegrity: "sha512-wecom", }, + trustedSourceLinkedOfficialInstall: true, }, prompter: { select: vi.fn(async () => "npm"), @@ -211,7 +212,9 @@ describe("ensureOnboardingPluginInstalled", () => { expect(installPluginFromNpmSpec).toHaveBeenCalledWith( expect.objectContaining({ spec: "@wecom/wecom-openclaw-plugin@1.2.3", + expectedPluginId: "demo-plugin", expectedIntegrity: "sha512-wecom", + trustedSourceLinkedOfficialInstall: true, timeoutMs: 300_000, }), ); diff --git a/src/commands/onboarding-plugin-install.ts b/src/commands/onboarding-plugin-install.ts index 24aa9aad073..16ae90f243b 100644 --- a/src/commands/onboarding-plugin-install.ts +++ b/src/commands/onboarding-plugin-install.ts @@ -30,6 +30,7 @@ export type OnboardingPluginInstallEntry = { pluginId: string; label: string; install: PluginPackageInstall; + trustedSourceLinkedOfficialInstall?: boolean; }; export type OnboardingPluginInstallStatus = "installed" | "skipped" | "failed" | "timed_out"; @@ -585,7 +586,11 @@ async function installPluginFromNpmSpecWithProgress(params: { installPluginFromNpmSpec({ spec: params.npmSpec, timeoutMs: ONBOARDING_PLUGIN_INSTALL_TIMEOUT_MS, + expectedPluginId: params.entry.pluginId, expectedIntegrity: params.entry.install.expectedIntegrity, + ...(params.entry.trustedSourceLinkedOfficialInstall + ? { trustedSourceLinkedOfficialInstall: true } + : {}), extensionsDir: resolveDefaultPluginExtensionsDir(), logger: { info: updateProgress, diff --git a/src/plugins/install.npm-spec.test.ts b/src/plugins/install.npm-spec.test.ts index 9286e738908..b312d6a40ea 100644 --- a/src/plugins/install.npm-spec.test.ts +++ b/src/plugins/install.npm-spec.test.ts @@ -342,7 +342,7 @@ describe("installPluginFromNpmSpec", () => { }); }); - it.each([ + const officialLaunchPluginCases = [ { spec: "@openclaw/acpx", pluginId: "acpx", @@ -363,8 +363,10 @@ describe("installPluginFromNpmSpec", () => { pluginId: "voice-call", indexJs: `import { spawn } from "node:child_process";\nspawn("ngrok", ["http", "3000"]);`, }, - ])( - "allows official npm plugin $spec with reviewed launch code", + ]; + + it.each(officialLaunchPluginCases)( + "blocks direct official npm plugin $spec with launch code without source provenance", async ({ spec, pluginId, indexJs }) => { const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm"); const warnings: string[] = []; @@ -386,6 +388,45 @@ describe("installPluginFromNpmSpec", () => { }, }); + expect(result.ok).toBe(false); + if (result.ok) { + return; + } + expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED); + expect(fs.existsSync(path.join(npmRoot, "node_modules", spec))).toBe(false); + expect( + warnings.some((warning) => + warning.includes("allowed because it is an official OpenClaw package"), + ), + ).toBe(false); + }, + ); + + it.each(officialLaunchPluginCases)( + "allows source-linked official npm plugin $spec with reviewed launch code", + async ({ spec, pluginId, indexJs }) => { + const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm"); + const warnings: string[] = []; + mockNpmViewAndInstall({ + spec, + packageName: spec, + version: "2026.5.2", + pluginId, + npmRoot, + indexJs, + }); + + const result = await installPluginFromNpmSpec({ + spec, + npmDir: npmRoot, + expectedPluginId: pluginId, + trustedSourceLinkedOfficialInstall: true, + logger: { + info: () => {}, + warn: (msg: string) => warnings.push(msg), + }, + }); + expect(result.ok).toBe(true); if (!result.ok) { return; diff --git a/src/plugins/install.ts b/src/plugins/install.ts index f8ca7ed12af..c50ac57a801 100644 --- a/src/plugins/install.ts +++ b/src/plugins/install.ts @@ -115,12 +115,6 @@ type PluginInstallPolicyRequest = { }; const defaultLogger: PluginInstallLogger = {}; -const TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES = new Map([ - ["@openclaw/acpx", "acpx"], - ["@openclaw/codex", "codex"], - ["@openclaw/google-meet", "google-meet"], - ["@openclaw/voice-call", "voice-call"], -]); function ensureOpenClawExtensions(params: { manifest: PackageManifest }): | { @@ -196,26 +190,6 @@ function hasPackageRuntimeDependencies(manifest: PackageManifest): boolean { ); } -function isTrustedOfficialNpmPluginInstall(params: { - installPolicyRequest?: PluginInstallPolicyRequest; - packageName: string; - pluginId: string; -}): boolean { - if (params.installPolicyRequest?.kind !== "plugin-npm") { - return false; - } - const requested = parseRegistryNpmSpec(params.installPolicyRequest.requestedSpecifier ?? ""); - if (!requested) { - return false; - } - const expectedPluginId = TRUSTED_OFFICIAL_NPM_PLUGIN_PACKAGES.get(requested.name); - return ( - expectedPluginId !== undefined && - params.packageName === requested.name && - params.pluginId === expectedPluginId - ); -} - function buildBlockedInstallResult(params: { blocked: NonNullable["blocked"]>; }): Extract { @@ -777,19 +751,12 @@ async function validatePackagePluginInstallSource(params: { const scanMode = params.resolveEffectiveMode ? await params.resolveEffectiveMode(pluginId) : params.mode; - const trustedOfficialInstall = - params.trustedSourceLinkedOfficialInstall || - isTrustedOfficialNpmPluginInstall({ - installPolicyRequest: params.installPolicyRequest, - packageName: pkgName, - pluginId, - }); const scanResult = await runInstallSourceScan({ subject: `Plugin "${pluginId}"`, scan: async () => await params.runtime.scanPackageInstallSource({ dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall, - trustedSourceLinkedOfficialInstall: trustedOfficialInstall, + trustedSourceLinkedOfficialInstall: params.trustedSourceLinkedOfficialInstall, packageDir: params.packageDir, pluginId, logger: params.logger, @@ -1279,6 +1246,7 @@ export async function installPluginFromNpmSpec( dependencyScanRootDir: npmRoot, logger, expectedPluginId, + trustedSourceLinkedOfficialInstall: params.trustedSourceLinkedOfficialInstall, mode: effectiveMode, installPolicyRequest: { kind: "plugin-npm", diff --git a/src/plugins/provider-auth-choice.ts b/src/plugins/provider-auth-choice.ts index 7543c3ff7cd..2b17d83c755 100644 --- a/src/plugins/provider-auth-choice.ts +++ b/src/plugins/provider-auth-choice.ts @@ -378,6 +378,9 @@ export async function applyAuthChoiceLoadedPluginProvider( pluginId: installCatalogEntry.pluginId, label: installCatalogEntry.label, install: installCatalogEntry.install, + ...(installCatalogEntry.origin === "bundled" + ? { trustedSourceLinkedOfficialInstall: true } + : {}), }, prompter: params.prompter, runtime: params.runtime,