mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 22:10:44 +00:00
[codex] Extract filesystem safety primitives (#77918)
* refactor: extract filesystem safety primitives * refactor: use fs-safe for file access helpers * refactor: reuse fs-safe for media reads * refactor: use fs-safe for image reads * refactor: reuse fs-safe in qqbot media opener * refactor: reuse fs-safe for local media checks * refactor: consume cleaner fs-safe api * refactor: align fs-safe json option names * fix: preserve fs-safe migration contracts * refactor: use fs-safe primitive subpaths * refactor: use grouped fs-safe subpaths * refactor: align fs-safe api usage * refactor: adapt private state store api * chore: refresh proof gate * refactor: follow fs-safe json api split * refactor: follow reduced fs-safe surface * build: default fs-safe python helper off * fix: preserve fs-safe plugin sdk aliases * refactor: consolidate fs-safe usage * refactor: unify fs-safe store usage * refactor: trim fs-safe temp workspace usage * refactor: hide low-level fs-safe primitives * build: use published fs-safe package * fix: preserve outbound recovery durability after rebase * chore: refresh pr checks
This commit is contained in:
committed by
GitHub
parent
61481eb34f
commit
538605ff44
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { parseFrontmatterBlock } from "../markdown/frontmatter.js";
|
||||
import { isPathInsideWithRealpath } from "../security/scan-paths.js";
|
||||
import {
|
||||
@@ -56,7 +56,7 @@ function stripFrontmatter(content: string): string {
|
||||
|
||||
function readClaudeBundleManifest(rootDir: string): Record<string, unknown> {
|
||||
const manifestPath = path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: rootDir,
|
||||
boundaryLabel: "plugin root",
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { applyMergePatch } from "../config/merge-patch.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { matchRootFileOpenFailure, openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
|
||||
import type { PluginBundleFormat } from "./manifest-types.js";
|
||||
@@ -23,11 +23,11 @@ export function readBundleJsonObject(params: {
|
||||
rootDir: string;
|
||||
relativePath: string;
|
||||
onOpenFailure?: (
|
||||
failure: Extract<ReturnType<typeof openBoundaryFileSync>, { ok: false }>,
|
||||
failure: Extract<ReturnType<typeof openRootFileSync>, { ok: false }>,
|
||||
) => ReadBundleJsonResult;
|
||||
}): ReadBundleJsonResult {
|
||||
const absolutePath = path.join(params.rootDir, params.relativePath);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath,
|
||||
rootPath: params.rootDir,
|
||||
boundaryLabel: "plugin root",
|
||||
@@ -50,11 +50,11 @@ export function readBundleJsonObject(params: {
|
||||
}
|
||||
|
||||
export function resolveBundleJsonOpenFailure(params: {
|
||||
failure: Extract<ReturnType<typeof openBoundaryFileSync>, { ok: false }>;
|
||||
failure: Extract<ReturnType<typeof openRootFileSync>, { ok: false }>;
|
||||
relativePath: string;
|
||||
allowMissing?: boolean;
|
||||
}): ReadBundleJsonResult {
|
||||
return matchBoundaryFileOpenFailure(params.failure, {
|
||||
return matchRootFileOpenFailure(params.failure, {
|
||||
path: () => {
|
||||
if (params.allowMissing) {
|
||||
return { ok: true, raw: {} };
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { applyMergePatch } from "../config/merge-patch.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import {
|
||||
inspectBundleServerRuntimeSupport,
|
||||
@@ -65,7 +65,7 @@ function loadBundleLspConfigFile(params: {
|
||||
relativePath: string;
|
||||
}): BundleLspConfig {
|
||||
const absolutePath = path.resolve(params.rootDir, params.relativePath);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath,
|
||||
rootPath: params.rootDir,
|
||||
boundaryLabel: "plugin root",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import JSON5 from "json5";
|
||||
import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { matchRootFileOpenFailure, openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
@@ -98,7 +98,7 @@ function loadBundleManifestFile(params: {
|
||||
allowMissing?: boolean;
|
||||
}): BundleManifestFileLoadResult {
|
||||
const manifestPath = path.join(params.rootDir, params.manifestRelativePath);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: params.rootDir,
|
||||
...(params.rootRealPath !== undefined ? { rootRealPath: params.rootRealPath } : {}),
|
||||
@@ -106,7 +106,7 @@ function loadBundleManifestFile(params: {
|
||||
rejectHardlinks: params.rejectHardlinks,
|
||||
});
|
||||
if (!opened.ok) {
|
||||
return matchBoundaryFileOpenFailure(opened, {
|
||||
return matchRootFileOpenFailure(opened, {
|
||||
path: () => {
|
||||
if (params.allowMissing) {
|
||||
return { ok: true, raw: {}, manifestPath };
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { applyMergePatch } from "../config/merge-patch.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import {
|
||||
inspectBundleServerRuntimeSupport,
|
||||
@@ -168,7 +168,7 @@ function loadBundleFileBackedMcpConfig(params: {
|
||||
}): BundleMcpConfig {
|
||||
const rootDir = normalizeBundlePath(params.rootDir);
|
||||
const absolutePath = path.resolve(rootDir, params.relativePath);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath,
|
||||
rootPath: rootDir,
|
||||
boundaryLabel: "plugin root",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import {
|
||||
withBundledPluginEnablementCompat,
|
||||
@@ -277,7 +277,7 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
|
||||
workspaceDir: candidate.workspaceDir,
|
||||
});
|
||||
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: record.source,
|
||||
rootPath: record.source === candidate.source ? candidate.rootDir : repoRoot,
|
||||
boundaryLabel: record.source === candidate.source ? "plugin root" : "repo root",
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
||||
import { isPathInside } from "../infra/path-guards.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
@@ -78,8 +79,7 @@ function safeRealpathSync(targetPath: string): string | null {
|
||||
}
|
||||
|
||||
function pathContains(parentDir: string, childPath: string): boolean {
|
||||
const relative = path.relative(parentDir, childPath);
|
||||
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
return isPathInside(parentDir, childPath);
|
||||
}
|
||||
|
||||
function trustedBundledPluginRootsForPackageRoot(packageRoot: string): string[] {
|
||||
|
||||
@@ -749,7 +749,7 @@ async function verifyClawHubArchiveFiles(params: {
|
||||
extractedBytes += bytes;
|
||||
return extractedBytes <= DEFAULT_MAX_EXTRACTED_BYTES;
|
||||
};
|
||||
for (const entry of Object.values(zip.files)) {
|
||||
for (const entry of Object.values(zip.files as Record<string, JSZip.JSZipObject>)) {
|
||||
entryCount += 1;
|
||||
if (entryCount > DEFAULT_MAX_ENTRIES) {
|
||||
return buildClawHubInstallFailure(
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { expandHomePrefix } from "../infra/home-dir.js";
|
||||
import { writeJsonAtomic } from "../infra/json-files.js";
|
||||
import { writeJson } from "../infra/json-files.js";
|
||||
import { type ConversationRef } from "../infra/outbound/session-binding-service.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { resolveGlobalMap, resolveGlobalSingleton } from "../shared/global-singleton.js";
|
||||
@@ -379,7 +379,7 @@ async function saveApprovals(file: PluginBindingApprovalsFile): Promise<void> {
|
||||
const state = getPluginBindingGlobalState();
|
||||
state.approvalsCache = file;
|
||||
state.approvalsLoaded = true;
|
||||
await writeJsonAtomic(filePath, file, {
|
||||
await writeJson(filePath, file, {
|
||||
mode: 0o600,
|
||||
trailingNewline: true,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
@@ -415,7 +415,7 @@ function readPackageManifest(
|
||||
rootRealPath?: string,
|
||||
): PackageManifest | null {
|
||||
const manifestPath = path.join(dir, "package.json");
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: dir,
|
||||
...(rootRealPath !== undefined ? { rootRealPath } : {}),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import "../infra/fs-safe-defaults.js";
|
||||
import { createHash } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { withTempDir } from "../infra/install-source-utils.js";
|
||||
import { replaceDirectoryAtomic } from "../infra/replace-file.js";
|
||||
import {
|
||||
createSafeNpmInstallArgs,
|
||||
createSafeNpmInstallEnv,
|
||||
@@ -192,34 +193,12 @@ async function replaceManagedGitRepo(params: {
|
||||
stagedRepoDir: string;
|
||||
persistentRepoDir: string;
|
||||
}): Promise<{ ok: true } | { ok: false; error: string }> {
|
||||
const parentDir = path.dirname(params.persistentRepoDir);
|
||||
const backupDir = path.join(parentDir, `.repo-backup-${process.pid}-${Date.now()}`);
|
||||
let backupCreated = false;
|
||||
|
||||
try {
|
||||
await fs.mkdir(parentDir, { recursive: true });
|
||||
try {
|
||||
await fs.rename(params.persistentRepoDir, backupDir);
|
||||
backupCreated = true;
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.rename(params.stagedRepoDir, params.persistentRepoDir);
|
||||
} catch (err) {
|
||||
if (backupCreated) {
|
||||
await fs.rename(backupDir, params.persistentRepoDir);
|
||||
backupCreated = false;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (backupCreated) {
|
||||
await fs.rm(backupDir, { recursive: true, force: true });
|
||||
}
|
||||
await replaceDirectoryAtomic({
|
||||
stagedDir: params.stagedRepoDir,
|
||||
targetDir: params.persistentRepoDir,
|
||||
backupPrefix: ".repo-backup-",
|
||||
});
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fileExists, readJsonFile, resolveArchiveKind } from "../infra/archive.js";
|
||||
import { writeFileFromPathWithinRoot } from "../infra/fs-safe.js";
|
||||
import { resolveArchiveKind } from "../infra/archive.js";
|
||||
import { pathExists, root } from "../infra/fs-safe.js";
|
||||
import { resolveExistingInstallPath, withExtractedArchiveRoot } from "../infra/install-flow.js";
|
||||
import {
|
||||
resolveInstallModeOptions,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ensureInstallTargetAvailable,
|
||||
resolveCanonicalInstallTarget,
|
||||
} from "../infra/install-target.js";
|
||||
import { readJson } from "../infra/json-files.js";
|
||||
import {
|
||||
finalizeNpmSpecArchiveInstall,
|
||||
installFromNpmSpecArchiveWithInstaller,
|
||||
@@ -40,9 +41,10 @@ export type { NpmIntegrityDrift, NpmSpecResolution };
|
||||
|
||||
export {
|
||||
checkMinHostVersion,
|
||||
root,
|
||||
detectBundleManifestFormat,
|
||||
ensureInstallTargetAvailable,
|
||||
fileExists,
|
||||
pathExists as fileExists,
|
||||
finalizeNpmSpecArchiveInstall,
|
||||
getPackageManifestMetadata,
|
||||
installFromNpmSpecArchiveWithInstaller,
|
||||
@@ -50,7 +52,7 @@ export {
|
||||
isPathInside,
|
||||
loadBundleManifest,
|
||||
loadPluginManifest,
|
||||
readJsonFile,
|
||||
readJson as readJsonFile,
|
||||
resolveArchiveKind,
|
||||
resolveArchiveSourcePath,
|
||||
resolveCanonicalInstallTarget,
|
||||
@@ -66,5 +68,4 @@ export {
|
||||
scanPackageInstallSource,
|
||||
validateRegistryNpmSpec,
|
||||
withExtractedArchiveRoot,
|
||||
writeFileFromPathWithinRoot,
|
||||
};
|
||||
|
||||
@@ -1204,11 +1204,8 @@ export async function installPluginFromFile(params: {
|
||||
|
||||
logger.info?.(`Installing to ${preparedTarget.targetPath}…`);
|
||||
try {
|
||||
await runtime.writeFileFromPathWithinRoot({
|
||||
rootDir: extensionsDir,
|
||||
relativePath: path.basename(preparedTarget.targetPath),
|
||||
sourcePath: filePath,
|
||||
});
|
||||
const root = await runtime.root(extensionsDir);
|
||||
await root.copyIn(path.basename(preparedTarget.targetPath), filePath);
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err) };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { readJsonFile, readJsonFileSync } from "../infra/json-files.js";
|
||||
import { tryReadJson, tryReadJsonSync } from "../infra/json-files.js";
|
||||
import { resolveDefaultPluginNpmDir, validatePluginId } from "./install-paths.js";
|
||||
import {
|
||||
resolveInstalledPluginIndexStorePath,
|
||||
@@ -163,14 +163,14 @@ function extractPluginInstallRecordsFromPersistedInstalledPluginIndex(
|
||||
export async function readPersistedInstalledPluginIndexInstallRecords(
|
||||
options: InstalledPluginIndexStoreOptions = {},
|
||||
): Promise<Record<string, PluginInstallRecord> | null> {
|
||||
const parsed = await readJsonFile<unknown>(resolveInstalledPluginIndexStorePath(options));
|
||||
const parsed = await tryReadJson<unknown>(resolveInstalledPluginIndexStorePath(options));
|
||||
return extractPluginInstallRecordsFromPersistedInstalledPluginIndex(parsed);
|
||||
}
|
||||
|
||||
export function readPersistedInstalledPluginIndexInstallRecordsSync(
|
||||
options: InstalledPluginIndexStoreOptions = {},
|
||||
): Record<string, PluginInstallRecord> | null {
|
||||
const parsed = readJsonFileSync(resolveInstalledPluginIndexStorePath(options));
|
||||
const parsed = tryReadJsonSync(resolveInstalledPluginIndexStorePath(options));
|
||||
return extractPluginInstallRecordsFromPersistedInstalledPluginIndex(parsed);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { saveJsonFile } from "../infra/json-file.js";
|
||||
import { readJsonFile, readJsonFileSync, writeJsonAtomic } from "../infra/json-files.js";
|
||||
import { tryReadJson, tryReadJsonSync, writeJson } from "../infra/json-files.js";
|
||||
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
|
||||
import { safeParseWithSchema } from "../utils/zod-parse.js";
|
||||
import { resolveCompatibilityHostVersion } from "../version.js";
|
||||
@@ -161,14 +161,14 @@ function parseInstalledPluginIndex(value: unknown): InstalledPluginIndex | null
|
||||
export async function readPersistedInstalledPluginIndex(
|
||||
options: InstalledPluginIndexStoreOptions = {},
|
||||
): Promise<InstalledPluginIndex | null> {
|
||||
const parsed = await readJsonFile<unknown>(resolveInstalledPluginIndexStorePath(options));
|
||||
const parsed = await tryReadJson<unknown>(resolveInstalledPluginIndexStorePath(options));
|
||||
return parseInstalledPluginIndex(parsed);
|
||||
}
|
||||
|
||||
export function readPersistedInstalledPluginIndexSync(
|
||||
options: InstalledPluginIndexStoreOptions = {},
|
||||
): InstalledPluginIndex | null {
|
||||
const parsed = readJsonFileSync(resolveInstalledPluginIndexStorePath(options));
|
||||
const parsed = tryReadJsonSync(resolveInstalledPluginIndexStorePath(options));
|
||||
return parseInstalledPluginIndex(parsed);
|
||||
}
|
||||
|
||||
@@ -177,12 +177,12 @@ export async function writePersistedInstalledPluginIndex(
|
||||
options: InstalledPluginIndexStoreOptions = {},
|
||||
): Promise<string> {
|
||||
const filePath = resolveInstalledPluginIndexStorePath(options);
|
||||
await writeJsonAtomic(
|
||||
await writeJson(
|
||||
filePath,
|
||||
{ ...index, warning: INSTALLED_PLUGIN_INDEX_WARNING },
|
||||
{
|
||||
trailingNewline: true,
|
||||
ensureDirMode: 0o700,
|
||||
dirMode: 0o700,
|
||||
mode: 0o600,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import {
|
||||
DEFAULT_MEMORY_DREAMING_PLUGIN_ID,
|
||||
@@ -2005,7 +2005,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
: runtimeCandidateEntry;
|
||||
const moduleLoadSource = resolveCanonicalDistRuntimeSource(loadEntry.source);
|
||||
const moduleRoot = resolveCanonicalDistRuntimeSource(loadEntry.rootDir);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: moduleLoadSource,
|
||||
rootPath: moduleRoot,
|
||||
boundaryLabel: "plugin root",
|
||||
@@ -2096,7 +2096,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
const runtimeModuleRoot = resolveCanonicalDistRuntimeSource(
|
||||
runtimeCandidateEntry.rootDir,
|
||||
);
|
||||
const runtimeOpened = openBoundaryFileSync({
|
||||
const runtimeOpened = openRootFileSync({
|
||||
absolutePath: runtimeModuleSource,
|
||||
rootPath: runtimeModuleRoot,
|
||||
boundaryLabel: "plugin root",
|
||||
@@ -2677,7 +2677,7 @@ export async function loadOpenClawPluginCliRegistry(
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
continue;
|
||||
}
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: sourceForCliMetadata,
|
||||
rootPath: pluginRoot,
|
||||
boundaryLabel: "plugin root",
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.config.js";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { matchRootFileOpenFailure, openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
|
||||
import {
|
||||
normalizeModelCatalog,
|
||||
@@ -1513,7 +1513,7 @@ export function loadPluginManifest(
|
||||
rootRealPath?: string,
|
||||
): PluginManifestLoadResult {
|
||||
const manifestPath = resolvePluginManifestPath(rootDir);
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: manifestPath,
|
||||
rootPath: rootDir,
|
||||
...(rootRealPath !== undefined ? { rootRealPath } : {}),
|
||||
@@ -1522,7 +1522,7 @@ export function loadPluginManifest(
|
||||
rejectHardlinks,
|
||||
});
|
||||
if (!opened.ok) {
|
||||
return matchBoundaryFileOpenFailure(opened, {
|
||||
return matchRootFileOpenFailure(opened, {
|
||||
path: () => ({
|
||||
ok: false,
|
||||
error: `plugin manifest not found: ${manifestPath}`,
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveArchiveKind } from "../infra/archive.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { pathExists } from "../infra/fs-safe.js";
|
||||
import { resolveOsHomeRelativePath } from "../infra/home-dir.js";
|
||||
import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
|
||||
import { isPathInside } from "../infra/path-guards.js";
|
||||
@@ -308,15 +309,6 @@ function parseMarketplaceManifest(
|
||||
};
|
||||
}
|
||||
|
||||
async function pathExists(target: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(target);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function readClaudeKnownMarketplaces(): Promise<Record<string, KnownMarketplaceRecord>> {
|
||||
const knownPath = resolveOsHomeRelativePath(CLAUDE_KNOWN_MARKETPLACES_PATH);
|
||||
if (!(await pathExists(knownPath))) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
matchBoundaryFileOpenFailure,
|
||||
openBoundaryFile,
|
||||
openBoundaryFileSync,
|
||||
matchRootFileOpenFailure,
|
||||
openRootFile,
|
||||
openRootFileSync,
|
||||
} from "../infra/boundary-file-read.js";
|
||||
import { resolveBoundaryPath, resolveBoundaryPathSync } from "../infra/boundary-path.js";
|
||||
import { resolveRootPath, resolveRootPathSync } from "../infra/boundary-path.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import type { PluginDiagnostic } from "./manifest-types.js";
|
||||
import { getPackageManifestMetadata, type PackageManifest } from "./manifest.js";
|
||||
@@ -98,7 +98,7 @@ async function validatePackageExtensionEntry(params: {
|
||||
}): Promise<ExtensionEntryValidation> {
|
||||
const absolutePath = path.resolve(params.packageDir, params.entry);
|
||||
try {
|
||||
const resolved = await resolveBoundaryPath({
|
||||
const resolved = await resolveRootPath({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
boundaryLabel: "plugin package directory",
|
||||
@@ -115,13 +115,13 @@ async function validatePackageExtensionEntry(params: {
|
||||
};
|
||||
}
|
||||
|
||||
const opened = await openBoundaryFile({
|
||||
const opened = await openRootFile({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
boundaryLabel: "plugin package directory",
|
||||
});
|
||||
if (!opened.ok) {
|
||||
return matchBoundaryFileOpenFailure(opened, {
|
||||
return matchRootFileOpenFailure(opened, {
|
||||
path: () => ({ ok: false, error: `${params.label} not found: ${params.entry}` }),
|
||||
io: () => ({ ok: false, error: `${params.label} unreadable: ${params.entry}` }),
|
||||
validation: () => ({
|
||||
@@ -326,7 +326,7 @@ function resolvePackageEntrySource(params: {
|
||||
const rejectHardlinks = params.rejectHardlinks ?? true;
|
||||
const candidates = [source];
|
||||
const openCandidate = (absolutePath: string): string | null => {
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
@@ -336,7 +336,7 @@ function resolvePackageEntrySource(params: {
|
||||
rejectHardlinks,
|
||||
});
|
||||
if (!opened.ok) {
|
||||
return matchBoundaryFileOpenFailure(opened, {
|
||||
return matchRootFileOpenFailure(opened, {
|
||||
path: () => null,
|
||||
io: () => {
|
||||
params.diagnostics.push({
|
||||
@@ -415,7 +415,7 @@ function resolveSafePackageEntry(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
resolveBoundaryPathSync({
|
||||
resolveRootPathSync({
|
||||
absolutePath,
|
||||
rootPath: params.packageDir,
|
||||
...(params.packageRootRealPath !== undefined
|
||||
|
||||
@@ -1,33 +1,16 @@
|
||||
import fs from "node:fs";
|
||||
import { isPathInside as isBoundaryPathInside } from "../infra/path-guards.js";
|
||||
|
||||
export function isPathInside(baseDir: string, targetPath: string): boolean {
|
||||
return isBoundaryPathInside(baseDir, targetPath);
|
||||
}
|
||||
|
||||
export function safeRealpathSync(targetPath: string, cache?: Map<string, string>): string | null {
|
||||
const cached = cache?.get(targetPath);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const resolved = fs.realpathSync(targetPath);
|
||||
cache?.set(targetPath, resolved);
|
||||
cache?.set(resolved, resolved);
|
||||
return resolved;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function safeStatSync(targetPath: string): fs.Stats | null {
|
||||
try {
|
||||
return fs.statSync(targetPath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatPosixMode(mode: number): string {
|
||||
return (mode & 0o777).toString(8).padStart(3, "0");
|
||||
}
|
||||
export {
|
||||
isNotFoundPathError,
|
||||
hasNodeErrorCode,
|
||||
isNodeError,
|
||||
isPathInside,
|
||||
isPathInsideWithRealpath,
|
||||
isSymlinkOpenError,
|
||||
isWithinDir,
|
||||
normalizeWindowsPathForComparison,
|
||||
resolveSafeBaseDir,
|
||||
resolveSafeRelativePath,
|
||||
safeRealpathSync,
|
||||
safeStatSync,
|
||||
splitSafeRelativePath,
|
||||
formatPosixMode,
|
||||
} from "../infra/path-safety.js";
|
||||
|
||||
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { sameFileIdentity } from "../infra/file-identity.js";
|
||||
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
||||
import { sameFileIdentity } from "../infra/fs-safe-advanced.js";
|
||||
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
@@ -129,7 +129,7 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
|
||||
return cached as T;
|
||||
}
|
||||
|
||||
const opened = openBoundaryFileSync({
|
||||
const opened = openRootFileSync({
|
||||
absolutePath: location.modulePath,
|
||||
rootPath: location.boundaryRoot,
|
||||
boundaryLabel:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { isPathInside } from "../infra/path-guards.js";
|
||||
import { shortenHomeInString } from "../utils.js";
|
||||
import type { PluginRecord } from "./registry.js";
|
||||
import type { PluginSourceRoots } from "./roots.js";
|
||||
@@ -6,19 +7,13 @@ export { resolvePluginSourceRoots } from "./roots.js";
|
||||
export type { PluginSourceRoots } from "./roots.js";
|
||||
|
||||
function tryRelative(root: string, filePath: string): string | null {
|
||||
if (!isPathInside(root, filePath)) {
|
||||
return null;
|
||||
}
|
||||
const rel = path.relative(root, filePath);
|
||||
if (!rel || rel === ".") {
|
||||
return null;
|
||||
}
|
||||
if (rel === "..") {
|
||||
return null;
|
||||
}
|
||||
if (rel.startsWith(`..${path.sep}`) || rel.startsWith("../") || rel.startsWith("..\\")) {
|
||||
return null;
|
||||
}
|
||||
if (path.isAbsolute(rel)) {
|
||||
return null;
|
||||
}
|
||||
// Normalize to forward slashes for display (path.relative uses backslashes on Windows)
|
||||
return rel.replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user