fix(cron): isolate fresh cron session state#71340
Conversation
Greptile SummaryThis PR fixes stale session state leaking into fresh isolated cron runs by introducing Confidence Score: 4/5Safe to merge; changes are well-tested and the core logic is correct with only minor style concerns. Only P2 findings: shallow clone in
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/cron/isolated-agent/run-session-state.ts
Line: 22-24
Comment:
**Shallow clone only protects top-level properties**
`cloneSessionEntry` uses a one-level spread, so nested objects (`skillsSnapshot`, any remaining `cliSessionIds`/`cliSessionBindings` if they survive to persist time, etc.) are shared references between the base session and the run snapshot. A direct mutation like `cronSession.sessionEntry.skillsSnapshot.someField = …` would silently contaminate the snapshot. The current test only exercises a primitive top-level field (`status`). A structuredClone (or deep-copy helper) would give complete isolation at modest cost.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/cron/isolated-agent/session.ts
Line: 12-68
Comment:
**Opt-out field list is fragile as `SessionEntry` grows**
`clearFreshCronSessionState` enumerates every runtime field to delete individually. Any new field added to `SessionEntry` that should be cleared on rollover must also be added here manually; there's no compiler or lint guard preventing a silent omission. A more robust pattern would be to define an explicit `PRESERVED_SESSION_FIELDS` allowlist (the few user-preference fields to keep) and reconstruct the entry from only those fields, so new runtime fields are cleared by default rather than by opt-in.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(cron): isolate fresh cron session st..." | Re-trigger Greptile |
71f08b4 to
fd7b447
Compare
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟠 SSRF and prompt exfiltration via unvalidated Anthropic Vertex `baseUrl` passed to SDK client
Description
If an attacker can influence
Vulnerable code: const client = new deps.AnthropicVertex({
region,
...(baseURL ? { baseURL } : {}),
...(projectId ? { projectId } : {}),
});
...
resolveAnthropicVertexSdkBaseUrl(model.baseUrl)RecommendationTreat Minimum hardening:
Example (conceptual): import { isBlockedHostnameOrIp } from "../../src/infra/net/ssrf.js";
function validateVertexBaseUrl(raw?: string): string | undefined {
if (!raw?.trim()) return undefined;
const url = new URL(raw);
if (url.protocol !== "https:") throw new Error("Vertex baseUrl must be https");
if (isBlockedHostnameOrIp(url.hostname)) throw new Error("Blocked baseUrl host");
// optional allowlist
if (!url.hostname.endsWith("aiplatform.googleapis.com") && url.hostname !== "proxy.example.com") {
throw new Error("Unapproved baseUrl host");
}
return url.toString().replace(/\/+$/, "");
}
const baseURL = validateVertexBaseUrl(model.baseUrl);
const client = new AnthropicVertex({ region, ...(baseURL ? { baseURL } : {}) });If possible, route SDK HTTP through the project’s existing 2. 🟡 Potential DoS: structuredClone of SessionEntry can throw due to non-cloneable Skill objects in skillsSnapshot.resolvedSkills
Description
Why this is plausible in this codebase:
Vulnerable code: function cloneSessionEntry(entry: MutableCronSessionEntry): MutableCronSessionEntry {
return globalThis.structuredClone(entry);
}
...
const runSessionEntry = cloneSessionEntry(params.cronSession.sessionEntry);RecommendationAvoid cloning the full in-memory Options:
function toPersistableSessionEntry(entry: SessionEntry): SessionEntry {
const next = { ...entry };
if (next.skillsSnapshot?.resolvedSkills) {
next.skillsSnapshot = { ...next.skillsSnapshot, resolvedSkills: undefined };
}
return next;
}
const runSessionEntry = toPersistableSessionEntry(params.cronSession.sessionEntry);
const runSessionEntry = JSON.parse(JSON.stringify(params.cronSession.sessionEntry)) as SessionEntry;Additionally, consider wrapping cloning in Analyzed PR: #71340 at commit Last updated on: 2026-04-25T05:06:52Z |
406eba8 to
7c3f239
Compare
7c3f239 to
c6e5cd9
Compare
Summary
:run:<sessionId>rows as snapshots instead of aliasing the mutable base cron session objectWhy
Fresh isolated cron runs were seeded from the previous session row, so old lifecycle fields, runtime model facts, auth/model switch state, CLI session ids, exec overrides, heartbeat cache, delivery metadata, usage totals, and fallback notices could leak into the next run. The base cron key and per-run key also pointed at the same object, so later base-session mutations could contaminate run rows.
Fixes #63211.
Fixes #68405.
Fixes #67598.
Fixes #58485.
Fixes #58285.
Fixes #57968.
Fixes #58575.
Fixes #61879.
Fixes #59257.
Fixes #47478.
Fixes #57947.
Fixes #58533.
Fixes #58511.
Fixes #61294.
Fixes #57112.
Fixes #58459.
Fixes #58517.
Fixes #57501.
Fixes #63617.
Fixes #62580.
Validation
pnpm test src/cron/isolated-agent/session.test.ts src/cron/isolated-agent/run-session-state.test.ts src/cron/isolated-agent/run.cron-model-override.test.ts src/cron/isolated-agent/run.cron-model-override-forwarding.test.ts src/cron/isolated-agent/run.message-tool-policy.test.tspnpm format:check src/cron/isolated-agent/session.ts src/cron/isolated-agent/session.test.ts src/cron/isolated-agent/run-session-state.ts src/cron/isolated-agent/run-session-state.test.ts CHANGELOG.mdpnpm check:changed