Skip to content

fix: stabilize context engine prompt cache touches#67767

Merged
jalehman merged 2 commits into
openclaw:mainfrom
jalehman:josh/pass-afterturn-runtime-context-loop-hook
Apr 16, 2026
Merged

fix: stabilize context engine prompt cache touches#67767
jalehman merged 2 commits into
openclaw:mainfrom
jalehman:josh/pass-afterturn-runtime-context-loop-hook

Conversation

@jalehman

Copy link
Copy Markdown
Contributor

What

This PR makes context-engine prompt-cache touch metadata consistent across the embedded runner afterTurn paths so lossless-claw can keep Anthropic and GPT cache state accurate during active tool loops and final turn ingestion.

Why

The loop-hook afterTurn path was dropping runtime context entirely, and the final afterTurn path only reused persisted TTL state. That left lossless-claw operating on stale or missing cache-touch signals, which could force unnecessary deferred compaction once Anthropic TTL expired.

Changes

  • Pass runtimeContext through loop-hook afterTurn
  • Derive live prompt-cache touch timestamps
  • Reuse shared touch-timestamp helper logic
  • Add regression coverage for both paths

Testing

  • pnpm exec vitest run src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts src/agents/pi-embedded-runner/tool-result-context-guard.test.ts
  • git commit hook ran repo checks successfully

@aisle-research-bot

aisle-research-bot Bot commented Apr 16, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Sensitive runtime metadata/PII exposed to pluggable ContextEngine via loop-hook runtimeContext
2 🟡 Medium Untrusted usage metrics can influence prompt-cache TTL touch timestamp decisions
1. 🟡 Sensitive runtime metadata/PII exposed to pluggable ContextEngine via loop-hook runtimeContext
Property Value
Severity Medium
CWE CWE-359
Location src/agents/pi-embedded-runner/tool-result-context-guard.ts:236-248

Description

installContextEngineLoopHook() now allows the caller to compute a runtimeContext (via getRuntimeContext({ messages, prePromptMessageCount })) and forwards it into contextEngine.afterTurn() on every tool-loop iteration.

If a ContextEngine implementation is pluggable/third-party (or configurable by a less-trusted actor), this increases the sensitive data surface exposed to that plugin:

  • afterTurn() already receives the full messages array (which commonly contains tool results and may include secrets).
  • The newly forwarded runtimeContext can additionally include host/runtime-only metadata and PII that may not be present in messages (e.g., workspace paths, agent paths, auth profile IDs, owner phone numbers, sender IDs) as built by the runtime.
  • Because the loop hook can run even in long tool loops (and potentially even when the overall attempt later aborts), this provides additional opportunities for a plugin to exfiltrate or log this runtime metadata.

Vulnerable code:

await contextEngine.afterTurn({// ...
  messages: sourceMessages,
  prePromptMessageCount,
  tokenBudget,
  runtimeContext: params.getRuntimeContext?.({
    messages: sourceMessages,
    prePromptMessageCount,
  }),
});

Recommendation

Treat ContextEngine implementations as an explicit trust boundary.

  • Minimize what is exposed in runtimeContext for loop-hook calls. Prefer a small, purpose-built object (e.g., prompt-cache telemetry only) rather than full runtime metadata.
  • Redact/omit high-sensitivity fields (e.g., ownerNumbers, authProfileId, filesystem paths) unless the engine is explicitly trusted.
  • Consider adding an allowlist / capability flag on the engine (e.g., engine.info.capabilities.runtimeContext = ['promptCache']) to gate what is shared.

Example (only expose prompt cache info):

getRuntimeContext: ({ messages, prePromptMessageCount }) => ({
  promptCache: buildLoopPromptCacheInfo({
    messagesSnapshot: messages,
    prePromptMessageCount,
    retention: effectivePromptCacheRetention,
    fallbackLastCacheTouchAt,
  }),
})

Additionally, document that afterTurn() receives full transcripts (including tool outputs) so operators avoid installing untrusted engines in sensitive environments.

2. 🟡 Untrusted usage metrics can influence prompt-cache TTL touch timestamp decisions
Property Value
Severity Medium
CWE CWE-840
Location src/agents/pi-embedded-runner/run/attempt.context-engine-helpers.ts:125-135

Description

buildLoopPromptCacheInfo() derives lastCallUsage from the latest assistant message via normalizeUsage(currentAttemptAssistant?.usage) and then uses the mere presence of cacheRead/cacheWrite (any number, including negative values) to decide whether to trust and parse assistantTimestamp as the prompt-cache touch time.

