























@@ -299,12 +299,16 @@ function resolveContainedSkillPath(params: {
299299rootDir: string;
300300rootRealPath: string;
301301candidatePath: string;
302+allowedSymlinkTargetRealPaths?: readonly string[];
302303}): string | null {
303304const candidateRealPath = tryRealpath(params.candidatePath);
304305if (!candidateRealPath) {
305306return null;
306307}
307-if (isPathInside(params.rootRealPath, candidateRealPath)) {
308+if (
309+isPathInside(params.rootRealPath, candidateRealPath) ||
310+isPathInsideAnyRoot(params.allowedSymlinkTargetRealPaths ?? [], candidateRealPath)
311+) {
308312return candidateRealPath;
309313}
310314warnEscapedSkillPath({
@@ -394,6 +398,16 @@ function resolvePluginSkillRootRealPaths(pluginSkillDirs: readonly string[]): st
394398.filter((dir, index, all) => all.indexOf(dir) === index);
395399}
396400401+function resolveAllowedSymlinkTargetRealPaths(config?: OpenClawConfig): string[] {
402+const rawTargets = config?.skills?.load?.allowSymlinkTargets ?? [];
403+return rawTargets
404+.map((dir) => normalizeOptionalString(dir) ?? "")
405+.filter(Boolean)
406+.map((dir) => tryRealpath(resolveUserPath(dir)))
407+.filter((dir): dir is string => Boolean(dir))
408+.filter((dir, index, all) => all.indexOf(dir) === index);
409+}
410+397411function loadGeneratedPluginSkillRecords(params: {
398412pluginSkillsDir: string;
399413pluginSkillDirs: readonly string[];
@@ -496,6 +510,7 @@ function loadSkillEntries(
496510},
497511): SkillEntry[] {
498512const limits = resolveSkillsLimits(opts?.config, opts?.agentId);
513+const allowedSymlinkTargetRealPaths = resolveAllowedSymlinkTargetRealPaths(opts?.config);
499514500515const loadSkills = (params: { dir: string; source: string }): LoadedSkillRecord[] => {
501516const rootDir = path.resolve(params.dir);
@@ -512,6 +527,7 @@ function loadSkillEntries(
512527 rootDir,
513528 rootRealPath,
514529candidatePath: baseDir,
530+ allowedSymlinkTargetRealPaths,
515531});
516532if (!baseDirRealPath) {
517533return [];
@@ -525,6 +541,7 @@ function loadSkillEntries(
525541 rootDir,
526542rootRealPath: baseDirRealPath,
527543candidatePath: rootSkillMd,
544+ allowedSymlinkTargetRealPaths,
528545});
529546if (!rootSkillRealPath) {
530547return [];
@@ -617,6 +634,7 @@ function loadSkillEntries(
617634 rootDir,
618635rootRealPath: baseDirRealPath,
619636candidatePath: skillDir,
637+ allowedSymlinkTargetRealPaths,
620638});
621639if (!skillDirRealPath) {
622640continue;
@@ -628,6 +646,7 @@ function loadSkillEntries(
628646 rootDir,
629647rootRealPath: baseDirRealPath,
630648candidatePath: skillMd,
649+ allowedSymlinkTargetRealPaths,
631650});
632651if (skillMdRealPath) {
633652loadCandidateSkill({ skillDir, name, skillMdRealPath });
@@ -674,12 +693,14 @@ function loadSkillEntries(
674693 rootDir,
675694rootRealPath: baseDirRealPath,
676695candidatePath: nestedDir,
696+ allowedSymlinkTargetRealPaths,
677697});
678698const nestedSkillMdRealPath = resolveContainedSkillPath({
679699source: params.source,
680700 rootDir,
681701rootRealPath: baseDirRealPath,
682702candidatePath: nestedSkillMd,
703+ allowedSymlinkTargetRealPaths,
683704});
684705if (nestedDirRealPath && nestedSkillMdRealPath) {
685706loadCandidateSkill({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。