mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(sessions): normalize absolute sessionFile paths for v2026.2.12 compatibility
Older OpenClaw versions stored absolute sessionFile paths in sessions.json. v2026.2.12 added path traversal security that rejected these absolute paths, breaking all Telegram group handlers with 'Session file path must be within sessions directory' errors. Changes: - resolvePathWithinSessionsDir() now normalizes absolute paths that resolve within the sessions directory, converting them to relative before validation - Added 3 tests for absolute path handling (within dir, with topic, outside dir) Fixes #15283 Closes #15214, #15237, #15216, #15152, #15213
This commit is contained in:
committed by
Peter Steinberger
parent
edfdd12d37
commit
abcdbd8afc
@@ -72,6 +72,42 @@ describe("session path safety", () => {
|
||||
expect(resolved).toBe(path.resolve(sessionsDir, "subdir/threaded-session.jsonl"));
|
||||
});
|
||||
|
||||
it("accepts absolute sessionFile paths that resolve within the sessions dir", () => {
|
||||
const sessionsDir = "/tmp/openclaw/agents/main/sessions";
|
||||
|
||||
const resolved = resolveSessionFilePath(
|
||||
"sess-1",
|
||||
{ sessionFile: "/tmp/openclaw/agents/main/sessions/abc-123.jsonl" },
|
||||
{ sessionsDir },
|
||||
);
|
||||
|
||||
expect(resolved).toBe(path.resolve(sessionsDir, "abc-123.jsonl"));
|
||||
});
|
||||
|
||||
it("accepts absolute sessionFile with topic suffix within the sessions dir", () => {
|
||||
const sessionsDir = "/tmp/openclaw/agents/main/sessions";
|
||||
|
||||
const resolved = resolveSessionFilePath(
|
||||
"sess-1",
|
||||
{ sessionFile: "/tmp/openclaw/agents/main/sessions/abc-123-topic-42.jsonl" },
|
||||
{ sessionsDir },
|
||||
);
|
||||
|
||||
expect(resolved).toBe(path.resolve(sessionsDir, "abc-123-topic-42.jsonl"));
|
||||
});
|
||||
|
||||
it("rejects absolute sessionFile paths outside the sessions dir", () => {
|
||||
const sessionsDir = "/tmp/openclaw/agents/main/sessions";
|
||||
|
||||
expect(() =>
|
||||
resolveSessionFilePath(
|
||||
"sess-1",
|
||||
{ sessionFile: "/tmp/openclaw/agents/work/sessions/abc-123.jsonl" },
|
||||
{ sessionsDir },
|
||||
),
|
||||
).toThrow(/within sessions directory/);
|
||||
});
|
||||
|
||||
it("uses agent sessions dir fallback for transcript path", () => {
|
||||
const resolved = resolveSessionTranscriptPath("sess-1", "main");
|
||||
expect(resolved.endsWith(path.join("agents", "main", "sessions", "sess-1.jsonl"))).toBe(true);
|
||||
|
||||
@@ -77,12 +77,14 @@ function resolvePathWithinSessionsDir(sessionsDir: string, candidate: string): s
|
||||
throw new Error("Session file path must not be empty");
|
||||
}
|
||||
const resolvedBase = path.resolve(sessionsDir);
|
||||
const resolvedCandidate = path.resolve(resolvedBase, trimmed);
|
||||
const relative = path.relative(resolvedBase, resolvedCandidate);
|
||||
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
||||
// Normalize absolute paths that are within the sessions directory.
|
||||
// Older versions stored absolute sessionFile paths in sessions.json;
|
||||
// convert them to relative so the containment check passes.
|
||||
const normalized = path.isAbsolute(trimmed) ? path.relative(resolvedBase, trimmed) : trimmed;
|
||||
if (!normalized || normalized.startsWith("..") || path.isAbsolute(normalized)) {
|
||||
throw new Error("Session file path must be within sessions directory");
|
||||
}
|
||||
return resolvedCandidate;
|
||||
return path.resolve(resolvedBase, normalized);
|
||||
}
|
||||
|
||||
export function resolveSessionTranscriptPathInDir(
|
||||
|
||||
Reference in New Issue
Block a user