If an attacker can inject/spoof assistant messages (or their usage fields) into the message history (e.g., via compromised tool/plugin output, persisted session tampering, or other transcript injection), they can:

  • Set usage.cacheRead/cacheWrite to any finite number (including negative) to force the hasCacheUsage branch.
  • Supply a crafted timestamp value (string/number) that will be parsed and used as lastCacheTouchAt.

Downstream, lastCacheTouchAt is used by cache-TTL based context pruning to decide whether pruning should be skipped (when Date.now() - lastTouch < ttlMs). A spoofed recent/future touch timestamp can therefore bypass pruning and potentially lead to runaway context growth/cost or context-window overflow (DoS/resource exhaustion).

Vulnerable logic:

const hasCacheUsage =
  typeof params.lastCallUsage?.cacheRead === "number" ||
  typeof params.lastCallUsage?.cacheWrite === "number";
if (!hasCacheUsage) {
  return params.fallbackLastCacheTouchAt ?? null;
}
return (
  parsePromptCacheTouchTimestamp(params.assistantTimestamp) ??
  params.fallbackLastCacheTouchAt ??
  null
);

Recommendation

Treat usage and timestamp from transcript messages as untrusted for cache-TTL decisions.

Suggested hardening (pick one or combine):

  1. Validate cache metrics and require a positive value before considering them evidence of prompt-cache activity:
const cacheRead = params.lastCallUsage?.cacheRead;
const cacheWrite = params.lastCallUsage?.cacheWrite;
const hasCacheUsage =
  (typeof cacheRead === "number" && Number.isFinite(cacheRead) && cacheRead > 0) ||
  (typeof cacheWrite === "number" && Number.isFinite(cacheWrite) && cacheWrite > 0);
  1. Clamp negative cache values to 0 in normalizeUsage() to prevent negative values from triggering logic.

  2. Constrain accepted timestamps (e.g., reject future timestamps or those too far from Date.now()), and/or only accept timestamps sourced from the provider response rather than from message history.

  3. If possible, store cache touch events in sessionManager entries (trusted local state) rather than deriving from message fields, and use those for TTL computations.


Analyzed PR: #67767 at commit 74d9b56

Last updated on: 2026-04-16T18:48:17Z

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M maintainer Maintainer-authored PR labels Apr 16, 2026
@jalehman jalehman self-assigned this Apr 16, 2026
@greptile-apps

greptile-apps Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two gaps in the embedded runner's context-engine prompt-cache metadata flow: the loop-hook afterTurn path was not forwarding runtimeContext at all, and the final afterTurn path was always using the persisted TTL timestamp instead of the live assistant timestamp when cache metrics were available. The fix adds a getRuntimeContext callback to installContextEngineLoopHook, extracts shared timestamp-resolution helpers (resolvePromptCacheTouchTimestamp, buildLoopPromptCacheInfo), and wires them into both call sites with targeted regression coverage.

Confidence Score: 5/5

Safe to merge — focused fix with full branch coverage for both corrected code paths.

All changes are additive and backward-compatible. The helper logic is straightforward and guarded against bad inputs (Number.isFinite, optional chaining). Regression tests cover the two previously-broken scenarios (missing runtimeContext in the loop hook, stale TTL timestamp in final afterTurn). No P0/P1 findings.

No files require special attention.

Reviews (1): Last reviewed commit: "fix: stabilize context engine prompt cac..." | Re-trigger Greptile

@jalehman jalehman force-pushed the josh/pass-afterturn-runtime-context-loop-hook branch from 6e77191 to 74d9b56 Compare April 16, 2026 18:44
@jalehman jalehman merged commit a327b67 into openclaw:main Apr 16, 2026
43 of 44 checks passed
@jalehman jalehman deleted the josh/pass-afterturn-runtime-context-loop-hook branch April 16, 2026 18:53
xudaiyanzi pushed a commit to xudaiyanzi/openclaw that referenced this pull request Apr 17, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
kvnkho pushed a commit to kvnkho/openclaw that referenced this pull request Apr 17, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
Mquarmoc pushed a commit to Mquarmoc/openclaw that referenced this pull request Apr 20, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* fix: stabilize context engine prompt cache touches

* fix(changelog): document context-engine prompt cache touch stabilization
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant