fix(security): remove post-compaction audit injection message (#28507)

* fix: remove post-compaction audit injection (Layer 3)

Remove the post-compaction read audit that injects fake system messages
into conversations after context compaction. This audit:

- Hardcodes WORKFLOW_AUTO.md (a file that doesn't exist in standard
  workspaces) as a required read after every compaction
- Leaks raw regex syntax (memory\/\d{4}-\d{2}-\d{2}\.md) in
  user-facing warning messages
- Injects messages via enqueueSystemEvent that appear as user-role
  messages, tricking agents into reading attacker-controlled files
- Creates a persistent prompt injection vector (see #27697)

Layer 1 (compaction summary) and Layer 2 (workspace context refresh
from AGENTS.md via post-compaction-context.ts) remain intact and are
sufficient for post-compaction context recovery.

Deleted files:
- src/auto-reply/reply/post-compaction-audit.ts
- src/auto-reply/reply/post-compaction-audit.test.ts

Modified files:
- src/auto-reply/reply/agent-runner.ts (removed imports, audit map,
  flag setting, and Layer 3 audit block)

Fixes #27697, fixes #26851, fixes #20484, fixes #22339, fixes #25600
Relates to #26461

* fix: resolve lint failures from post-compaction audit removal

* Tests: add regression for removed post-compaction audit warnings

---------

Co-authored-by: Wilfred (OpenClaw Agent) <jay@openclaw.dev>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
fuller-stack-dev
2026-02-27 18:15:59 -07:00
committed by GitHub
parent a509154be5
commit 70a4f25ab1
4 changed files with 85 additions and 341 deletions

View File

@@ -44,12 +44,6 @@ import { createAudioAsVoiceBuffer, createBlockReplyPipeline } from "./block-repl
import { resolveEffectiveBlockStreamingConfig } from "./block-streaming.js";
import { createFollowupRunner } from "./followup-runner.js";
import { resolveOriginMessageProvider, resolveOriginMessageTo } from "./origin-routing.js";
import {
auditPostCompactionReads,
extractReadPaths,
formatAuditWarning,
readSessionMessages,
} from "./post-compaction-audit.js";
import { readPostCompactionContext } from "./post-compaction-context.js";
import { resolveActiveRunQueueAction } from "./queue-policy.js";
import { enqueueFollowupRun, type FollowupRun, type QueueSettings } from "./queue.js";
@@ -95,9 +89,6 @@ function appendUnscheduledReminderNote(payloads: ReplyPayload[]): ReplyPayload[]
});
}
// Track sessions pending post-compaction read audit (Layer 3)
const pendingPostCompactionAudits = new Map<string, boolean>();
export async function runReplyAgent(params: {
commandBody: string;
followupRun: FollowupRun;
@@ -704,9 +695,6 @@ export async function runReplyAgent(params: {
.catch(() => {
// Silent failure — post-compaction context is best-effort
});
// Set pending audit flag for Layer 3 (post-compaction read audit)
pendingPostCompactionAudits.set(sessionKey, true);
}
if (verboseEnabled) {
@@ -721,25 +709,6 @@ export async function runReplyAgent(params: {
finalPayloads = appendUsageLine(finalPayloads, responseUsageLine);
}
// Post-compaction read audit (Layer 3)
if (sessionKey && pendingPostCompactionAudits.get(sessionKey)) {
pendingPostCompactionAudits.delete(sessionKey); // Delete FIRST — one-shot only
try {
const sessionFile = activeSessionEntry?.sessionFile;
if (sessionFile) {
const messages = readSessionMessages(sessionFile);
const readPaths = extractReadPaths(messages);
const workspaceDir = process.cwd();
const audit = auditPostCompactionReads(readPaths, workspaceDir);
if (!audit.passed) {
enqueueSystemEvent(formatAuditWarning(audit.missingPatterns), { sessionKey });
}
}
} catch {
// Silent failure — audit is best-effort
}
}
return finalizeWithFollowup(
finalPayloads.length === 1 ? finalPayloads[0] : finalPayloads,
queueKey,