CLI/plugins: stop forced-unsafe installs from falling back to hook packs (#58909)

Merged via squash.

Prepared head SHA: 7cf146efb6
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
This commit is contained in:
Mason Huang
2026-04-15 13:23:17 +08:00
committed by GitHub
parent 7fc5a18d89
commit 3d2f51c0a4
4 changed files with 320 additions and 2 deletions

View File

@@ -9,7 +9,11 @@ import { extractErrorCode, formatErrorMessage } from "../infra/errors.js";
import { type BundledPluginSource, findBundledPluginSource } from "../plugins/bundled-sources.js";
import { formatClawHubSpecifier, installPluginFromClawHub } from "../plugins/clawhub.js";
import type { InstallSafetyOverrides } from "../plugins/install-security-scan.js";
import { installPluginFromNpmSpec, installPluginFromPath } from "../plugins/install.js";
import {
PLUGIN_INSTALL_ERROR_CODE,
installPluginFromNpmSpec,
installPluginFromPath,
} from "../plugins/install.js";
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
import {
installPluginFromMarketplace,
@@ -191,6 +195,17 @@ async function tryInstallHookPackFromNpmSpec(params: {
return { ok: true };
}
function shouldExitOnForcedUnsafeInstall(params: {
forceUnsafeInstall: boolean;
code?: string;
}): boolean {
return (
params.forceUnsafeInstall &&
(params.code === PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED ||
params.code === PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_FAILED)
);
}
function isAllowedBundledRecoveryIssue(
issue: { path?: string; message?: string },
request: PluginInstallRequestContext,
@@ -345,6 +360,7 @@ export async function runPluginInstallCommand(params: {
}
const resolved = request.resolvedPath ?? request.normalizedSpec;
const forceUnsafeInstall = opts.dangerouslyForceUnsafeInstall === true;
if (fs.existsSync(resolved)) {
if (opts.link) {
@@ -352,10 +368,16 @@ export async function runPluginInstallCommand(params: {
const merged = Array.from(new Set([...existing, resolved]));
const probe = await installPluginFromPath({
...safetyOverrides,
mode: installMode,
path: resolved,
dryRun: true,
logger: createPluginInstallLogger(),
});
if (!probe.ok) {
if (shouldExitOnForcedUnsafeInstall({ forceUnsafeInstall, code: probe.code })) {
defaultRuntime.error(probe.error);
return defaultRuntime.exit(1);
}
const hookFallback = await tryInstallHookPackFromLocalPath({
config: cfg,
installMode,
@@ -402,6 +424,10 @@ export async function runPluginInstallCommand(params: {
logger: createPluginInstallLogger(),
});
if (!result.ok) {
if (shouldExitOnForcedUnsafeInstall({ forceUnsafeInstall, code: result.code })) {
defaultRuntime.error(result.error);
return defaultRuntime.exit(1);
}
const hookFallback = await tryInstallHookPackFromLocalPath({
config: cfg,
installMode,
@@ -547,6 +573,10 @@ export async function runPluginInstallCommand(params: {
logger: createPluginInstallLogger(),
});
if (!result.ok) {
if (shouldExitOnForcedUnsafeInstall({ forceUnsafeInstall, code: result.code })) {
defaultRuntime.error(result.error);
return defaultRuntime.exit(1);
}
const bundledFallbackPlan = resolveBundledInstallPlanForNpmFailure({
rawSpec: raw,
code: result.code,