diff --git a/CHANGELOG.md b/CHANGELOG.md index 0acca1436caa..97d835bcdc91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,7 +143,6 @@ Docs: https://docs.openclaw.ai - Codex app-server: mirror native Codex subagent spawn lifecycle events into Task Registry so app-server child agents appear in task/status surfaces without relying on transcript text. (#79512) Thanks @mbelinky. - Gateway: expose optional `isHeartbeat` metadata on agent event payloads so clients can distinguish scheduled heartbeat runs from ordinary chat runs. (#80610) Thanks @medns. - Agents: add `agents.defaults.runRetries` and `agents.list[].runRetries` config for embedded Pi runner retry loop limits. (#80661) Thanks @medns. -- Plugins/ClawHub: check exact-release trust before ClawHub plugin install/update downloads, require confirmation for risky releases, and add `--acknowledge-clawhub-risk` for reviewed automation. (#81307) Thanks @jesse-merhi. ### Fixes diff --git a/docs/cli/plugins.md b/docs/cli/plugins.md index 063fb5363c2d..9106d15c7f8e 100644 --- a/docs/cli/plugins.md +++ b/docs/cli/plugins.md @@ -83,7 +83,6 @@ openclaw plugins install git:github.com// # git repo openclaw plugins install git:github.com//@ openclaw plugins install --force # overwrite existing install openclaw plugins install --pin # pin version -openclaw plugins install --acknowledge-clawhub-risk openclaw plugins install --dangerously-force-unsafe-install openclaw plugins install # local path openclaw plugins install @ # marketplace @@ -136,12 +135,6 @@ is available, then fall back to `latest`. If a plugin you published on ClawHub is blocked by a registry scan, use the publisher steps in [ClawHub](/clawhub/security). - - - ClawHub installs check the selected release trust record before downloading the package. If ClawHub reports a risky scan status, risky moderation state, download block, or registry reason, OpenClaw shows the trust details and asks for confirmation before continuing. - - Use `--acknowledge-clawhub-risk` only after reviewing the ClawHub warning and deciding to continue without an interactive prompt. Pending or stale clean trust records warn but do not require acknowledgement. - `plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation. @@ -331,7 +324,6 @@ openclaw plugins update openclaw plugins update --all openclaw plugins update --dry-run openclaw plugins update @openclaw/voice-call -openclaw plugins update openclaw-codex-app-server --acknowledge-clawhub-risk openclaw plugins update openclaw-codex-app-server --dangerously-force-unsafe-install ``` @@ -359,9 +351,6 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked `--dangerously-force-unsafe-install` is also available on `plugins update` as a break-glass override for built-in dangerous-code scan false positives during plugin updates. It still does not bypass plugin `before_install` policy blocks or scan-failure blocking, and it only applies to plugin updates, not hook-pack updates. - - ClawHub-backed plugin updates run the same exact-release trust check as installs before downloading the replacement package. Use `--acknowledge-clawhub-risk` for reviewed automation that should continue when the selected ClawHub release has a risky trust warning. - ### Inspect diff --git a/docs/cli/update.md b/docs/cli/update.md index 5bf35807a582..34f396040198 100644 --- a/docs/cli/update.md +++ b/docs/cli/update.md @@ -27,7 +27,6 @@ openclaw update --tag main openclaw update --dry-run openclaw update --no-restart openclaw update --yes -openclaw update --acknowledge-clawhub-risk openclaw update --json openclaw --update ``` @@ -45,10 +44,6 @@ openclaw --update when npm plugin artifact drift is detected during post-update plugin sync. - `--timeout `: per-step timeout (default is 1800s). - `--yes`: skip confirmation prompts (for example downgrade confirmation). -- `--acknowledge-clawhub-risk`: continue post-update ClawHub plugin sync when - the selected plugin release has a ClawHub trust warning. Without this flag, - interactive runs ask before downloading risky ClawHub plugin releases and - non-interactive runs fail closed. `openclaw update` does not have a `--verbose` flag. Use `--dry-run` to preview the planned channel/tag/install/restart actions, `--json` for machine-readable diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index 7fdb758006b4..b7bc374995ad 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -543,10 +543,8 @@ openclaw plugins install -l # link (no copy) for dev openclaw plugins install --marketplace openclaw plugins install --marketplace https://github.com// openclaw plugins install --pin # record exact resolved npm spec -openclaw plugins install --acknowledge-clawhub-risk openclaw plugins install --dangerously-force-unsafe-install openclaw plugins update # update one plugin -openclaw plugins update --acknowledge-clawhub-risk openclaw plugins update --dangerously-force-unsafe-install openclaw plugins update --all # update all openclaw plugins uninstall # remove config and plugin index records @@ -605,14 +603,6 @@ beta release exists. Exact versions and explicit tags stay pinned. `--pin` is npm-only. It is not supported with `--marketplace`, because marketplace installs persist marketplace source metadata instead of an npm spec. -ClawHub-backed plugin installs and updates check the exact target release's -ClawHub trust record before download. If ClawHub reports risk for that release, -OpenClaw prints the package, version, scan status, moderation state, and reason -codes, then requires confirmation before continuing. Non-interactive automation -must pass `--acknowledge-clawhub-risk` after reviewing that warning. The flag -acknowledges ClawHub registry risk only; it does not bypass plugin -`before_install` hook policy blocks or the built-in dangerous-code scanner. - `--dangerously-force-unsafe-install` is a break-glass override for false positives from the built-in dangerous-code scanner. It allows plugin installs and plugin updates to continue past built-in `critical` findings, but it still diff --git a/src/cli/clawhub-risk-acknowledgement.ts b/src/cli/clawhub-risk-acknowledgement.ts deleted file mode 100644 index 378cf95c56f5..000000000000 --- a/src/cli/clawhub-risk-acknowledgement.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { ClawHubRiskAcknowledgementRequest } from "../plugins/clawhub.js"; -import { promptYesNo } from "./prompt.js"; - -export type ClawHubRiskAcknowledgementCliOptions = { - acknowledgeClawHubRisk?: boolean; -}; - -function canPromptForClawHubRisk(): boolean { - return process.stdin.isTTY && process.stdout.isTTY; -} - -export function resolveClawHubRiskAcknowledgementCliOptions(params: { - acknowledgeClawHubRisk?: boolean; - action: "installing" | "updating"; -}): ClawHubRiskAcknowledgementCliOptions & { - onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => Promise; -} { - return { - acknowledgeClawHubRisk: params.acknowledgeClawHubRisk, - onClawHubRisk: - params.acknowledgeClawHubRisk || !canPromptForClawHubRisk() - ? undefined - : async (request) => - await promptYesNo( - `Continue ${params.action} ClawHub package "${request.packageName}@${request.version}" despite this warning?`, - ), - }; -} diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index de0589daaef6..1f3c166b7953 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -620,33 +620,6 @@ describe("plugins cli install", () => { expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); }); - it("passes ClawHub risk acknowledgement to explicit ClawHub installs", async () => { - loadConfig.mockReturnValue(createEmptyPluginConfig()); - parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); - installPluginFromClawHub.mockResolvedValue( - createClawHubInstallResult({ - pluginId: "demo", - packageName: "demo", - version: "1.2.3", - channel: "official", - }), - ); - enablePluginInConfig.mockReturnValue({ config: createEnabledPluginConfig("demo") }); - applyExclusiveSlotSelection.mockReturnValue({ - config: createEnabledPluginConfig("demo"), - warnings: [], - }); - - await runPluginsCommand(["plugins", "install", "clawhub:demo", "--acknowledge-clawhub-risk"]); - - expect(installPluginFromClawHub).toHaveBeenCalledWith( - expect.objectContaining({ - spec: "clawhub:demo", - acknowledgeClawHubRisk: true, - }), - ); - }); - it("passes the active profile extensions dir to ClawHub installs", async () => { const extensionsDir = useProfileExtensionsDir(); const cfg = createEmptyPluginConfig(); diff --git a/src/cli/plugins-cli.ts b/src/cli/plugins-cli.ts index b5d31b004c83..d635e856cafd 100644 --- a/src/cli/plugins-cli.ts +++ b/src/cli/plugins-cli.ts @@ -18,19 +18,10 @@ import { applyParentDefaultHelpAction } from "./program/parent-default-help.js"; export type PluginUpdateOptions = { all?: boolean; - acknowledgeClawhubRisk?: boolean; dryRun?: boolean; dangerouslyForceUnsafeInstall?: boolean; }; -type CommanderClawHubRiskOptions = Record & { - acknowledgeClawhubRisk?: boolean; -}; - -function normalizeCommanderClawHubRiskOption(opts: CommanderClawHubRiskOptions): boolean { - return opts.acknowledgeClawhubRisk === true || opts.acknowledgeClawHubRisk === true; -} - export type PluginMarketplaceListOptions = { json?: boolean; }; @@ -262,11 +253,6 @@ export function registerPluginsCli(program: Command) { "Bypass built-in dangerous-code install blocking (plugin hooks may still block)", false, ) - .option( - "--acknowledge-clawhub-risk", - "Acknowledge ClawHub release trust warnings without prompting", - false, - ) .option( "--marketplace ", "Install a Claude marketplace plugin from a local repo/path or git/GitHub source", @@ -274,7 +260,7 @@ export function registerPluginsCli(program: Command) { .action( async ( raw: string, - opts: CommanderClawHubRiskOptions & { + opts: { dangerouslyForceUnsafeInstall?: boolean; force?: boolean; link?: boolean; @@ -286,13 +272,7 @@ export function registerPluginsCli(program: Command) { "install command", async () => { const { runPluginInstallCommand } = await import("./plugins-install-command.js"); - await runPluginInstallCommand({ - raw, - opts: { - ...opts, - acknowledgeClawHubRisk: normalizeCommanderClawHubRiskOption(opts), - }, - }); + await runPluginInstallCommand({ raw, opts }); }, { command: "install" }, ); @@ -310,20 +290,9 @@ export function registerPluginsCli(program: Command) { "Bypass built-in dangerous-code update blocking for plugins (plugin hooks may still block)", false, ) - .option( - "--acknowledge-clawhub-risk", - "Acknowledge ClawHub release trust warnings without prompting", - false, - ) .action(async (id: string | undefined, opts: PluginUpdateOptions) => { const { runPluginUpdateCommand } = await import("./plugins-update-command.js"); - await runPluginUpdateCommand({ - id, - opts: { - ...opts, - acknowledgeClawHubRisk: normalizeCommanderClawHubRiskOption(opts), - }, - }); + await runPluginUpdateCommand({ id, opts }); }); plugins diff --git a/src/cli/plugins-cli.update.test.ts b/src/cli/plugins-cli.update.test.ts index 8ab315c15d32..f358df221015 100644 --- a/src/cli/plugins-cli.update.test.ts +++ b/src/cli/plugins-cli.update.test.ts @@ -210,35 +210,6 @@ describe("plugins cli update", () => { expect(updateParams.dangerouslyForceUnsafeInstall).toBe(true); }); - it("passes ClawHub risk acknowledgement to plugin updates", async () => { - const config = createTrackedPluginConfig({ - pluginId: "openclaw-codex-app-server", - spec: "openclaw-codex-app-server@beta", - }); - loadConfig.mockReturnValue(config); - setInstalledPluginIndexInstallRecords(config.plugins?.installs ?? {}); - updateNpmInstalledPlugins.mockResolvedValue({ - config, - changed: false, - outcomes: [], - }); - - await runPluginsCommand([ - "plugins", - "update", - "openclaw-codex-app-server", - "--acknowledge-clawhub-risk", - ]); - - expect(updateNpmInstalledPlugins).toHaveBeenCalledWith( - expect.objectContaining({ - config, - pluginIds: ["openclaw-codex-app-server"], - acknowledgeClawHubRisk: true, - }), - ); - }); - it("writes updated config when updater reports changes", async () => { const cfg = { plugins: { diff --git a/src/cli/plugins-install-command.ts b/src/cli/plugins-install-command.ts index 9fad6d3001b0..faa4f6afd1ee 100644 --- a/src/cli/plugins-install-command.ts +++ b/src/cli/plugins-install-command.ts @@ -33,7 +33,6 @@ import { validateJsonSchemaValue } from "../plugins/schema-validator.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { theme } from "../terminal/theme.js"; import { shortenHomePath } from "../utils.js"; -import { resolveClawHubRiskAcknowledgementCliOptions } from "./clawhub-risk-acknowledgement.js"; import { formatCliCommand } from "./command-format.js"; import { looksLikeLocalInstallSpec } from "./install-spec.js"; import { resolvePinnedNpmInstallRecordForCli } from "./npm-resolution.js"; @@ -559,7 +558,6 @@ export async function loadConfigForInstall( export async function runPluginInstallCommand(params: { raw: string; opts: InstallSafetyOverrides & { - acknowledgeClawHubRisk?: boolean; force?: boolean; link?: boolean; pin?: boolean; @@ -943,10 +941,6 @@ export async function runPluginInstallCommand(params: { if (clawhubSpec) { const result = await installPluginFromClawHub({ ...safetyOverrides, - ...resolveClawHubRiskAcknowledgementCliOptions({ - acknowledgeClawHubRisk: opts.acknowledgeClawHubRisk, - action: "installing", - }), mode: installMode, spec: raw, extensionsDir, diff --git a/src/cli/plugins-update-command.ts b/src/cli/plugins-update-command.ts index 0b257d4183bf..4e3baae4ed10 100644 --- a/src/cli/plugins-update-command.ts +++ b/src/cli/plugins-update-command.ts @@ -13,7 +13,6 @@ import { import { updateNpmInstalledPlugins } from "../plugins/update.js"; import { defaultRuntime } from "../runtime.js"; import { theme } from "../terminal/theme.js"; -import { resolveClawHubRiskAcknowledgementCliOptions } from "./clawhub-risk-acknowledgement.js"; import { commitPluginInstallRecordsWithConfig } from "./plugins-install-record-commit.js"; import { refreshPluginRegistryAfterConfigMutation } from "./plugins-registry-refresh.js"; import { logPluginUpdateOutcomes } from "./plugins-update-outcomes.js"; @@ -25,12 +24,7 @@ import { promptYesNo } from "./prompt.js"; export async function runPluginUpdateCommand(params: { id?: string; - opts: { - all?: boolean; - acknowledgeClawHubRisk?: boolean; - dryRun?: boolean; - dangerouslyForceUnsafeInstall?: boolean; - }; + opts: { all?: boolean; dryRun?: boolean; dangerouslyForceUnsafeInstall?: boolean }; }) { assertConfigWriteAllowedInCurrentMode(); @@ -68,10 +62,6 @@ export async function runPluginUpdateCommand(params: { specOverrides: pluginSelection.specOverrides, dryRun: params.opts.dryRun, dangerouslyForceUnsafeInstall: params.opts.dangerouslyForceUnsafeInstall, - ...resolveClawHubRiskAcknowledgementCliOptions({ - acknowledgeClawHubRisk: params.opts.acknowledgeClawHubRisk, - action: "updating", - }), logger, onIntegrityDrift: async (drift) => { const specLabel = drift.resolvedSpec ?? drift.spec; diff --git a/src/cli/update-cli.test.ts b/src/cli/update-cli.test.ts index 42876e80432b..79735c60d7c2 100644 --- a/src/cli/update-cli.test.ts +++ b/src/cli/update-cli.test.ts @@ -405,14 +405,14 @@ describe("update-cli", () => { const syncPluginCall = (index = 0) => { const calls = syncPluginsForUpdateChannel.mock.calls as unknown as Array< - [Record & { channel?: string; config?: OpenClawConfig }] + [{ channel?: string; config?: OpenClawConfig }] >; return calls[index]?.[0]; }; const npmPluginUpdateCall = (index = 0) => { const calls = updateNpmInstalledPlugins.mock.calls as unknown as Array< - [Record & { config?: OpenClawConfig; timeoutMs?: number }] + [{ config?: OpenClawConfig; timeoutMs?: number }] >; return calls[index]?.[0]; }; @@ -930,33 +930,6 @@ describe("update-cli", () => { expect(updateNpmInstalledPlugins).not.toHaveBeenCalled(); }); - it("carries ClawHub risk acknowledgement into post-core resume", async () => { - const { entrypoints } = setupUpdatedRootRefresh({ - gatewayUpdateImpl: async (root) => - makeOkUpdateResult({ - mode: "git", - root, - before: { sha: "old-sha", version: "2026.4.26" }, - after: { sha: "new-sha", version: "2026.4.27" }, - }), - }); - - await updateCommand({ - channel: "dev", - yes: true, - restart: false, - acknowledgeClawHubRisk: true, - }); - - expect(spawnCall()?.[1]).toEqual([ - entrypoints[0], - "update", - "--no-restart", - "--yes", - "--acknowledge-clawhub-risk", - ]); - }); - it("keeps downgrade post-update work in the current process", async () => { const downgradedRoot = createCaseDir("openclaw-downgraded-root"); setupUpdatedRootRefresh({ @@ -2919,22 +2892,6 @@ describe("update-cli", () => { expect(updateCall?.syncOfficialPluginInstalls).toBe(true); }); - it("forwards ClawHub risk acknowledgement to post-update plugin work", async () => { - const tempDir = createCaseDir("openclaw-update"); - mockPackageInstallStatus(tempDir); - - await updateCommand({ - channel: "beta", - yes: true, - restart: false, - acknowledgeClawHubRisk: true, - }); - - expect(syncPluginCall()?.acknowledgeClawHubRisk).toBe(true); - expect(npmPluginUpdateCall()?.acknowledgeClawHubRisk).toBe(true); - expect(lastNpmPluginUpdateCall()?.acknowledgeClawHubRisk).toBe(true); - }); - it("persists channel and runs post-update work after switching from package to git", async () => { const tempDir = createCaseDir("openclaw-update"); const gitRoot = path.join(tempDir, "..", "openclaw"); diff --git a/src/cli/update-cli.ts b/src/cli/update-cli.ts index 778d774da8d3..554e7963c111 100644 --- a/src/cli/update-cli.ts +++ b/src/cli/update-cli.ts @@ -31,21 +31,6 @@ function inheritedUpdateTimeout( return inheritOptionFromParent(command, "timeout"); } -type CommanderUpdateOptions = Record & { - acknowledgeClawhubRisk?: boolean; - channel?: string; - dryRun?: boolean; - json?: boolean; - restart?: boolean; - tag?: string; - timeout?: string; - yes?: boolean; -}; - -function normalizeCommanderClawHubRiskOption(opts: CommanderUpdateOptions): boolean { - return opts.acknowledgeClawhubRisk === true || opts.acknowledgeClawHubRisk === true; -} - export function registerUpdateCli(program: Command) { program.enablePositionalOptions(); const update = program @@ -61,11 +46,6 @@ export function registerUpdateCli(program: Command) { ) .option("--timeout ", "Timeout for each update step in seconds (default: 1800)") .option("--yes", "Skip confirmation prompts (non-interactive)", false) - .option( - "--acknowledge-clawhub-risk", - "Acknowledge ClawHub release trust warnings during post-update plugin sync", - false, - ) .addHelpText("after", () => { const examples = [ ["openclaw update", "Update a source checkout (git)"], @@ -77,7 +57,6 @@ export function registerUpdateCli(program: Command) { ["openclaw update --no-restart", "Update without restarting the service"], ["openclaw update --json", "Output result as JSON"], ["openclaw update --yes", "Non-interactive (accept downgrade prompts)"], - ["openclaw update --acknowledge-clawhub-risk", "Acknowledge ClawHub plugin trust warnings"], ["openclaw update wizard", "Interactive update wizard"], ["openclaw --update", "Shorthand for openclaw update"], ] as const; @@ -96,7 +75,6 @@ ${theme.heading("Switch channels:")} ${theme.heading("Non-interactive:")} - Use --yes to accept downgrade prompts - - Use --acknowledge-clawhub-risk only after reviewing ClawHub plugin trust warnings - Combine with --channel/--tag/--no-restart/--json/--timeout as needed - Use --dry-run to preview actions without writing config/installing/restarting @@ -111,17 +89,16 @@ ${theme.heading("Notes:")} ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.openclaw.ai/cli/update")}`; }) - .action(async (opts: CommanderUpdateOptions) => { + .action(async (opts) => { try { await updateCommand({ json: Boolean(opts.json), restart: Boolean(opts.restart), dryRun: Boolean(opts.dryRun), - channel: opts.channel, - tag: opts.tag, - timeout: opts.timeout, + channel: opts.channel as string | undefined, + tag: opts.tag as string | undefined, + timeout: opts.timeout as string | undefined, yes: Boolean(opts.yes), - acknowledgeClawHubRisk: normalizeCommanderClawHubRiskOption(opts), }); } catch (err) { defaultRuntime.error(String(err)); diff --git a/src/cli/update-cli/shared.ts b/src/cli/update-cli/shared.ts index 0e6f8e73ada6..784d114ae36d 100644 --- a/src/cli/update-cli/shared.ts +++ b/src/cli/update-cli/shared.ts @@ -32,7 +32,6 @@ export type UpdateCommandOptions = { tag?: string; timeout?: string; yes?: boolean; - acknowledgeClawHubRisk?: boolean; }; export type UpdateStatusOptions = { diff --git a/src/cli/update-cli/update-command.ts b/src/cli/update-cli/update-command.ts index 13a334b9757d..73cec7d50cb3 100644 --- a/src/cli/update-cli/update-command.ts +++ b/src/cli/update-cli/update-command.ts @@ -55,7 +55,6 @@ import { resolvePnpmGlobalDirFromGlobalRoot, } from "../../infra/update-global.js"; import { runGatewayUpdate, type UpdateRunResult } from "../../infra/update-runner.js"; -import type { ClawHubRiskAcknowledgementRequest } from "../../plugins/clawhub.js"; import { normalizePluginsConfig, resolveEffectiveEnableState } from "../../plugins/config-state.js"; import { loadInstalledPluginIndexInstallRecords, @@ -174,29 +173,6 @@ type MissingPluginInstallPayload = { type PostUpdatePluginWarning = NonNullable[number]; -function resolveUpdateClawHubRiskAcknowledgementOptions(opts: UpdateCommandOptions): { - acknowledgeClawHubRisk?: boolean; - onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => Promise; -} { - if (opts.acknowledgeClawHubRisk) { - return { acknowledgeClawHubRisk: true }; - } - if (opts.json || !process.stdin.isTTY) { - return {}; - } - return { - onClawHubRisk: async (request) => { - const ok = await confirm({ - message: stylePromptMessage( - `Continue updating ClawHub package "${request.packageName}@${request.version}" despite this warning?`, - ), - initialValue: false, - }); - return !isCancel(ok) && ok; - }, - }; -} - function pickUpdateQuip(): string { return UPDATE_QUIPS[Math.floor(Math.random() * UPDATE_QUIPS.length)] ?? "Update complete."; } @@ -1209,9 +1185,6 @@ export async function updatePluginsAfterCoreUpdate(params: { } const warnings: PostUpdatePluginWarning[] = []; - const clawHubRiskAcknowledgementOptions = resolveUpdateClawHubRiskAcknowledgementOptions( - params.opts, - ); const pluginInstallRecords = params.pluginInstallRecords ?? (await loadInstalledPluginIndexInstallRecords()); const syncConfig = withPluginInstallRecords( @@ -1225,7 +1198,6 @@ export async function updatePluginsAfterCoreUpdate(params: { externalizedBundledPluginBridges: await listPersistedBundledPluginLocationBridges({ workspaceDir: params.root, }), - ...clawHubRiskAcknowledgementOptions, logger: pluginLogger, }); for (const error of syncResult.summary.errors) { @@ -1299,7 +1271,6 @@ export async function updatePluginsAfterCoreUpdate(params: { disableOnFailure: true, logger: pluginLogger, onIntegrityDrift: onPluginIntegrityDrift, - ...clawHubRiskAcknowledgementOptions, }); pluginConfig = repairResult.config; pluginsChanged ||= repairResult.changed; @@ -1320,7 +1291,6 @@ export async function updatePluginsAfterCoreUpdate(params: { disableOnFailure: true, logger: pluginLogger, onIntegrityDrift: onPluginIntegrityDrift, - ...clawHubRiskAcknowledgementOptions, }); pluginConfig = npmResult.config; pluginsChanged ||= npmResult.changed; @@ -1979,9 +1949,6 @@ async function continuePostCoreUpdateInFreshProcess(params: { if (params.opts.yes) { argv.push("--yes"); } - if (params.opts.acknowledgeClawHubRisk) { - argv.push("--acknowledge-clawhub-risk"); - } if (params.opts.timeout) { argv.push("--timeout", params.opts.timeout); } diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index d40fcb60a12f..32dbfc257f7c 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -506,7 +506,6 @@ async function installCandidate(params: { const { candidate } = params; const extensionsDir = resolveDefaultPluginExtensionsDir(); const changes: string[] = []; - const warnings: string[] = []; const clawhubSpecs = candidate.clawhubSpec ? resolveClawHubInstallSpecsForUpdateChannel({ spec: candidate.clawhubSpec, @@ -527,9 +526,6 @@ async function installCandidate(params: { extensionsDir, expectedPluginId: candidate.pluginId, mode: "install", - logger: { - warn: (message) => warnings.push(message), - }, }); if (clawhubResult.ok) { const pluginId = clawhubResult.pluginId; @@ -544,7 +540,7 @@ async function installCandidate(params: { }, }, changes: [`Installed missing configured plugin "${pluginId}" from ${clawhubInstallSpec}.`], - warnings, + warnings: [], }; } if (!npmInstallSpec || !shouldFallbackClawHubToNpm(clawhubResult)) { @@ -552,7 +548,6 @@ async function installCandidate(params: { records: params.records, changes: [], warnings: [ - ...warnings, `Failed to install missing configured plugin "${candidate.pluginId}" from ${clawhubInstallSpec}: ${clawhubResult.error}`, ], }; @@ -566,7 +561,6 @@ async function installCandidate(params: { records: params.records, changes: [], warnings: [ - ...warnings, `Failed to install missing configured plugin "${candidate.pluginId}": missing npm spec.`, ], }; @@ -586,7 +580,6 @@ async function installCandidate(params: { records: params.records, changes: [], warnings: [ - ...warnings, `Failed to install missing configured plugin "${candidate.pluginId}" from ${npmInstallSpec}: ${result.error}`, ], }; @@ -608,7 +601,7 @@ async function installCandidate(params: { ...changes, `Installed missing configured plugin "${pluginId}" from ${npmInstallSpec}.`, ], - warnings, + warnings: [], }; } diff --git a/src/commands/onboarding-plugin-install.test.ts b/src/commands/onboarding-plugin-install.test.ts index 971d3654defd..14f5795999e4 100644 --- a/src/commands/onboarding-plugin-install.test.ts +++ b/src/commands/onboarding-plugin-install.test.ts @@ -127,7 +127,6 @@ type NpmSpecInstallCall = { type ClawHubInstallCall = { expectedPluginId?: string; mode?: string; - onClawHubRisk?: unknown; spec?: string; timeoutMs?: number; }; @@ -369,7 +368,6 @@ describe("ensureOnboardingPluginInstalled", () => { expect(clawHubCall.expectedPluginId).toBe("demo-plugin"); expect(clawHubCall.mode).toBe("install"); expect(clawHubCall.timeoutMs).toBe(300_000); - expect(typeof clawHubCall.onClawHubRisk).toBe("function"); expect(update).toHaveBeenCalledWith("Downloading"); expect(stop).toHaveBeenCalledWith("Installed Demo Provider plugin"); const [, recordUpdate] = readFirstMockCall(recordPluginInstall, "recordPluginInstall") as [ diff --git a/src/commands/onboarding-plugin-install.ts b/src/commands/onboarding-plugin-install.ts index bbc5a09528c2..d63d5f82431a 100644 --- a/src/commands/onboarding-plugin-install.ts +++ b/src/commands/onboarding-plugin-install.ts @@ -889,14 +889,6 @@ async function installPluginFromClawHubSpecWithProgress(params: { logInstallWarningWithSpacing(params.runtime, message); }, }, - onClawHubRisk: async (request) => { - animated.stop(); - progress.stop("Review ClawHub warning"); - return await params.prompter.confirm({ - message: `Continue installing ClawHub package "${sanitizeTerminalText(request.packageName)}@${sanitizeTerminalText(request.version)}" despite this warning?`, - initialValue: false, - }); - }, }), ONBOARDING_PLUGIN_INSTALL_WATCHDOG_TIMEOUT_MS, ); diff --git a/src/infra/clawhub.test.ts b/src/infra/clawhub.test.ts index c4691afcb11c..9906968ce20a 100644 --- a/src/infra/clawhub.test.ts +++ b/src/infra/clawhub.test.ts @@ -330,75 +330,30 @@ describe("clawhub helpers", () => { requestedUrl = input instanceof Request ? input.url : String(input); return new Response( JSON.stringify({ - package: { - name: "@openclaw/diagnostics-otel", - displayName: "Diagnostics", - family: "code-plugin", - }, - release: { - id: "rel_demo", - version: "2026.3.22", - }, - trust: { - scanStatus: "clean", - moderationState: null, - blockedFromDownload: false, - reasons: [], - pending: false, - stale: true, - }, + releaseId: "rel_demo", + state: "approved", + reasonCode: "clean", + createdAt: 1774256733107, + scanState: "clean", + moderationState: "approved", }), { status: 200, headers: { "content-type": "application/json" } }, ); }, }), ).resolves.toEqual({ - package: { - name: "@openclaw/diagnostics-otel", - displayName: "Diagnostics", - family: "code-plugin", - }, - release: { - id: "rel_demo", - version: "2026.3.22", - }, - trust: { - scanStatus: "clean", - moderationState: null, - blockedFromDownload: false, - reasons: [], - pending: false, - stale: true, - }, + releaseId: "rel_demo", + state: "approved", + reasonCode: "clean", + createdAt: 1774256733107, + scanState: "clean", + moderationState: "approved", }); expect(new URL(requestedUrl).pathname).toBe( "/api/v1/packages/%40openclaw%2Fdiagnostics-otel/versions/2026.3.22/security", ); }); - it("rejects malformed package security reports", async () => { - await expect( - fetchClawHubPackageSecurity({ - name: "@openclaw/diagnostics-otel", - version: "2026.3.22", - fetchImpl: async () => - new Response( - JSON.stringify({ - trust: { - scanStatus: "clean", - moderationState: null, - blockedFromDownload: false, - reasons: "clean", - pending: false, - stale: false, - }, - }), - { status: 200, headers: { "content-type": "application/json" } }, - ), - }), - ).rejects.toThrow("expected reasons to be a string array"); - }); - it("downloads package archives to sanitized temp paths and cleans them up", async () => { const archive = await downloadClawHubPackageArchive({ name: "@hyf/zai-external-alpha", diff --git a/src/infra/clawhub.ts b/src/infra/clawhub.ts index 32527cc9e258..d8f4a4df59c0 100644 --- a/src/infra/clawhub.ts +++ b/src/infra/clawhub.ts @@ -62,14 +62,14 @@ export type ClawHubArtifactScanState = | "not-run" | (string & {}); export type ClawHubArtifactModerationState = "approved" | "quarantined" | "revoked" | (string & {}); -export type ClawHubPackageSecurityTrust = { - scanStatus?: ClawHubArtifactScanState | null; - moderationState?: ClawHubArtifactModerationState | null; - blockedFromDownload: boolean; - reasons: string[]; - pending: boolean; - stale: boolean; -}; +export type ClawHubPackageSecurityState = + | "pending" + | "approved" + | "limited" + | "quarantined" + | "rejected" + | "revoked" + | (string & {}); export type ClawHubResolvedArtifact = | { source: "clawhub"; @@ -116,16 +116,15 @@ export type ClawHubPackageArtifactResolverResponse = { artifact?: ClawHubResolvedArtifact | null; }; export type ClawHubPackageSecurityResponse = { - package?: { - name?: string | null; - displayName?: string | null; - family?: ClawHubPackageFamily | (string & {}) | null; - } | null; - release?: { - id?: string | null; - version?: string | null; - } | null; - trust: ClawHubPackageSecurityTrust; + packageId?: string | null; + releaseId?: string | null; + state: ClawHubPackageSecurityState; + reasonCode?: string | null; + moderatorNote?: string | null; + actorId?: string | null; + createdAt?: number | null; + scanState?: ClawHubArtifactScanState | null; + moderationState?: ClawHubArtifactModerationState | null; }; export type ClawHubPackageClawPackSummary = { available: boolean; @@ -637,126 +636,6 @@ async function fetchJson(params: ClawHubRequestParams): Promise { return (await response.json()) as T; } -function isJsonObject(value: unknown): value is Record { - return Boolean(value && typeof value === "object" && !Array.isArray(value)); -} - -function optionalStringField( - source: Record, - field: string, - context: string, -): string | null | undefined { - const value = source[field]; - if (value === undefined || value === null || typeof value === "string") { - return value; - } - throw new Error(`Malformed ClawHub ${context}: expected ${field} to be a string or null.`); -} - -function requiredBooleanField( - source: Record, - field: string, - context: string, -): boolean { - const value = source[field]; - if (typeof value === "boolean") { - return value; - } - throw new Error(`Malformed ClawHub ${context}: expected ${field} to be a boolean.`); -} - -function requiredStringArrayField( - source: Record, - field: string, - context: string, -): string[] { - const value = source[field]; - if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) { - return value; - } - throw new Error(`Malformed ClawHub ${context}: expected ${field} to be a string array.`); -} - -function parseOptionalSecurityPackage(value: unknown): ClawHubPackageSecurityResponse["package"] { - if (value === undefined || value === null) { - return value; - } - if (!isJsonObject(value)) { - throw new Error( - "Malformed ClawHub security response: expected package to be an object or null.", - ); - } - const result: NonNullable = {}; - const name = optionalStringField(value, "name", "security package"); - const displayName = optionalStringField(value, "displayName", "security package"); - const family = optionalStringField(value, "family", "security package"); - if (name !== undefined) { - result.name = name; - } - if (displayName !== undefined) { - result.displayName = displayName; - } - if (family !== undefined) { - result.family = family; - } - return result; -} - -function parseOptionalSecurityRelease(value: unknown): ClawHubPackageSecurityResponse["release"] { - if (value === undefined || value === null) { - return value; - } - if (!isJsonObject(value)) { - throw new Error( - "Malformed ClawHub security response: expected release to be an object or null.", - ); - } - const result: NonNullable = {}; - const id = optionalStringField(value, "id", "security release"); - const version = optionalStringField(value, "version", "security release"); - if (id !== undefined) { - result.id = id; - } - if (version !== undefined) { - result.version = version; - } - return result; -} - -function parseClawHubPackageSecurityResponse(value: unknown): ClawHubPackageSecurityResponse { - if (!isJsonObject(value)) { - throw new Error("Malformed ClawHub security response: expected an object."); - } - const trust = value.trust; - if (!isJsonObject(trust)) { - throw new Error("Malformed ClawHub security response: expected trust to be an object."); - } - const parsedTrust: ClawHubPackageSecurityTrust = { - blockedFromDownload: requiredBooleanField(trust, "blockedFromDownload", "security trust"), - reasons: requiredStringArrayField(trust, "reasons", "security trust"), - pending: requiredBooleanField(trust, "pending", "security trust"), - stale: requiredBooleanField(trust, "stale", "security trust"), - }; - const scanStatus = optionalStringField(trust, "scanStatus", "security trust"); - const moderationState = optionalStringField(trust, "moderationState", "security trust"); - if (scanStatus !== undefined) { - parsedTrust.scanStatus = scanStatus; - } - if (moderationState !== undefined) { - parsedTrust.moderationState = moderationState; - } - const result: ClawHubPackageSecurityResponse = { trust: parsedTrust }; - const parsedPackage = parseOptionalSecurityPackage(value.package); - const parsedRelease = parseOptionalSecurityRelease(value.release); - if (parsedPackage !== undefined) { - result.package = parsedPackage; - } - if (parsedRelease !== undefined) { - result.release = parsedRelease; - } - return result; -} - export function resolveClawHubBaseUrl(baseUrl?: string): string { return normalizeBaseUrl(baseUrl); } @@ -889,7 +768,7 @@ export async function fetchClawHubPackageSecurity(params: { timeoutMs?: number; fetchImpl?: FetchLike; }): Promise { - const response = await fetchJson({ + return await fetchJson({ baseUrl: params.baseUrl, path: `/api/v1/packages/${encodeURIComponent(params.name)}/versions/${encodeURIComponent( params.version, @@ -898,7 +777,6 @@ export async function fetchClawHubPackageSecurity(params: { timeoutMs: params.timeoutMs, fetchImpl: params.fetchImpl, }); - return parseClawHubPackageSecurityResponse(response); } export async function fetchClawHubPackageReadiness(params: { diff --git a/src/plugins/clawhub.test.ts b/src/plugins/clawhub.test.ts index 98e17896c1cb..c7f9edc360a5 100644 --- a/src/plugins/clawhub.test.ts +++ b/src/plugins/clawhub.test.ts @@ -9,7 +9,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const parseClawHubPluginSpecMock = vi.fn(); const fetchClawHubPackageDetailMock = vi.fn(); const fetchClawHubPackageArtifactMock = vi.fn(); -const fetchClawHubPackageSecurityMock = vi.fn(); const fetchClawHubPackageVersionMock = vi.fn(); const downloadClawHubPackageArchiveMock = vi.fn(); const archiveCleanupMock = vi.fn(); @@ -24,7 +23,6 @@ vi.mock("../infra/clawhub.js", async () => { parseClawHubPluginSpec: (...args: unknown[]) => parseClawHubPluginSpecMock(...args), fetchClawHubPackageDetail: (...args: unknown[]) => fetchClawHubPackageDetailMock(...args), fetchClawHubPackageArtifact: (...args: unknown[]) => fetchClawHubPackageArtifactMock(...args), - fetchClawHubPackageSecurity: (...args: unknown[]) => fetchClawHubPackageSecurityMock(...args), fetchClawHubPackageVersion: (...args: unknown[]) => fetchClawHubPackageVersionMock(...args), downloadClawHubPackageArchive: (...args: unknown[]) => downloadClawHubPackageArchiveMock(...args), @@ -144,8 +142,6 @@ function expectClawHubInstallFlow(params: { expect(packageVersionCall().version).toBe(params.version); expect(packageArtifactCall().name).toBe("demo"); expect(packageArtifactCall().version).toBe(params.version); - expect(packageSecurityCall().name).toBe("demo"); - expect(packageSecurityCall().version).toBe(params.version); expect(archiveInstallCall().archivePath).toBe(params.archivePath); } @@ -215,10 +211,6 @@ function packageArtifactCall(callIndex = 0): PackageLookupCall { return mockCallArg(fetchClawHubPackageArtifactMock, callIndex) as PackageLookupCall; } -function packageSecurityCall(callIndex = 0): PackageLookupCall { - return mockCallArg(fetchClawHubPackageSecurityMock, callIndex) as PackageLookupCall; -} - function archiveDownloadCall(callIndex = 0): PackageLookupCall { return mockCallArg(downloadClawHubPackageArchiveMock, callIndex) as PackageLookupCall; } @@ -258,7 +250,6 @@ describe("installPluginFromClawHub", () => { parseClawHubPluginSpecMock.mockReset(); fetchClawHubPackageDetailMock.mockReset(); fetchClawHubPackageArtifactMock.mockReset(); - fetchClawHubPackageSecurityMock.mockReset(); fetchClawHubPackageVersionMock.mockReset(); downloadClawHubPackageArchiveMock.mockReset(); archiveCleanupMock.mockReset(); @@ -298,24 +289,6 @@ describe("installPluginFromClawHub", () => { fetchClawHubPackageArtifactMock.mockImplementation((params) => fetchClawHubPackageVersionMock(params), ); - fetchClawHubPackageSecurityMock.mockResolvedValue({ - package: { - name: "demo", - displayName: "Demo", - family: "code-plugin", - }, - release: { - version: "2026.3.22", - }, - trust: { - scanStatus: "clean", - moderationState: null, - blockedFromDownload: false, - reasons: [], - pending: false, - stale: false, - }, - }); downloadClawHubPackageArchiveMock.mockResolvedValue({ archivePath: "/tmp/clawhub-demo/archive.zip", integrity: DEMO_ARCHIVE_INTEGRITY, @@ -358,111 +331,6 @@ describe("installPluginFromClawHub", () => { expect(archiveCleanupMock).toHaveBeenCalledTimes(1); }); - it("requires acknowledgement before downloading risky ClawHub releases", async () => { - fetchClawHubPackageSecurityMock.mockResolvedValueOnce({ - trust: { - scanStatus: "malicious", - moderationState: "quarantined", - blockedFromDownload: true, - reasons: ["manual_moderation"], - pending: false, - stale: false, - }, - }); - const logger = createLoggerSpies(); - - const result = await installPluginFromClawHub({ - spec: "clawhub:demo", - baseUrl: "https://clawhub.ai", - logger, - }); - - const failure = expectInstallFailure(result); - expect(failure.code).toBe(CLAWHUB_INSTALL_ERROR_CODE.CLAWHUB_RISK_ACKNOWLEDGEMENT_REQUIRED); - expect(failure.error).toContain("--acknowledge-clawhub-risk"); - expect(logger.warn).toHaveBeenCalledWith( - expect.stringContaining('ClawHub trust warning for "demo@2026.3.22"'), - ); - expect(downloadClawHubPackageArchiveMock).not.toHaveBeenCalled(); - expect(installPluginFromArchiveMock).not.toHaveBeenCalled(); - }); - - it("continues after a risky ClawHub release is acknowledged", async () => { - const onClawHubRisk = vi.fn(async () => true); - fetchClawHubPackageSecurityMock.mockResolvedValueOnce({ - trust: { - scanStatus: "suspicious", - moderationState: null, - blockedFromDownload: false, - reasons: ["payload_strings"], - pending: false, - stale: false, - }, - }); - - const result = await installPluginFromClawHub({ - spec: "clawhub:demo", - baseUrl: "https://clawhub.ai", - onClawHubRisk, - }); - - expectSuccessfulClawHubInstall(result); - expect(onClawHubRisk).toHaveBeenCalledWith( - expect.objectContaining({ - packageName: "demo", - version: "2026.3.22", - warning: expect.stringContaining("payload_strings"), - }), - ); - expect(downloadClawHubPackageArchiveMock).toHaveBeenCalled(); - }); - - it("warns for stale clean ClawHub trust without requiring acknowledgement", async () => { - const onClawHubRisk = vi.fn(async () => false); - const logger = createLoggerSpies(); - fetchClawHubPackageSecurityMock.mockResolvedValueOnce({ - trust: { - scanStatus: "clean", - moderationState: null, - blockedFromDownload: false, - reasons: [], - pending: false, - stale: true, - }, - }); - - const result = await installPluginFromClawHub({ - spec: "clawhub:demo", - baseUrl: "https://clawhub.ai", - logger, - onClawHubRisk, - }); - - expectSuccessfulClawHubInstall(result); - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("stale=true")); - expect(onClawHubRisk).not.toHaveBeenCalled(); - }); - - it("stops when the ClawHub security response is unavailable", async () => { - fetchClawHubPackageSecurityMock.mockRejectedValueOnce( - new ClawHubRequestError({ - path: "/api/v1/packages/demo/versions/2026.3.22/security", - status: 404, - body: "not found", - }), - ); - - const result = await installPluginFromClawHub({ - spec: "clawhub:demo", - baseUrl: "https://clawhub.ai", - }); - - const failure = expectInstallFailure(result); - expect(failure.code).toBe(CLAWHUB_INSTALL_ERROR_CODE.CLAWHUB_SECURITY_UNAVAILABLE); - expect(failure.error).toContain("ClawHub release trust check failed"); - expect(downloadClawHubPackageArchiveMock).not.toHaveBeenCalled(); - }); - it("marks official source-linked OpenClaw packages as trusted for install scanning", async () => { fetchClawHubPackageDetailMock.mockResolvedValueOnce({ package: { diff --git a/src/plugins/clawhub.ts b/src/plugins/clawhub.ts index b77af09a2394..705feb7e4b12 100644 --- a/src/plugins/clawhub.ts +++ b/src/plugins/clawhub.ts @@ -15,7 +15,6 @@ import { downloadClawHubPackageArchive, fetchClawHubPackageArtifact, fetchClawHubPackageDetail, - fetchClawHubPackageSecurity, fetchClawHubPackageVersion, normalizeClawHubSha256Integrity, normalizeClawHubSha256Hex, @@ -28,7 +27,6 @@ import { type ClawHubPackageCompatibility, type ClawHubPackageDetail, type ClawHubPackageClawPackSummary, - type ClawHubPackageSecurityTrust, type ClawHubResolvedArtifact, type ClawHubPackageVersion, } from "../infra/clawhub.js"; @@ -51,8 +49,6 @@ export const CLAWHUB_INSTALL_ERROR_CODE = { INCOMPATIBLE_GATEWAY: "incompatible_gateway", MISSING_ARCHIVE_INTEGRITY: "missing_archive_integrity", ARCHIVE_INTEGRITY_MISMATCH: "archive_integrity_mismatch", - CLAWHUB_SECURITY_UNAVAILABLE: "clawhub_security_unavailable", - CLAWHUB_RISK_ACKNOWLEDGEMENT_REQUIRED: "clawhub_risk_acknowledgement_required", } as const; export type ClawHubInstallErrorCode = @@ -63,13 +59,6 @@ type PluginInstallLogger = { warn?: (message: string) => void; }; -export type ClawHubRiskAcknowledgementRequest = { - packageName: string; - version: string; - trust: ClawHubPackageSecurityTrust; - warning: string; -}; - type ClawHubInstallFailure = { ok: false; error: string; @@ -360,121 +349,6 @@ function mapClawHubRequestError( return buildClawHubInstallFailure(formatErrorMessage(error)); } -const CLAWHUB_RISK_SCAN_STATUSES = new Set(["malicious", "suspicious"]); -const CLAWHUB_RISK_MODERATION_STATES = new Set(["blocked", "quarantined", "revoked"]); -const CLAWHUB_NON_RISK_REASONS = new Set([ - "pending", - "pending_scan", - "scan_pending", - "stale", - "stale_scan", -]); - -function normalizeClawHubTrustToken(value: string | null | undefined): string { - return normalizeOptionalString(value)?.toLowerCase() ?? ""; -} - -function resolveClawHubRiskReasons(trust: ClawHubPackageSecurityTrust): string[] { - const reasons: string[] = []; - if (trust.blockedFromDownload) { - reasons.push("blocked from download"); - } - const scanStatus = normalizeClawHubTrustToken(trust.scanStatus); - if (CLAWHUB_RISK_SCAN_STATUSES.has(scanStatus)) { - reasons.push(`scan status ${scanStatus}`); - } - const moderationState = normalizeClawHubTrustToken(trust.moderationState); - if (CLAWHUB_RISK_MODERATION_STATES.has(moderationState)) { - reasons.push(`moderation state ${moderationState}`); - } - for (const reason of trust.reasons) { - const normalized = normalizeClawHubTrustToken(reason); - if (normalized && !CLAWHUB_NON_RISK_REASONS.has(normalized)) { - reasons.push(reason); - } - } - return reasons; -} - -function formatClawHubTrustWarning(params: { - packageName: string; - version: string; - trust: ClawHubPackageSecurityTrust; - riskReasons: readonly string[]; -}): string { - const details = [ - `scan=${params.trust.scanStatus ?? "unknown"}`, - `moderation=${params.trust.moderationState ?? "none"}`, - `blockedFromDownload=${String(params.trust.blockedFromDownload)}`, - `pending=${String(params.trust.pending)}`, - `stale=${String(params.trust.stale)}`, - `reasons=${params.trust.reasons.length ? params.trust.reasons.join(", ") : "none"}`, - ]; - const riskSuffix = - params.riskReasons.length > 0 ? ` Risk signals: ${params.riskReasons.join(", ")}.` : ""; - return `ClawHub trust warning for "${params.packageName}@${params.version}": ${details.join("; ")}.${riskSuffix}`; -} - -async function ensureClawHubPackageTrustAcknowledged(params: { - packageName: string; - version: string; - baseUrl?: string; - token?: string; - timeoutMs?: number; - acknowledgeClawHubRisk?: boolean; - onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => boolean | Promise; - logger?: PluginInstallLogger; -}): Promise { - let trust: ClawHubPackageSecurityTrust; - try { - const security = await fetchClawHubPackageSecurity({ - name: params.packageName, - version: params.version, - baseUrl: params.baseUrl, - token: params.token, - timeoutMs: params.timeoutMs, - }); - trust = security.trust; - } catch (error) { - return buildClawHubInstallFailure( - `ClawHub release trust check failed for "${params.packageName}@${params.version}": ${formatErrorMessage(error)}`, - CLAWHUB_INSTALL_ERROR_CODE.CLAWHUB_SECURITY_UNAVAILABLE, - ); - } - - const riskReasons = resolveClawHubRiskReasons(trust); - if (riskReasons.length === 0 && !trust.pending && !trust.stale) { - return null; - } - - const warning = formatClawHubTrustWarning({ - packageName: params.packageName, - version: params.version, - trust, - riskReasons, - }); - params.logger?.warn?.(warning); - if (riskReasons.length === 0 || params.acknowledgeClawHubRisk) { - return null; - } - - const acknowledged = params.onClawHubRisk - ? await params.onClawHubRisk({ - packageName: params.packageName, - version: params.version, - trust, - warning, - }) - : false; - if (acknowledged) { - return null; - } - return buildClawHubInstallFailure( - `ClawHub release "${params.packageName}@${params.version}" has trust warnings. Review the package and rerun with --acknowledge-clawhub-risk to continue.`, - CLAWHUB_INSTALL_ERROR_CODE.CLAWHUB_RISK_ACKNOWLEDGEMENT_REQUIRED, - ); -} - function isMissingArtifactResolverRoute(error: unknown): boolean { return ( error instanceof ClawHubRequestError && @@ -1181,8 +1055,6 @@ export async function installPluginFromClawHub( timeoutMs?: number; dryRun?: boolean; expectedPluginId?: string; - acknowledgeClawHubRisk?: boolean; - onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => boolean | Promise; }, ): Promise< | ({ @@ -1253,19 +1125,6 @@ export async function installPluginFromClawHub( compatibility: versionState.compatibility, logger: params.logger, }); - const trustFailure = await ensureClawHubPackageTrustAcknowledged({ - packageName: canonicalPackageName, - version: versionState.version, - baseUrl: params.baseUrl, - token: params.token, - timeoutMs: params.timeoutMs, - acknowledgeClawHubRisk: params.acknowledgeClawHubRisk, - onClawHubRisk: params.onClawHubRisk, - logger: params.logger, - }); - if (trustFailure) { - return trustFailure; - } let archive; try { diff --git a/src/plugins/update.test.ts b/src/plugins/update.test.ts index 736a8b1ee738..b109a1ad9b11 100644 --- a/src/plugins/update.test.ts +++ b/src/plugins/update.test.ts @@ -2397,54 +2397,6 @@ describe("updateNpmInstalledPlugins", () => { }); }); - it("forwards ClawHub risk acknowledgement inputs to dry-run and live ClawHub updates", async () => { - const onClawHubRisk = vi.fn(async () => true); - const config = createClawHubInstallConfig({ - pluginId: "demo", - installPath: "/tmp/demo", - clawhubUrl: "https://clawhub.ai", - clawhubPackage: "demo", - clawhubFamily: "code-plugin", - clawhubChannel: "official", - }); - installPluginFromClawHubMock.mockResolvedValue({ - ok: true, - pluginId: "demo", - targetDir: "/tmp/demo", - version: "1.2.4", - clawhub: { - source: "clawhub", - clawhubUrl: "https://clawhub.ai", - clawhubPackage: "demo", - clawhubFamily: "code-plugin", - clawhubChannel: "official", - integrity: "sha256-next", - resolvedAt: "2026-03-22T00:00:00.000Z", - }, - }); - - for (const dryRun of [true, false]) { - installPluginFromClawHubMock.mockClear(); - - await updateNpmInstalledPlugins({ - config, - pluginIds: ["demo"], - acknowledgeClawHubRisk: true, - onClawHubRisk, - ...(dryRun ? { dryRun: true } : {}), - }); - - expect(installPluginFromClawHubMock).toHaveBeenCalledWith( - expect.objectContaining({ - spec: "clawhub:demo", - acknowledgeClawHubRisk: true, - onClawHubRisk, - ...(dryRun ? { dryRun: true } : {}), - }), - ); - } - }); - it("migrates legacy unscoped install keys when a scoped npm package updates", async () => { installPluginFromNpmSpecMock.mockResolvedValue({ ok: true, @@ -2995,12 +2947,9 @@ describe("syncPluginsForUpdateChannel", () => { clawhubPackage: "legacy-chat", }), ); - const onClawHubRisk = vi.fn(async () => true); const result = await syncPluginsForUpdateChannel({ channel: "stable", - acknowledgeClawHubRisk: true, - onClawHubRisk, externalizedBundledPluginBridges: [ { bundledPluginId: "legacy-chat", @@ -3034,8 +2983,6 @@ describe("syncPluginsForUpdateChannel", () => { expect(clawHubInstallCall()?.baseUrl).toBe("https://clawhub.ai"); expect(clawHubInstallCall()?.mode).toBe("update"); expect(clawHubInstallCall()?.expectedPluginId).toBe("legacy-chat"); - expect(clawHubInstallCall()?.acknowledgeClawHubRisk).toBe(true); - expect(clawHubInstallCall()?.onClawHubRisk).toBe(onClawHubRisk); expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled(); expect(result.changed).toBe(true); expect(result.summary.switchedToClawHub).toEqual(["legacy-chat"]); diff --git a/src/plugins/update.ts b/src/plugins/update.ts index 60d157a30a10..69cba96c4df7 100644 --- a/src/plugins/update.ts +++ b/src/plugins/update.ts @@ -20,11 +20,7 @@ import type { UpdateChannel } from "../infra/update-channels.js"; import { resolveUserPath } from "../utils.js"; import { resolveBundledPluginSources } from "./bundled-sources.js"; import { buildClawHubPluginInstallRecordFields } from "./clawhub-install-records.js"; -import { - CLAWHUB_INSTALL_ERROR_CODE, - installPluginFromClawHub, - type ClawHubRiskAcknowledgementRequest, -} from "./clawhub.js"; +import { CLAWHUB_INSTALL_ERROR_CODE, installPluginFromClawHub } from "./clawhub.js"; import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js"; import { getExternalizedBundledPluginLegacyPathSuffix, @@ -852,8 +848,6 @@ export async function updateNpmInstalledPlugins(params: { dangerouslyForceUnsafeInstall?: boolean; specOverrides?: Record; onIntegrityDrift?: (params: PluginUpdateIntegrityDriftParams) => boolean | Promise; - acknowledgeClawHubRisk?: boolean; - onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => boolean | Promise; }): Promise { const logger = params.logger ?? {}; const installs = params.config.plugins?.installs ?? {}; @@ -872,10 +866,6 @@ export async function updateNpmInstalledPlugins(params: { ranNpmInstaller = true; return await installPluginFromNpmSpec(installParams); }; - const clawHubRiskAcknowledgementOptions = { - ...(params.acknowledgeClawHubRisk ? { acknowledgeClawHubRisk: true } : {}), - ...(params.onClawHubRisk ? { onClawHubRisk: params.onClawHubRisk } : {}), - }; const recordFailure = (pluginId: string, message: string) => { if (params.disableOnFailure && !params.dryRun) { @@ -1155,7 +1145,6 @@ export async function updateNpmInstalledPlugins(params: { dryRun: true, dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall, expectedPluginId: pluginId, - ...clawHubRiskAcknowledgementOptions, logger, }) : record.source === "git" @@ -1243,7 +1232,6 @@ export async function updateNpmInstalledPlugins(params: { dryRun: true, dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall, expectedPluginId: pluginId, - ...clawHubRiskAcknowledgementOptions, logger, }); } @@ -1357,7 +1345,6 @@ export async function updateNpmInstalledPlugins(params: { timeoutMs: params.timeoutMs, dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall, expectedPluginId: pluginId, - ...clawHubRiskAcknowledgementOptions, logger, }) : record.source === "git" @@ -1441,7 +1428,6 @@ export async function updateNpmInstalledPlugins(params: { timeoutMs: params.timeoutMs, dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall, expectedPluginId: pluginId, - ...clawHubRiskAcknowledgementOptions, logger, }); } @@ -1583,8 +1569,6 @@ export async function syncPluginsForUpdateChannel(params: { env?: NodeJS.ProcessEnv; logger?: PluginUpdateLogger; externalizedBundledPluginBridges?: readonly ExternalizedBundledPluginBridge[]; - acknowledgeClawHubRisk?: boolean; - onClawHubRisk?: (request: ClawHubRiskAcknowledgementRequest) => boolean | Promise; }): Promise { const env = params.env ?? process.env; const logger = params.logger ?? {}; @@ -1604,10 +1588,6 @@ export async function syncPluginsForUpdateChannel(params: { const loadHelpers = buildLoadPathHelpers(next.plugins?.load?.paths ?? [], env); let installs = next.plugins?.installs ?? {}; let changed = false; - const clawHubRiskAcknowledgementOptions = { - ...(params.acknowledgeClawHubRisk ? { acknowledgeClawHubRisk: true } : {}), - ...(params.onClawHubRisk ? { onClawHubRisk: params.onClawHubRisk } : {}), - }; if (params.channel === "dev") { for (const [pluginId, record] of Object.entries(installs)) { @@ -1720,7 +1700,6 @@ export async function syncPluginsForUpdateChannel(params: { ...(bridge.clawhubUrl ? { baseUrl: bridge.clawhubUrl } : {}), mode: "update", expectedPluginId: targetPluginId, - ...clawHubRiskAcknowledgementOptions, logger, }); if (!result.ok && npmSpec && shouldFallbackClawHubBridgeToNpm(result)) {