sandbox: scope skill loading to workspace for sandboxed agents

Prevents managed/bundled skill file paths from leaking into sandboxed
agent skill snapshots, which caused 'path escapes sandbox root' errors.
Adds scopeToWorkspace option to loadSkillEntries/buildWorkspaceSkillSnapshot.
Also fixes stale Docker mount detection on container probe failure.
This commit is contained in:
Tarun Sukhani
2026-02-05 18:43:05 +00:00
parent 50f095ecb0
commit bf5a7a05dd
5 changed files with 69 additions and 5 deletions

View File

@@ -65,22 +65,23 @@ async function resolveContextReport(
sessionKey: params.sessionKey,
sessionId: params.sessionEntry?.sessionId,
});
const sandboxRuntime = resolveSandboxRuntimeStatus({
cfg: params.cfg,
sessionKey: params.ctx.SessionKey ?? params.sessionKey,
});
const skillsSnapshot = (() => {
try {
return buildWorkspaceSkillSnapshot(workspaceDir, {
config: params.cfg,
eligibility: { remote: getRemoteSkillEligibility() },
snapshotVersion: getSkillsSnapshotVersion(workspaceDir),
scopeToWorkspace: sandboxRuntime.sandboxed,
});
} catch {
return { prompt: "", skills: [], resolvedSkills: [] };
}
})();
const skillsPrompt = skillsSnapshot.prompt ?? "";
const sandboxRuntime = resolveSandboxRuntimeStatus({
cfg: params.cfg,
sessionKey: params.ctx.SessionKey ?? params.sessionKey,
});
const tools = (() => {
try {
return createOpenClawCodingTools({

View File

@@ -1,6 +1,7 @@
import crypto from "node:crypto";
import type { OpenClawConfig } from "../../config/config.js";
import { resolveUserTimezone } from "../../agents/date-time.js";
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox/runtime-status.js";
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
import { ensureSkillsWatcher, getSkillsSnapshotVersion } from "../../agents/skills/refresh.js";
import { type SessionEntry, updateSessionStore } from "../../config/sessions.js";
@@ -149,6 +150,10 @@ export async function ensureSkillSnapshot(params: {
skillFilter,
} = params;
// Sandboxed agents should only see workspace skills — managed/bundled skill
// paths are outside the sandbox root and would be blocked by path assertions.
const sandboxed = sessionKey ? resolveSandboxRuntimeStatus({ cfg, sessionKey }).sandboxed : false;
let nextEntry = sessionEntry;
let systemSent = sessionEntry?.systemSent ?? false;
const remoteEligibility = getRemoteSkillEligibility();
@@ -170,6 +175,7 @@ export async function ensureSkillSnapshot(params: {
skillFilter,
eligibility: { remote: remoteEligibility },
snapshotVersion,
scopeToWorkspace: sandboxed,
})
: current.skillsSnapshot;
nextEntry = {
@@ -194,6 +200,7 @@ export async function ensureSkillSnapshot(params: {
skillFilter,
eligibility: { remote: remoteEligibility },
snapshotVersion,
scopeToWorkspace: sandboxed,
})
: (nextEntry?.skillsSnapshot ??
(isFirstTurnInSession
@@ -203,6 +210,7 @@ export async function ensureSkillSnapshot(params: {
skillFilter,
eligibility: { remote: remoteEligibility },
snapshotVersion,
scopeToWorkspace: sandboxed,
})));
if (
skillsSnapshot &&