mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 11:26:46 +00:00
refactor(config): share include scan helper
This commit is contained in:
87
src/config/includes-scan.ts
Normal file
87
src/config/includes-scan.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import JSON5 from "json5";
|
||||
import * as fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "./includes.js";
|
||||
|
||||
function listDirectIncludes(parsed: unknown): string[] {
|
||||
const out: string[] = [];
|
||||
const visit = (value: unknown) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
visit(item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof value !== "object") {
|
||||
return;
|
||||
}
|
||||
const rec = value as Record<string, unknown>;
|
||||
const includeVal = rec[INCLUDE_KEY];
|
||||
if (typeof includeVal === "string") {
|
||||
out.push(includeVal);
|
||||
} else if (Array.isArray(includeVal)) {
|
||||
for (const item of includeVal) {
|
||||
if (typeof item === "string") {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const v of Object.values(rec)) {
|
||||
visit(v);
|
||||
}
|
||||
};
|
||||
visit(parsed);
|
||||
return out;
|
||||
}
|
||||
|
||||
function resolveIncludePath(baseConfigPath: string, includePath: string): string {
|
||||
return path.normalize(
|
||||
path.isAbsolute(includePath)
|
||||
? includePath
|
||||
: path.resolve(path.dirname(baseConfigPath), includePath),
|
||||
);
|
||||
}
|
||||
|
||||
export async function collectIncludePathsRecursive(params: {
|
||||
configPath: string;
|
||||
parsed: unknown;
|
||||
}): Promise<string[]> {
|
||||
const visited = new Set<string>();
|
||||
const result: string[] = [];
|
||||
|
||||
const walk = async (basePath: string, parsed: unknown, depth: number): Promise<void> => {
|
||||
if (depth > MAX_INCLUDE_DEPTH) {
|
||||
return;
|
||||
}
|
||||
for (const raw of listDirectIncludes(parsed)) {
|
||||
const resolved = resolveIncludePath(basePath, raw);
|
||||
if (visited.has(resolved)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(resolved);
|
||||
result.push(resolved);
|
||||
|
||||
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
||||
if (!rawText) {
|
||||
continue;
|
||||
}
|
||||
const nestedParsed = (() => {
|
||||
try {
|
||||
return JSON5.parse(rawText);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
if (nestedParsed) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walk(resolved, nestedParsed, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await walk(params.configPath, params.parsed, 0);
|
||||
return result;
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
*
|
||||
* These functions perform I/O (filesystem, config reads) to detect security issues.
|
||||
*/
|
||||
import JSON5 from "json5";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { SandboxToolPolicy } from "../agents/sandbox/types.js";
|
||||
@@ -22,7 +21,7 @@ import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { resolveNativeSkillsEnabled } from "../config/commands.js";
|
||||
import { createConfigIO } from "../config/config.js";
|
||||
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
|
||||
import { collectIncludePathsRecursive } from "../config/includes-scan.js";
|
||||
import { resolveOAuthDir } from "../config/paths.js";
|
||||
import { normalizePluginsConfig } from "../plugins/config-state.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
@@ -63,88 +62,6 @@ function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveIncludePath(baseConfigPath: string, includePath: string): string {
|
||||
return path.normalize(
|
||||
path.isAbsolute(includePath)
|
||||
? includePath
|
||||
: path.resolve(path.dirname(baseConfigPath), includePath),
|
||||
);
|
||||
}
|
||||
|
||||
function listDirectIncludes(parsed: unknown): string[] {
|
||||
const out: string[] = [];
|
||||
const visit = (value: unknown) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
visit(item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof value !== "object") {
|
||||
return;
|
||||
}
|
||||
const rec = value as Record<string, unknown>;
|
||||
const includeVal = rec[INCLUDE_KEY];
|
||||
if (typeof includeVal === "string") {
|
||||
out.push(includeVal);
|
||||
} else if (Array.isArray(includeVal)) {
|
||||
for (const item of includeVal) {
|
||||
if (typeof item === "string") {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const v of Object.values(rec)) {
|
||||
visit(v);
|
||||
}
|
||||
};
|
||||
visit(parsed);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function collectIncludePathsRecursive(params: {
|
||||
configPath: string;
|
||||
parsed: unknown;
|
||||
}): Promise<string[]> {
|
||||
const visited = new Set<string>();
|
||||
const result: string[] = [];
|
||||
|
||||
const walk = async (basePath: string, parsed: unknown, depth: number): Promise<void> => {
|
||||
if (depth > MAX_INCLUDE_DEPTH) {
|
||||
return;
|
||||
}
|
||||
for (const raw of listDirectIncludes(parsed)) {
|
||||
const resolved = resolveIncludePath(basePath, raw);
|
||||
if (visited.has(resolved)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(resolved);
|
||||
result.push(resolved);
|
||||
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
||||
if (!rawText) {
|
||||
continue;
|
||||
}
|
||||
const nestedParsed = (() => {
|
||||
try {
|
||||
return JSON5.parse(rawText);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
if (nestedParsed) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walk(resolved, nestedParsed, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await walk(params.configPath, params.parsed, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
function isPathInside(basePath: string, candidatePath: string): boolean {
|
||||
const base = path.resolve(basePath);
|
||||
const candidate = path.resolve(candidatePath);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import JSON5 from "json5";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { createConfigIO } from "../config/config.js";
|
||||
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
|
||||
import { collectIncludePathsRecursive } from "../config/includes-scan.js";
|
||||
import { resolveConfigPath, resolveOAuthDir, resolveStateDir } from "../config/paths.js";
|
||||
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
||||
import { runExec } from "../process/exec.js";
|
||||
@@ -303,88 +302,6 @@ function applyConfigFixes(params: { cfg: OpenClawConfig; env: NodeJS.ProcessEnv
|
||||
return { cfg: next, changes, policyFlips };
|
||||
}
|
||||
|
||||
function listDirectIncludes(parsed: unknown): string[] {
|
||||
const out: string[] = [];
|
||||
const visit = (value: unknown) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
visit(item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof value !== "object") {
|
||||
return;
|
||||
}
|
||||
const rec = value as Record<string, unknown>;
|
||||
const includeVal = rec[INCLUDE_KEY];
|
||||
if (typeof includeVal === "string") {
|
||||
out.push(includeVal);
|
||||
} else if (Array.isArray(includeVal)) {
|
||||
for (const item of includeVal) {
|
||||
if (typeof item === "string") {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const v of Object.values(rec)) {
|
||||
visit(v);
|
||||
}
|
||||
};
|
||||
visit(parsed);
|
||||
return out;
|
||||
}
|
||||
|
||||
function resolveIncludePath(baseConfigPath: string, includePath: string): string {
|
||||
return path.normalize(
|
||||
path.isAbsolute(includePath)
|
||||
? includePath
|
||||
: path.resolve(path.dirname(baseConfigPath), includePath),
|
||||
);
|
||||
}
|
||||
|
||||
async function collectIncludePathsRecursive(params: {
|
||||
configPath: string;
|
||||
parsed: unknown;
|
||||
}): Promise<string[]> {
|
||||
const visited = new Set<string>();
|
||||
const result: string[] = [];
|
||||
|
||||
const walk = async (basePath: string, parsed: unknown, depth: number): Promise<void> => {
|
||||
if (depth > MAX_INCLUDE_DEPTH) {
|
||||
return;
|
||||
}
|
||||
for (const raw of listDirectIncludes(parsed)) {
|
||||
const resolved = resolveIncludePath(basePath, raw);
|
||||
if (visited.has(resolved)) {
|
||||
continue;
|
||||
}
|
||||
visited.add(resolved);
|
||||
result.push(resolved);
|
||||
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
||||
if (!rawText) {
|
||||
continue;
|
||||
}
|
||||
const nestedParsed = (() => {
|
||||
try {
|
||||
return JSON5.parse(rawText);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
if (nestedParsed) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await walk(resolved, nestedParsed, depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await walk(params.configPath, params.parsed, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function chmodCredentialsAndAgentState(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
stateDir: string;
|
||||
|
||||
Reference in New Issue
Block a user