Fix CLI transcript compaction lifecycle#71916
Conversation
🔒 Aisle Security AnalysisWe found 4 potential security issue(s) in this PR:
1. 🟠 Sensitive transcript reseeding into fresh CLI prompts without redaction/consent gating
DescriptionFresh CLI sessions are automatically reseeded with prior OpenClaw transcript content (compaction summary + post-compaction tail messages) and embedded into the next prompt sent to external CLI backends.
This can leak secrets from prior messages/compaction summaries (API keys, tokens, file contents, tool outputs quoted into assistant messages, etc.) to third-party model providers and/or logs/artifacts, without an explicit opt-in or redaction step. Vulnerable code (history prompt construction): return [
"Continue this conversation using the OpenClaw transcript below as prior session history.",
...
"<conversation_history>",
renderedHistory,
"</conversation_history>",
...
"<next_user_message>",
params.prompt,
"</next_user_message>",
].join("\n");RecommendationAdd explicit controls and redaction before reseeding stored history into prompts sent to external backends. Suggested mitigations (combine as appropriate):
function redactSecrets(text: string): string {
return text
.replace(/\b(sk-[A-Za-z0-9]{20,})\b/g, "sk-***")
.replace(/\b(AKIA[0-9A-Z]{16})\b/g, "AKIA***")
// add org-specific patterns + consider a dedicated secret-scanner
;
}
// when rendering
const safeText = redactSecrets(text);
2. 🟠 IDOR / cross-session transcript exposure via OpenClaw CLI reseed prompt
DescriptionA fresh CLI run (no reusable backend This creates a cross-session information disclosure risk if an attacker can cause
Vulnerable flow (simplified):
Vulnerable code: const openClawHistoryPrompt = reusableCliSession.sessionId
? undefined
: buildCliSessionHistoryPrompt({
messages: loadCliSessionReseedMessages({
sessionId: params.sessionId,
sessionFile: params.sessionFile,
sessionKey: params.sessionKey,
agentId: params.agentId,
config: params.config,
}),
prompt: preparedPrompt,
});const basePrompt = cliSessionIdToUse
? params.prompt
: (context.openClawHistoryPrompt ?? params.prompt);return SessionManager.open(realSessionFile).getEntries();RecommendationEnforce session ownership/authorization before reading any transcript for reseeding. Minimum fixes:
Example pattern (conceptual): export function loadCliSessionReseedMessages(params: {
sessionId: string;
sessionFile: string;
sessionKey?: string;
requesterAccountId: string;
sessionEntry: SessionEntry;
// ...
}): unknown[] {
if (params.sessionEntry.accountId !== params.requesterAccountId) {
return []; // or throw
}
// then load entries...
}Also consider disabling reseed-by-transcript entirely unless explicitly enabled in config for single-tenant/local CLI use. 3. 🟡 Prompt delimiter injection in CLI session reseed prompt construction
Description
Because the inserted content can contain the same delimiter strings (e.g. Vulnerable code: return [
"<conversation_history>",
renderedHistory,
"</conversation_history>",
"",
"<next_user_message>",
params.prompt,
"</next_user_message>",
].join("\n");Data-flow context:
RecommendationTreat prior transcript content as data, not as inline control text. Recommended mitigations (pick one):
function escapeXmlLike(s: string): string {
return s
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">");
}
// When building:
"<conversation_history>",
escapeXmlLike(renderedHistory),
"</conversation_history>",
"<next_user_message>",
escapeXmlLike(params.prompt),
"</next_user_message>",
Also consider adding explicit instruction to ignore any “tag-like” content inside the history block and to treat it purely as quoted text. 4. 🟡 Potential compaction/maintenance DoS via stale session totalTokens snapshot
Description
However,
This creates an unnecessary repeated expensive maintenance/compaction loop (CPU/IO), which can be triggered by a corrupted/stale Vulnerable flow:
RecommendationInvalidate or bound the snapshot token count after compaction so stale/high values cannot force repeated compaction. Options:
// in recordCliCompactionInStore
next.totalTokensFresh = false;(and/or clear
const tokenSnapshot = resolveSessionTokenSnapshot(params.sessionEntry);
const boundedSnapshot = tokenSnapshot ? Math.min(tokenSnapshot, contextTokenBudget * 2) : 0;
const currentTokenCount = Math.max(preemptiveCompaction.estimatedPromptTokens, boundedSnapshot);Also consider skipping compaction if Analyzed PR: #71916 at commit Last updated on: 2026-04-26T04:06:09Z |
Greptile SummaryThis PR implements the CLI transcript compaction lifecycle: it compacts OpenClaw transcripts after over-budget CLI turns, clears stale external CLI resume state post-compaction, and reseeds fresh CLI sessions from the compacted OpenClaw transcript rather than provider-private history files. The implementation correctly mirrors the existing Confidence Score: 4/5Safe to merge; only P2 style/performance observations, no correctness or security defects found. All findings are P2 (style and minor efficiency). Core logic for compaction triggering, CLI session clearing, and history prompt injection is correct and well-tested. src/agents/cli-runner/prepare.ts (unnecessary history computation for native-resume sessions) and src/agents/command/cli-compaction.ts (empty-prompt comment clarification).
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 253d5a6bdc
ℹ️ 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".
65a4988 to
e0cb96a
Compare
Fixes #68329.
Supersedes #68388.
Summary:
Tests: