Gateway: harden custom session-store discovery#44176
Gateway: harden custom session-store discovery#44176gumadeiras merged 6 commits intoopenclaw:mainfrom
Conversation
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟡 Blocking synchronous filesystem discovery on gateway request path enables DoS via resource exhaustion
Description
This becomes a denial-of-service risk because:
Vulnerable code: const targets = resolveAllAgentSessionStoreTargetsSync(cfg);
for (const target of targets) {
const store = loadSessionStore(target.storePath);
for (const [key, entry] of Object.entries(store)) {
// merge
}
}Impact:
RecommendationAvoid synchronous discovery/loading on the request path and add caching + bounds. Recommended mitigation (example): cache discovered targets for a short TTL and refresh asynchronously. // module-level cache
let cachedTargets: { targets: SessionStoreTarget[]; expiresAt: number } | null = null;
const TARGETS_TTL_MS = 5_000;
async function getTargetsCached(cfg: OpenClawConfig): Promise<SessionStoreTarget[]> {
const now = Date.now();
if (cachedTargets && cachedTargets.expiresAt > now) return cachedTargets.targets;
const targets = await resolveAllAgentSessionStoreTargets(cfg);
cachedTargets = { targets, expiresAt: now + TARGETS_TTL_MS };
return targets;
}
export async function loadCombinedSessionStoreForGatewayAsync(cfg: OpenClawConfig) {
const targets = await getTargetsCached(cfg);
// load stores (ideally async) and merge
}Additional hardening options:
2. 🟡 TOCTOU + hardlink bypass in session-store discovery validation (possible arbitrary file read)
DescriptionSession-store discovery attempts to keep discovered
Vulnerable code (validation step): const stat = fsSync.lstatSync(storePath);
if (stat.isSymbolicLink() || !stat.isFile()) {
return undefined;
}
const realStorePath = fsSync.realpathSync(storePath);
const realAgentsRoot = params.realAgentsRoot ?? fsSync.realpathSync(params.agentsRoot);
return isWithinRoot(realStorePath, realAgentsRoot) ? realStorePath : undefined;Because the validation does not bind the check to the subsequent read, and does not reject hardlinks, it is possible for a discovered store to reference unexpected content. RecommendationBind validation to the actual file opened, and reject hardlinks. Option A (recommended): when reading import { openVerifiedFileSync } from "../infra/safe-open-sync.js";
const opened = openVerifiedFileSync({
filePath: storePath,
rejectPathSymlink: true,
rejectHardlinks: true,
maxBytes: /* reasonable max size */,
allowedType: "file",
});
if (!opened.ok) return undefined;
try {
const raw = fs.readFileSync(opened.fd, "utf8");
// parse JSON...
} finally {
fs.closeSync(opened.fd);
}Option B: if discovery must return a string path, then at minimum re-validate immediately before reading (including Additionally, consider adding a maximum allowed size for Analyzed PR: #44176 at commit Last updated on: 2026-03-12T18:19:39Z |
初歩確認CI 通過後にレビュー予定。変更規模 (+517/-171, 14 files) を考慮し、以下を確認予定:
Greptile Review / Aisle 結果も参照します。 |
stop running automated checks and spamming comments/PRs; final warning |
There was a problem hiding this comment.
Pull request overview
This PR hardens gateway/ACP session-store discovery so that disk-only or retired agent session stores are still found when session.store is configured with a custom templated root (not just under the default state tree). It centralizes session-store target discovery, preserves real on-disk store paths for discovered stores, and reuses that logic across ACP enumeration and gateway store loading, with added regression tests and a docs clarification.
Changes:
- Add shared session-store target discovery (
resolveAllAgentSessionStoreTargets{Sync}) that merges configured targets with discovered on-diskagents/*/sessions/sessions.jsonstores. - Update gateway combined-store loading and run-id→session-key fallback to use the shared discovery (covering disk-only agents under custom roots).
- Add regression tests for discovery + run-id fallback, and clarify
/acp sessionsbehavior in docs.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/gateway/session-utils.ts | Use shared discovery to build the combined gateway session store across configured + discovered agent stores. |
| src/gateway/session-utils.test.ts | Adjust regression test to cover a custom templated session.store root. |
| src/gateway/server-session-key.ts | Switch run-id fallback lookup to the combined store (covers disk-only/retired stores). |
| src/gateway/server-session-key.test.ts | New regression coverage for run-id lookup through disk-only agent stores under custom root. |
| src/config/sessions/targets.ts | New shared discovery/selection helpers for configured + discovered session-store targets. |
| src/config/sessions/targets.test.ts | New tests for discovery behavior across default + custom roots and path preservation. |
| src/config/sessions/paths.ts | Extend resolveStorePath to accept env; add helper to infer agents root from a canonical store path. |
| src/config/sessions.ts | Export the new session target helpers. |
| src/commands/session-store-targets.ts | Refactor to delegate to the shared config helper (removes duplicated logic). |
| src/commands/session-store-targets.test.ts | Update tests to validate delegation instead of duplicating selector logic. |
| src/agents/session-dirs.ts | Extract reusable sync/async helpers for enumerating agent sessions directories from an agents root. |
| src/acp/runtime/session-meta.ts | Use shared discovery for ACP session enumeration (covers custom-root disk stores). |
| src/acp/runtime/session-meta.test.ts | New test validating ACP enumeration uses resolved store targets. |
| docs/tools/acp-agents.md | Clarify /acp sessions vs gateway discovery behavior for session-key/id/label targeting. |
Greptile SummaryThis PR hardens custom session-store discovery across ACP enumeration, gateway combined-store loading, and run-id fallback lookup by extracting a shared Key changes:
Confidence Score: 4/5
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67be0eabd6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
b99c3a4 to
7d10ab4
Compare
|
Thanks for the review. I checked these concerns against From a security-triage standpoint, I do not think the reported scenarios are in-scope vulnerabilities under OpenClaw's stated trust model:
That applies to the symlinked-store, retired-store visibility, normalized-dir collision, and run-id poisoning scenarios as written in the comment. That said, the underlying hardening opportunities were still worthwhile, so I pushed defense-in-depth changes in the PR head:
This was added in the current PR head commit: Targeted verification passed: So my conclusion is:
|
9bc2184 to
52ebbf5
Compare
|
Merged via squash.
Thanks @gumadeiras! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bc2184f77
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Merged via squash. Prepared head SHA: 52ebbf5 Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: 52ebbf5 Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: 52ebbf5 Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras
Merged via squash. Prepared head SHA: 52ebbf5 Co-authored-by: gumadeiras <[email protected]> Co-authored-by: gumadeiras <[email protected]> Reviewed-by: @gumadeiras (cherry picked from commit 46f0bfc)
Summary
Describe the problem and fix in 2–5 bullets:
session.storeroot could be missed./acp sessionsstill reads the current bound/requester store rather than acting as a global ACP session browser.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
session.storeroots.session-idorsession-labelnow resolve sessions found in those same custom-root disk stores.Security Impact (required)
Yes/No): NoYes/No): NoYes/No): NoYes/No): NoYes/No): YesYes, explain risk + mitigation:Discovery now includes disk-only
agents/*/sessions/sessions.jsonstores under the configured templated session root and the default state root. The scope is still bounded to existing local session-store locations and does not add new write paths or broader filesystem traversal.Repro + Verification
Environment
session.store: <state>/custom-state/agents/{agentId}/sessions/sessions.jsonSteps
session.storeroot outside the default state tree.sessions.jsonon disk under that custom root.Expected
Actual
Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
acp:...) after lookup.Review Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No): YesYes/No): NoYes/No): NoFailure Recovery (if this breaks)
agents.list.src/config/sessions/targets.ts,src/gateway/session-utils.ts, andsrc/gateway/server-session-key.ts.Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.updatedAt, with regression coverage on disk-only agent aggregation.