mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 20:48:26 +00:00
refactor(plugins): extract safety and provenance helpers
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { initializeGlobalHookRunner } from "./hook-runner-global.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { isPathInside, safeStatSync } from "./path-safety.js";
|
||||
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
|
||||
import { setActivePluginRegistry } from "./runtime.js";
|
||||
import { createPluginRuntime } from "./runtime/index.js";
|
||||
@@ -177,61 +178,100 @@ function pushDiagnostics(diagnostics: PluginDiagnostic[], append: PluginDiagnost
|
||||
diagnostics.push(...append);
|
||||
}
|
||||
|
||||
function isPathInside(baseDir: string, targetPath: string): boolean {
|
||||
const rel = path.relative(baseDir, targetPath);
|
||||
if (!rel) {
|
||||
return true;
|
||||
}
|
||||
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
||||
type PathMatcher = {
|
||||
exact: Set<string>;
|
||||
dirs: string[];
|
||||
};
|
||||
|
||||
type InstallTrackingRule = {
|
||||
trackedWithoutPaths: boolean;
|
||||
matcher: PathMatcher;
|
||||
};
|
||||
|
||||
type PluginProvenanceIndex = {
|
||||
loadPathMatcher: PathMatcher;
|
||||
installRules: Map<string, InstallTrackingRule>;
|
||||
};
|
||||
|
||||
function createPathMatcher(): PathMatcher {
|
||||
return { exact: new Set<string>(), dirs: [] };
|
||||
}
|
||||
|
||||
function pathMatchesBaseOrFile(params: { baseOrFile: string; targetFile: string }): boolean {
|
||||
const baseResolved = resolveUserPath(params.baseOrFile);
|
||||
const targetResolved = resolveUserPath(params.targetFile);
|
||||
if (baseResolved === targetResolved) {
|
||||
function addPathToMatcher(matcher: PathMatcher, rawPath: string): void {
|
||||
const trimmed = rawPath.trim();
|
||||
if (!trimmed) {
|
||||
return;
|
||||
}
|
||||
const resolved = resolveUserPath(trimmed);
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
if (matcher.exact.has(resolved) || matcher.dirs.includes(resolved)) {
|
||||
return;
|
||||
}
|
||||
const stat = safeStatSync(resolved);
|
||||
if (stat?.isDirectory()) {
|
||||
matcher.dirs.push(resolved);
|
||||
return;
|
||||
}
|
||||
matcher.exact.add(resolved);
|
||||
}
|
||||
|
||||
function matchesPathMatcher(matcher: PathMatcher, sourcePath: string): boolean {
|
||||
if (matcher.exact.has(sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const stat = fs.statSync(baseResolved);
|
||||
if (!stat.isDirectory()) {
|
||||
return false;
|
||||
return matcher.dirs.some((dirPath) => isPathInside(dirPath, sourcePath));
|
||||
}
|
||||
|
||||
function buildProvenanceIndex(params: {
|
||||
config: OpenClawConfig;
|
||||
normalizedLoadPaths: string[];
|
||||
}): PluginProvenanceIndex {
|
||||
const loadPathMatcher = createPathMatcher();
|
||||
for (const loadPath of params.normalizedLoadPaths) {
|
||||
addPathToMatcher(loadPathMatcher, loadPath);
|
||||
}
|
||||
|
||||
const installRules = new Map<string, InstallTrackingRule>();
|
||||
const installs = params.config.plugins?.installs ?? {};
|
||||
for (const [pluginId, install] of Object.entries(installs)) {
|
||||
const rule: InstallTrackingRule = {
|
||||
trackedWithoutPaths: false,
|
||||
matcher: createPathMatcher(),
|
||||
};
|
||||
const trackedPaths = [install.installPath, install.sourcePath]
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
if (trackedPaths.length === 0) {
|
||||
rule.trackedWithoutPaths = true;
|
||||
} else {
|
||||
for (const trackedPath of trackedPaths) {
|
||||
addPathToMatcher(rule.matcher, trackedPath);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
installRules.set(pluginId, rule);
|
||||
}
|
||||
return isPathInside(baseResolved, targetResolved);
|
||||
|
||||
return { loadPathMatcher, installRules };
|
||||
}
|
||||
|
||||
function isTrackedByInstallRecord(params: {
|
||||
function isTrackedByProvenance(params: {
|
||||
pluginId: string;
|
||||
source: string;
|
||||
config: OpenClawConfig;
|
||||
index: PluginProvenanceIndex;
|
||||
}): boolean {
|
||||
const install = params.config.plugins?.installs?.[params.pluginId];
|
||||
if (!install) {
|
||||
return false;
|
||||
const sourcePath = resolveUserPath(params.source);
|
||||
const installRule = params.index.installRules.get(params.pluginId);
|
||||
if (installRule) {
|
||||
if (installRule.trackedWithoutPaths) {
|
||||
return true;
|
||||
}
|
||||
if (matchesPathMatcher(installRule.matcher, sourcePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const trackedPaths = [install.installPath, install.sourcePath]
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
||||
.filter(Boolean);
|
||||
if (trackedPaths.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return trackedPaths.some((trackedPath) =>
|
||||
pathMatchesBaseOrFile({
|
||||
baseOrFile: trackedPath,
|
||||
targetFile: params.source,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function isTrackedByLoadPath(params: { source: string; loadPaths: string[] }): boolean {
|
||||
return params.loadPaths.some((loadPath) =>
|
||||
pathMatchesBaseOrFile({
|
||||
baseOrFile: loadPath,
|
||||
targetFile: params.source,
|
||||
}),
|
||||
);
|
||||
return matchesPathMatcher(params.index.loadPathMatcher, sourcePath);
|
||||
}
|
||||
|
||||
function warnWhenAllowlistIsOpen(params: {
|
||||
@@ -262,8 +302,7 @@ function warnWhenAllowlistIsOpen(params: {
|
||||
|
||||
function warnAboutUntrackedLoadedPlugins(params: {
|
||||
registry: PluginRegistry;
|
||||
config: OpenClawConfig;
|
||||
normalizedLoadPaths: string[];
|
||||
provenance: PluginProvenanceIndex;
|
||||
logger: PluginLogger;
|
||||
}) {
|
||||
for (const plugin of params.registry.plugins) {
|
||||
@@ -271,18 +310,10 @@ function warnAboutUntrackedLoadedPlugins(params: {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
isTrackedByInstallRecord({
|
||||
isTrackedByProvenance({
|
||||
pluginId: plugin.id,
|
||||
source: plugin.source,
|
||||
config: params.config,
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
isTrackedByLoadPath({
|
||||
source: plugin.source,
|
||||
loadPaths: params.normalizedLoadPaths,
|
||||
index: params.provenance,
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
@@ -351,6 +382,10 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
origin: plugin.origin,
|
||||
})),
|
||||
});
|
||||
const provenance = buildProvenanceIndex({
|
||||
config: cfg,
|
||||
normalizedLoadPaths: normalized.loadPaths,
|
||||
});
|
||||
|
||||
// Lazy: avoid creating the Jiti loader when all plugins are disabled (common in unit tests).
|
||||
let jitiLoader: ReturnType<typeof createJiti> | null = null;
|
||||
@@ -605,8 +640,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
|
||||
warnAboutUntrackedLoadedPlugins({
|
||||
registry,
|
||||
config: cfg,
|
||||
normalizedLoadPaths: normalized.loadPaths,
|
||||
provenance,
|
||||
logger,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user