mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:13:53 +00:00
* refactor(shared): add markdown code span/fence helpers and migrate seven call sites * refactor(normalization-core): add canonical toErrorObject and migrate six copies * refactor: consolidate byte-identical helper pairs onto owner modules * refactor: reuse canonical path and token helpers in session tools and credentials * refactor(packages): dedupe compaction summarization tail and session dir parsing * fix(security): keep missing extensions dir silent in shared plugin dir lister * refactor: reuse media-core chunk reader and shared dreaming session key helper * fix(plugins): register error-coercion subpath in sdk alias table * chore(plugin-sdk): re-pin callable export count after rebase * chore(plugin-sdk): re-pin callable export count after rebase
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
// Resolves installed plugin directories for security trust audits.
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
|
|
|
|
const IGNORED_INSTALLED_PLUGIN_DIR_NAMES = new Set(["node_modules", ".openclaw-install-backups"]);
|
|
|
|
/**
|
|
* Decide whether an installed-plugin directory should be skipped by security audits.
|
|
* This filters generated install debris while keeping real plugin roots visible to scans.
|
|
*/
|
|
export function shouldIgnoreInstalledPluginDirName(name: string): boolean {
|
|
const normalized = normalizeOptionalLowercaseString(name);
|
|
if (!normalized) {
|
|
return true;
|
|
}
|
|
if (IGNORED_INSTALLED_PLUGIN_DIR_NAMES.has(normalized)) {
|
|
return true;
|
|
}
|
|
if (normalized.startsWith(".")) {
|
|
return true;
|
|
}
|
|
// Failed installs and rollback copies can contain stale plugin code; audit the live
|
|
// root once and ignore these generated backups so findings stay actionable.
|
|
if (normalized.endsWith(".bak")) {
|
|
return true;
|
|
}
|
|
if (normalized.includes(".backup-")) {
|
|
return true;
|
|
}
|
|
if (normalized.includes(".disabled")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Lists installed plugin directories under the state extensions dir. Read
|
|
* failures surface through `onReadError` so audits can report scan problems,
|
|
* except a missing extensions dir, which is the normal no-plugins state.
|
|
*/
|
|
export async function listInstalledPluginDirs(params: {
|
|
stateDir: string;
|
|
onReadError?: (error: unknown) => void;
|
|
}): Promise<{ extensionsDir: string; pluginDirs: string[] }> {
|
|
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
const st = await fs.stat(extensionsDir).catch((err: unknown) => {
|
|
const code = (err as NodeJS.ErrnoException | null)?.code;
|
|
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
params.onReadError?.(err);
|
|
}
|
|
return null;
|
|
});
|
|
if (!st?.isDirectory()) {
|
|
return { extensionsDir, pluginDirs: [] };
|
|
}
|
|
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err: unknown) => {
|
|
params.onReadError?.(err);
|
|
return [];
|
|
});
|
|
const pluginDirs = entries
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => entry.name)
|
|
.filter((name) => !shouldIgnoreInstalledPluginDirName(name))
|
|
.filter(Boolean);
|
|
return { extensionsDir, pluginDirs };
|
|
}
|