Compare commits
2 Commits
feat/volce
...
fix/sessio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d0effd9d4 | ||
|
|
abcdbd8afc |
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Outbound/Threading: pass `replyTo` and `threadId` from `message send` tool actions through the core outbound send path to channel adapters, preserving thread/reply routing. (#14948) Thanks @mcaxtr.
|
||||
- Sessions/Agents: pass `agentId` when resolving existing transcript paths in reply runs so non-default agents and heartbeat/chat handlers no longer fail with `Session file path must be within sessions directory`. (#15141) Thanks @Goldenmonstew.
|
||||
- Sessions/Agents: pass `agentId` through status and usage transcript-resolution paths (auto-reply, gateway usage APIs, and session cost/log loaders) so non-default agents can resolve absolute session files without path-validation failures. (#15103) Thanks @jalehman.
|
||||
- Sessions: accept legacy absolute `sessionFile` paths from prior releases while preserving containment checks to block traversal escapes. (#15323) Thanks @mudrii.
|
||||
- Signal/Install: auto-install `signal-cli` via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary `Exec format error` failures on arm64/arm hosts. (#15443) Thanks @jogvan-k.
|
||||
|
||||
## 2026.2.12
|
||||
|
||||
@@ -55,6 +55,14 @@ describe("session path safety", () => {
|
||||
resolveSessionFilePath("sess-1", { sessionFile: "../../etc/passwd" }, { sessionsDir }),
|
||||
).toThrow(/within sessions directory/);
|
||||
|
||||
expect(() =>
|
||||
resolveSessionFilePath(
|
||||
"sess-1",
|
||||
{ sessionFile: "subdir/../../escape.jsonl" },
|
||||
{ sessionsDir },
|
||||
),
|
||||
).toThrow(/within sessions directory/);
|
||||
|
||||
expect(() =>
|
||||
resolveSessionFilePath("sess-1", { sessionFile: "/etc/passwd" }, { sessionsDir }),
|
||||
).toThrow(/within sessions directory/);
|
||||
@@ -72,6 +80,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,9 +77,12 @@ 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);
|
||||
// Older versions stored absolute sessionFile paths in sessions.json.
|
||||
// Preserve compatibility, but validate containment against the resolved path.
|
||||
const normalized = path.isAbsolute(trimmed) ? path.relative(resolvedBase, trimmed) : trimmed;
|
||||
const resolvedCandidate = path.resolve(resolvedBase, normalized);
|
||||
const relative = path.relative(resolvedBase, resolvedCandidate);
|
||||
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
||||
if (!normalized || relative.startsWith("..") || path.isAbsolute(relative)) {
|
||||
throw new Error("Session file path must be within sessions directory");
|
||||
}
|
||||
return resolvedCandidate;
|
||||
|
||||
Reference in New Issue
Block a user