Files
openclaw/src/plugins/official-external-install-trust.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

113 lines
3.4 KiB
TypeScript

// Resolves trusted official external plugin installs from the OpenClaw-owned catalog.
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
import {
getOfficialExternalPluginCatalogEntry,
getOfficialExternalPluginCatalogEntryForPackage,
resolveOfficialExternalPluginId,
resolveOfficialExternalPluginInstall,
} from "./official-external-plugin-catalog.js";
type OfficialExternalPluginLookup = (pluginId: string) =>
| {
pluginId: string;
npmSpec?: string;
expectedIntegrity?: string;
}
| undefined;
type OfficialExternalPackageLookup = (packageName: string) =>
| {
pluginId: string;
npmSpec?: string;
expectedIntegrity?: string;
}
| undefined;
function isBareNpmPackageName(spec: string): boolean {
const trimmed = spec.trim();
return /^[a-z0-9][a-z0-9-._~]*$/.test(trimmed);
}
function resolveCatalogInstall(value: string, lookup: "package" | "plugin") {
const entry =
lookup === "package"
? getOfficialExternalPluginCatalogEntryForPackage(value)
: getOfficialExternalPluginCatalogEntry(value);
if (!entry) {
return undefined;
}
const pluginId = resolveOfficialExternalPluginId(entry);
if (!pluginId) {
return undefined;
}
const install = resolveOfficialExternalPluginInstall(entry);
return {
pluginId,
...(install?.npmSpec ? { npmSpec: install.npmSpec } : {}),
...(install?.expectedIntegrity ? { expectedIntegrity: install.expectedIntegrity } : {}),
};
}
function resolveOfficialExternalInstallPlanBeforeNpm(params: {
rawSpec: string;
findOfficialExternalPlugin: OfficialExternalPluginLookup;
}): { pluginId: string; npmSpec: string; expectedIntegrity?: string } | null {
if (!isBareNpmPackageName(params.rawSpec)) {
return null;
}
const entry = params.findOfficialExternalPlugin(params.rawSpec);
const npmSpec = entry?.npmSpec?.trim();
if (!entry?.pluginId || !npmSpec) {
return null;
}
return {
pluginId: entry.pluginId,
npmSpec,
...(entry.expectedIntegrity ? { expectedIntegrity: entry.expectedIntegrity } : {}),
};
}
function resolveOfficialExternalNpmPackageTrust(params: {
npmSpec: string;
findOfficialExternalPackage: OfficialExternalPackageLookup;
}): {
pluginId: string;
expectedIntegrity?: string;
trustedSourceLinkedOfficialInstall: true;
} | null {
const parsed = parseRegistryNpmSpec(params.npmSpec);
if (!parsed) {
return null;
}
const entry = params.findOfficialExternalPackage(parsed.name);
if (!entry?.pluginId) {
return null;
}
const catalogSpec = entry.npmSpec?.trim();
const catalogPackageName = catalogSpec ? parseRegistryNpmSpec(catalogSpec)?.name : undefined;
if (catalogPackageName && catalogPackageName !== parsed.name) {
return null;
}
return {
pluginId: entry.pluginId,
...(entry.expectedIntegrity && catalogSpec === params.npmSpec.trim()
? { expectedIntegrity: entry.expectedIntegrity }
: {}),
trustedSourceLinkedOfficialInstall: true,
};
}
export function resolveCatalogOfficialExternalInstallPlan(rawSpec: string) {
return resolveOfficialExternalInstallPlanBeforeNpm({
rawSpec,
findOfficialExternalPlugin: (pluginId) => resolveCatalogInstall(pluginId, "plugin"),
});
}
export function resolveCatalogOfficialExternalNpmPackageTrust(npmSpec: string) {
return resolveOfficialExternalNpmPackageTrust({
npmSpec,
findOfficialExternalPackage: (packageName) => resolveCatalogInstall(packageName, "package"),
});
}