refactor(security): share scan path helpers
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
} from "../infra/install-safe-path.js";
|
||||
import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { extensionUsesSkippedScannerPath, isPathInside } from "../security/scan-paths.js";
|
||||
import * as skillScanner from "../security/skill-scanner.js";
|
||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||
|
||||
@@ -60,22 +61,6 @@ function validatePluginId(pluginId: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPathInside(basePath: string, candidatePath: string): boolean {
|
||||
const base = path.resolve(basePath);
|
||||
const candidate = path.resolve(candidatePath);
|
||||
const rel = path.relative(base, candidate);
|
||||
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
||||
}
|
||||
|
||||
function extensionUsesSkippedScannerPath(entry: string): boolean {
|
||||
const segments = entry.split(/[\\/]+/).filter(Boolean);
|
||||
return segments.some(
|
||||
(segment) =>
|
||||
segment === "node_modules" ||
|
||||
(segment.startsWith(".") && segment !== "." && segment !== ".."),
|
||||
);
|
||||
}
|
||||
|
||||
async function ensureOpenClawExtensions(manifest: PackageManifest) {
|
||||
const extensions = manifest[MANIFEST_KEY]?.extensions;
|
||||
if (!Array.isArray(extensions)) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
inspectPathPermissions,
|
||||
safeStat,
|
||||
} from "./audit-fs.js";
|
||||
import { extensionUsesSkippedScannerPath, isPathInside } from "./scan-paths.js";
|
||||
import * as skillScanner from "./skill-scanner.js";
|
||||
|
||||
export type SecurityAuditFinding = {
|
||||
@@ -62,22 +63,6 @@ function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isPathInside(basePath: string, candidatePath: string): boolean {
|
||||
const base = path.resolve(basePath);
|
||||
const candidate = path.resolve(candidatePath);
|
||||
const rel = path.relative(base, candidate);
|
||||
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
||||
}
|
||||
|
||||
function extensionUsesSkippedScannerPath(entry: string): boolean {
|
||||
const segments = entry.split(/[\\/]+/).filter(Boolean);
|
||||
return segments.some(
|
||||
(segment) =>
|
||||
segment === "node_modules" ||
|
||||
(segment.startsWith(".") && segment !== "." && segment !== ".."),
|
||||
);
|
||||
}
|
||||
|
||||
async function readPluginManifestExtensions(pluginPath: string): Promise<string[]> {
|
||||
const manifestPath = path.join(pluginPath, "package.json");
|
||||
const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
|
||||
|
||||
17
src/security/scan-paths.ts
Normal file
17
src/security/scan-paths.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import path from "node:path";
|
||||
|
||||
export function isPathInside(basePath: string, candidatePath: string): boolean {
|
||||
const base = path.resolve(basePath);
|
||||
const candidate = path.resolve(candidatePath);
|
||||
const rel = path.relative(base, candidate);
|
||||
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
||||
}
|
||||
|
||||
export function extensionUsesSkippedScannerPath(entry: string): boolean {
|
||||
const segments = entry.split(/[\\/]+/).filter(Boolean);
|
||||
return segments.some(
|
||||
(segment) =>
|
||||
segment === "node_modules" ||
|
||||
(segment.startsWith(".") && segment !== "." && segment !== ".."),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user