mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(security): harden structural session path fallback
This commit is contained in:
@@ -561,6 +561,43 @@ describe("sessions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back when structural cross-root path traverses after sessions", () => {
|
||||
withStateDir(path.resolve("/different/state"), () => {
|
||||
const originalBase = path.resolve("/original/state");
|
||||
const unsafe = path.join(originalBase, "agents", "bot2", "sessions", "..", "..", "etc");
|
||||
const sessionFile = resolveSessionFilePath(
|
||||
"sess-1",
|
||||
{ sessionFile: path.join(unsafe, "passwd") },
|
||||
{ agentId: "bot1" },
|
||||
);
|
||||
expect(sessionFile).toBe(
|
||||
path.join(path.resolve("/different/state"), "agents", "bot1", "sessions", "sess-1.jsonl"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back when structural cross-root path nests under sessions", () => {
|
||||
withStateDir(path.resolve("/different/state"), () => {
|
||||
const originalBase = path.resolve("/original/state");
|
||||
const nested = path.join(
|
||||
originalBase,
|
||||
"agents",
|
||||
"bot2",
|
||||
"sessions",
|
||||
"nested",
|
||||
"sess-1.jsonl",
|
||||
);
|
||||
const sessionFile = resolveSessionFilePath(
|
||||
"sess-1",
|
||||
{ sessionFile: nested },
|
||||
{ agentId: "bot1" },
|
||||
);
|
||||
expect(sessionFile).toBe(
|
||||
path.join(path.resolve("/different/state"), "agents", "bot1", "sessions", "sess-1.jsonl"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to derived transcript path when sessionFile is outside agent sessions directories", () => {
|
||||
withStateDir(path.resolve("/home/user/.openclaw"), () => {
|
||||
const sessionFile = resolveSessionFilePath(
|
||||
|
||||
@@ -115,6 +115,39 @@ function extractAgentIdFromAbsoluteSessionPath(candidateAbsPath: string): string
|
||||
return agentId || undefined;
|
||||
}
|
||||
|
||||
function resolveStructuralSessionFallbackPath(
|
||||
candidateAbsPath: string,
|
||||
expectedAgentId: string,
|
||||
): string | undefined {
|
||||
const normalized = path.normalize(path.resolve(candidateAbsPath));
|
||||
const parts = normalized.split(path.sep).filter(Boolean);
|
||||
const sessionsIndex = parts.lastIndexOf("sessions");
|
||||
if (sessionsIndex < 2 || parts[sessionsIndex - 2] !== "agents") {
|
||||
return undefined;
|
||||
}
|
||||
const agentIdPart = parts[sessionsIndex - 1];
|
||||
if (!agentIdPart) {
|
||||
return undefined;
|
||||
}
|
||||
const normalizedAgentId = normalizeAgentId(agentIdPart);
|
||||
if (normalizedAgentId !== agentIdPart.toLowerCase()) {
|
||||
return undefined;
|
||||
}
|
||||
if (normalizedAgentId !== normalizeAgentId(expectedAgentId)) {
|
||||
return undefined;
|
||||
}
|
||||
const relativeSegments = parts.slice(sessionsIndex + 1);
|
||||
// Session transcripts are stored as direct files in "sessions/".
|
||||
if (relativeSegments.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
const fileName = relativeSegments[0];
|
||||
if (!fileName || fileName === "." || fileName === "..") {
|
||||
return undefined;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function safeRealpathSync(filePath: string): string | undefined {
|
||||
try {
|
||||
return fs.realpathSync(filePath);
|
||||
@@ -170,11 +203,15 @@ function resolvePathWithinSessionsDir(
|
||||
if (resolvedFromPath) {
|
||||
return resolvedFromPath;
|
||||
}
|
||||
// The path structurally matches .../agents/<agentId>/sessions/...
|
||||
// Accept it even if the root directory differs from the current env
|
||||
// (e.g., OPENCLAW_STATE_DIR changed between session creation and resolution).
|
||||
// The structural pattern provides sufficient containment guarantees.
|
||||
return path.resolve(realTrimmed);
|
||||
// Cross-root compatibility for older absolute paths:
|
||||
// keep only canonical .../agents/<agentId>/sessions/<file> shapes.
|
||||
const structuralFallback = resolveStructuralSessionFallbackPath(
|
||||
realTrimmed,
|
||||
extractedAgentId,
|
||||
);
|
||||
if (structuralFallback) {
|
||||
return structuralFallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!normalized || normalized.startsWith("..") || path.isAbsolute(normalized)) {
|
||||
|
||||
Reference in New Issue
Block a user