




















@@ -5,11 +5,6 @@
55 */
66import fs from "node:fs/promises";
77import path from "node:path";
8-import { resolveDefaultAgentId } from "../agents/agent-scope.js";
9-import { SANDBOX_BROWSER_SECURITY_HASH_EPOCH } from "../agents/sandbox/constants.js";
10-import { execDockerRaw, type ExecDockerRawResult } from "../agents/sandbox/docker.js";
11-import { resolveSkillSource } from "../agents/skills/source.js";
12-import { listAgentWorkspaceDirs } from "../agents/workspace-dirs.js";
138import { formatCliCommand } from "../cli/command-format.js";
149import { MANIFEST_KEY } from "../compat/legacy-names.js";
1510import type { OpenClawConfig, ConfigFileSnapshot } from "../config/config.js";
@@ -20,19 +15,10 @@ import {
2015normalizeOptionalLowercaseString,
2116normalizeOptionalString,
2217} from "../shared/string-coerce.js";
23-import {
24-formatPermissionDetail,
25-formatPermissionRemediation,
26-inspectPathPermissions,
27-safeStat,
28-} from "./audit-fs.js";
2918import { extensionUsesSkippedScannerPath, isPathInside } from "./scan-paths.js";
3019import type { SkillScanFinding } from "./skill-scanner.js";
31-import * as skillScanner from "./skill-scanner.js";
3220import type { ExecFn } from "./windows-acl.js";
332134-export { collectPluginsTrustFindings } from "./audit-plugins-trust.js";
35-3622export type SecurityAuditFinding = {
3723checkId: string;
3824severity: "info" | "warn" | "critical";
@@ -41,10 +27,16 @@ export type SecurityAuditFinding = {
4127remediation?: string;
4228};
432930+type CollectPluginsTrustFindingsParams = Parameters<
31+typeof import("./audit-plugins-trust.js").collectPluginsTrustFindings
32+>[0];
33+type SkillScanSummary = Awaited<
34+ReturnType<typeof import("./skill-scanner.js").scanDirectoryWithSummary>
35+>;
4436type ExecDockerRawFn = (
4537args: string[],
4638opts?: { allowFailure?: boolean; input?: Buffer | string; signal?: AbortSignal },
47-) => Promise<ExecDockerRawResult>;
39+) => Promise<import("../agents/sandbox/docker.js").ExecDockerRawResult>;
48404941type CodeSafetySummaryCache = Map<string, Promise<unknown>>;
5042type WorkspaceSkillScanLimits = {
@@ -91,6 +83,18 @@ function realpathWithTimeout(p: string, timeoutMs = 2000): Promise<string | null
9183}
9284let skillsModulePromise: Promise<typeof import("../agents/skills.js")> | undefined;
9385let configModulePromise: Promise<typeof import("../config/config.js")> | undefined;
86+let agentScopeModulePromise: Promise<typeof import("../agents/agent-scope.js")> | undefined;
87+let agentWorkspaceDirsModulePromise:
88+| Promise<typeof import("../agents/workspace-dirs.js")>
89+| undefined;
90+let skillSourceModulePromise: Promise<typeof import("../agents/skills/source.js")> | undefined;
91+let sandboxDockerModulePromise: Promise<typeof import("../agents/sandbox/docker.js")> | undefined;
92+let sandboxConstantsModulePromise:
93+| Promise<typeof import("../agents/sandbox/constants.js")>
94+| undefined;
95+let auditPluginsTrustModulePromise: Promise<typeof import("./audit-plugins-trust.js")> | undefined;
96+let auditFsModulePromise: Promise<typeof import("./audit-fs.js")> | undefined;
97+let skillScannerModulePromise: Promise<typeof import("./skill-scanner.js")> | undefined;
94989599function loadSkillsModule() {
96100skillsModulePromise ??= import("../agents/skills.js");
@@ -102,10 +106,87 @@ function loadConfigModule() {
102106return configModulePromise;
103107}
104108109+function loadAuditFsModule() {
110+auditFsModulePromise ??= import("./audit-fs.js");
111+return auditFsModulePromise;
112+}
113+114+function loadAgentScopeModule() {
115+agentScopeModulePromise ??= import("../agents/agent-scope.js");
116+return agentScopeModulePromise;
117+}
118+119+function loadAgentWorkspaceDirsModule() {
120+agentWorkspaceDirsModulePromise ??= import("../agents/workspace-dirs.js");
121+return agentWorkspaceDirsModulePromise;
122+}
123+124+function loadSkillSourceModule() {
125+skillSourceModulePromise ??= import("../agents/skills/source.js");
126+return skillSourceModulePromise;
127+}
128+129+function loadSkillScannerModule() {
130+skillScannerModulePromise ??= import("./skill-scanner.js");
131+return skillScannerModulePromise;
132+}
133+134+async function loadExecDockerRaw(): Promise<ExecDockerRawFn> {
135+sandboxDockerModulePromise ??= import("../agents/sandbox/docker.js");
136+const { execDockerRaw } = await sandboxDockerModulePromise;
137+return execDockerRaw;
138+}
139+140+async function loadSandboxBrowserSecurityHashEpoch(): Promise<string> {
141+sandboxConstantsModulePromise ??= import("../agents/sandbox/constants.js");
142+const { SANDBOX_BROWSER_SECURITY_HASH_EPOCH } = await sandboxConstantsModulePromise;
143+return SANDBOX_BROWSER_SECURITY_HASH_EPOCH;
144+}
145+146+export async function collectPluginsTrustFindings(
147+params: CollectPluginsTrustFindingsParams,
148+): Promise<SecurityAuditFinding[]> {
149+auditPluginsTrustModulePromise ??= import("./audit-plugins-trust.js");
150+const { collectPluginsTrustFindings: collect } = await auditPluginsTrustModulePromise;
151+return await collect(params);
152+}
153+105154// --------------------------------------------------------------------------
106155// Helpers
107156// --------------------------------------------------------------------------
108157158+async function safeStat(targetPath: string): Promise<{
159+ok: boolean;
160+isSymlink: boolean;
161+isDir: boolean;
162+mode: number | null;
163+uid: number | null;
164+gid: number | null;
165+error?: string;
166+}> {
167+try {
168+const lst = await fs.lstat(targetPath);
169+return {
170+ok: true,
171+isSymlink: lst.isSymbolicLink(),
172+isDir: lst.isDirectory(),
173+mode: typeof lst.mode === "number" ? lst.mode : null,
174+uid: typeof lst.uid === "number" ? lst.uid : null,
175+gid: typeof lst.gid === "number" ? lst.gid : null,
176+};
177+} catch (err) {
178+return {
179+ok: false,
180+isSymlink: false,
181+isDir: false,
182+mode: null,
183+uid: null,
184+gid: null,
185+error: String(err),
186+};
187+}
188+}
189+109190function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null {
110191if (!p.startsWith("~")) {
111192return p;
@@ -197,7 +278,7 @@ async function getCodeSafetySummary(params: {
197278dirPath: string;
198279includeFiles?: string[];
199280summaryCache?: CodeSafetySummaryCache;
200-}): Promise<Awaited<ReturnType<typeof skillScanner.scanDirectoryWithSummary>>> {
281+}): Promise<SkillScanSummary> {
201282const cacheKey = buildCodeSafetySummaryCacheKey({
202283dirPath: params.dirPath,
203284includeFiles: params.includeFiles,
@@ -206,14 +287,16 @@ async function getCodeSafetySummary(params: {
206287if (cache) {
207288const hit = cache.get(cacheKey);
208289if (hit) {
209-return (await hit) as Awaited<ReturnType<typeof skillScanner.scanDirectoryWithSummary>>;
290+return (await hit) as SkillScanSummary;
210291}
292+const skillScanner = await loadSkillScannerModule();
211293const pending = skillScanner.scanDirectoryWithSummary(params.dirPath, {
212294includeFiles: params.includeFiles,
213295});
214296cache.set(cacheKey, pending);
215297return await pending;
216298}
299+const skillScanner = await loadSkillScannerModule();
217300return await skillScanner.scanDirectoryWithSummary(params.dirPath, {
218301includeFiles: params.includeFiles,
219302});
@@ -388,7 +471,10 @@ export async function collectSandboxBrowserHashLabelFindings(params?: {
388471execDockerRawFn?: ExecDockerRawFn;
389472}): Promise<SecurityAuditFinding[]> {
390473const findings: SecurityAuditFinding[] = [];
391-const execFn = params?.execDockerRawFn ?? execDockerRaw;
474+const [execFn, browserHashEpoch] = await Promise.all([
475+params?.execDockerRawFn ? Promise.resolve(params.execDockerRawFn) : loadExecDockerRaw(),
476+loadSandboxBrowserSecurityHashEpoch(),
477+]);
392478const containers = await listSandboxBrowserContainers(execFn);
393479if (!containers || containers.length === 0) {
394480return findings;
@@ -406,7 +492,7 @@ export async function collectSandboxBrowserHashLabelFindings(params?: {
406492if (!labels.configHash) {
407493missingHash.push(containerName);
408494}
409-if (labels.epoch !== SANDBOX_BROWSER_SECURITY_HASH_EPOCH) {
495+if (labels.epoch !== browserHashEpoch) {
410496staleEpoch.push(containerName);
411497}
412498const portMappings = await readSandboxBrowserPortMappings({
@@ -444,7 +530,7 @@ export async function collectSandboxBrowserHashLabelFindings(params?: {
444530title: "Sandbox browser container hash epoch is stale",
445531detail:
446532`Containers: ${staleEpoch.join(", ")}. ` +
447-`Expected openclaw.browserConfigEpoch=${SANDBOX_BROWSER_SECURITY_HASH_EPOCH}.`,
533+`Expected openclaw.browserConfigEpoch=${browserHashEpoch}.`,
448534remediation: `${formatCliCommand("openclaw sandbox recreate --browser --all")} (add --force to skip prompt).`,
449535});
450536}
@@ -471,6 +557,7 @@ export async function collectWorkspaceSkillSymlinkEscapeFindings(params: {
471557skillScanLimits?: WorkspaceSkillScanLimits;
472558}): Promise<SecurityAuditFinding[]> {
473559const findings: SecurityAuditFinding[] = [];
560+const { listAgentWorkspaceDirs } = await loadAgentWorkspaceDirsModule();
474561const workspaceDirs = listAgentWorkspaceDirs(params.cfg);
475562if (workspaceDirs.length === 0) {
476563return findings;
@@ -589,6 +676,9 @@ export async function collectIncludeFilePermFindings(params: {
589676return findings;
590677}
591678679+const { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions } =
680+await loadAuditFsModule();
681+592682for (const p of includePaths) {
593683const perms = await inspectPathPermissions(p, {
594684env: params.env,
@@ -655,6 +745,8 @@ export async function collectStateDeepFilesystemFindings(params: {
655745}): Promise<SecurityAuditFinding[]> {
656746const findings: SecurityAuditFinding[] = [];
657747const oauthDir = resolveOAuthDir(params.env, params.stateDir);
748+const { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions } =
749+await loadAuditFsModule();
658750659751const oauthPerms = await inspectPathPermissions(oauthDir, {
660752env: params.env,
@@ -703,6 +795,7 @@ export async function collectStateDeepFilesystemFindings(params: {
703795)
704796.filter(Boolean)
705797 : [];
798+const { resolveDefaultAgentId } = await loadAgentScopeModule();
706799const defaultAgentId = resolveDefaultAgentId(params.cfg);
707800const ids = Array.from(new Set([defaultAgentId, ...agentIds])).map((id) => normalizeAgentId(id));
708801@@ -943,6 +1036,10 @@ export async function collectInstalledSkillsCodeSafetyFindings(params: {
9431036const findings: SecurityAuditFinding[] = [];
9441037const pluginExtensionsDir = path.join(params.stateDir, "extensions");
9451038const scannedSkillDirs = new Set<string>();
1039+const [{ listAgentWorkspaceDirs }, { resolveSkillSource }] = await Promise.all([
1040+loadAgentWorkspaceDirsModule(),
1041+loadSkillSourceModule(),
1042+]);
9461043const workspaceDirs = listAgentWorkspaceDirs(params.cfg);
9471044const { loadWorkspaceSkillEntries } = await loadSkillsModule();
9481045此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。