Files
openclaw/src/plugins/install-provenance.ts
T
Jesse Merhi 00364ee777 improve: warn before non-ClawHub plugin installs (#102197)
Merged via squash.

Prepared head SHA: e08d9e737d
Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Reviewed-by: @jesse-merhi
2026-07-15 03:25:36 +10:00

101 lines
3.2 KiB
TypeScript

// Shared policy and messaging for installs outside OpenClaw's trusted plugin sources.
import { sanitizeTerminalText } from "../../packages/terminal-core/src/safe-text.js";
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
import {
findBundledPluginSourceInMap,
getProcessBundledPluginSources,
type BundledPluginSource,
} from "./bundled-sources.js";
import {
resolveCatalogOfficialExternalInstallPlan,
resolveCatalogOfficialExternalNpmPackageTrust,
} from "./official-external-install-trust.js";
export const NON_CLAWHUB_INSTALL_FORCE_FLAG = "--force";
export type NonClawHubInstallSourceClass =
| "git"
| "local-archive"
| "local-path"
| "marketplace"
| "npm"
| "npm-pack";
export function resolveOpenClawTrustedNpmPackageInstall(
npmSpec: string,
bundledSources: ReadonlyMap<string, BundledPluginSource> = getProcessBundledPluginSources(),
): {
pluginId: string;
expectedIntegrity?: string;
} | null {
const packageName = parseRegistryNpmSpec(npmSpec)?.name;
if (!packageName) {
return null;
}
const bundled = findBundledPluginSourceInMap({
bundled: bundledSources,
lookup: { kind: "npmSpec", value: packageName },
});
if (bundled) {
return { pluginId: bundled.pluginId };
}
return resolveCatalogOfficialExternalNpmPackageTrust(npmSpec);
}
export function isOpenClawTrustedPluginInstallSpec(
spec: string,
bundledSources: ReadonlyMap<string, BundledPluginSource> = getProcessBundledPluginSources(),
): boolean {
const trimmed = spec.trim();
if (trimmed.toLowerCase().startsWith("clawhub:")) {
return true;
}
const explicitNpm = trimmed.toLowerCase().startsWith("npm:");
const npmSpec = explicitNpm ? trimmed.slice("npm:".length) : trimmed;
if (explicitNpm) {
return resolveOpenClawTrustedNpmPackageInstall(npmSpec, bundledSources) !== null;
}
const parsedPackageName = parseRegistryNpmSpec(npmSpec)?.name;
const bundled =
findBundledPluginSourceInMap({
bundled: bundledSources,
lookup: { kind: "pluginId", value: npmSpec },
}) ??
(parsedPackageName
? findBundledPluginSourceInMap({
bundled: bundledSources,
lookup: { kind: "npmSpec", value: parsedPackageName },
})
: undefined) ??
findBundledPluginSourceInMap({
bundled: bundledSources,
lookup: { kind: "localPath", value: npmSpec },
});
return Boolean(
bundled ??
resolveOpenClawTrustedNpmPackageInstall(npmSpec, bundledSources) ??
resolveCatalogOfficialExternalInstallPlan(npmSpec),
);
}
const sourceClassLabels: Record<NonClawHubInstallSourceClass, string> = {
git: "Git repository",
"local-archive": "local archive",
"local-path": "local path",
marketplace: "marketplace source",
npm: "npm registry",
"npm-pack": "local npm-pack archive",
};
export function formatNonClawHubInstallWarning(params: {
sourceClass: NonClawHubInstallSourceClass;
spec: string;
}): string {
const sourceLabel = sourceClassLabels[params.sourceClass];
const spec = sanitizeTerminalText(params.spec);
return [
`WARNING - Installing plugin from ${sourceLabel}: ${spec}`,
"This source is outside ClawHub review and trust metadata. Only continue if you trust the publisher, package contents, and install source.",
].join("\n");
}