Files
openclaw/src/gateway/boot-echo-guard.ts
T
Peter Steinberger 26312fd0b0 refactor: remove gateway and channel dead exports (#106038)
* refactor: remove gateway and channel dead exports

* style: format config reload test

* refactor: remove newly dead gateway exports

* test: preserve approval audience coverage

* refactor: preserve gateway contracts while pruning exports

* test: retain gateway regression coverage

* test: restore gateway policy coverage

* test: close dead-export CI gaps

* fix: reconcile dead exports with latest main

* refactor: remove newly dead gateway runner export

* test: remove private worker verifier coverage

* test: preserve weak secret docs coverage

* fix: avoid gateway health import cycle

* fix: preserve node pairing type contract

* style: format talk relay test

* fix: preserve gateway protocol generation contract

* chore: refresh dead export baseline

* fix: preserve loaded target compatibility exports

* fix: preserve plugin SDK declaration resolution

* chore: refresh dead export baseline

* chore: refresh dead export baseline

* test(gateway): derive private worker service options
2026-07-13 05:47:05 -07:00

115 lines
4.0 KiB
TypeScript

// Boot-run echo guard: tracks the active boot prompt per session key so that
// downstream user-visible delivery paths (currently the message tool) can
// suppress fallback-model echoes that copy substantial portions of the boot
// prompt without preserving the internal-runtime-context delimiters.
//
// The marker-based strip in `stripInternalRuntimeContext` only catches
// echoes that include the delimiter lines verbatim. A model that paraphrases
// out the wrapper but reproduces a long contiguous chunk of the BOOT.md
// content would slip past the marker strip and reach the user. This module
// adds a defense-in-depth substantial-echo check using the active boot prompt
// as the comparison source. Refs #53732.
const MIN_ECHO_CHARS = 80;
type BootEchoContext = {
bootPrompt: string;
normalizedBootPrompt: string;
};
const bootContextBySessionKey = new Map<string, BootEchoContext>();
const bootChunksByNormalizedPrompt = new Map<string, Map<number, Set<string>>>();
function normalizeEchoComparisonText(text: string): string {
return text.replace(/\s+/gu, " ").trim();
}
function getBootPromptChunks(normalizedBootPrompt: string, minLen: number): Set<string> {
let chunksByLength = bootChunksByNormalizedPrompt.get(normalizedBootPrompt);
if (!chunksByLength) {
chunksByLength = new Map();
bootChunksByNormalizedPrompt.set(normalizedBootPrompt, chunksByLength);
}
const cached = chunksByLength.get(minLen);
if (cached) {
return cached;
}
const chunks = new Set<string>();
for (let i = 0; i <= normalizedBootPrompt.length - minLen; i += 1) {
chunks.add(normalizedBootPrompt.slice(i, i + minLen));
}
chunksByLength.set(minLen, chunks);
return chunks;
}
export function setBootEchoContextForSession(sessionKey: string, bootPrompt: string): void {
if (!sessionKey || !bootPrompt) {
return;
}
const normalizedBootPrompt = normalizeEchoComparisonText(bootPrompt);
if (normalizedBootPrompt.length >= MIN_ECHO_CHARS) {
getBootPromptChunks(normalizedBootPrompt, MIN_ECHO_CHARS);
}
bootContextBySessionKey.set(sessionKey, { bootPrompt, normalizedBootPrompt });
}
export function clearBootEchoContextForSession(sessionKey: string): void {
if (!sessionKey) {
return;
}
const context = bootContextBySessionKey.get(sessionKey);
if (context) {
bootChunksByNormalizedPrompt.delete(context.normalizedBootPrompt);
}
bootContextBySessionKey.delete(sessionKey);
}
export function getBootEchoContextForSession(sessionKey: string | undefined): string | undefined {
if (!sessionKey) {
return undefined;
}
return bootContextBySessionKey.get(sessionKey)?.bootPrompt;
}
/**
* Returns true if `outboundText` contains a contiguous substring of
* `bootPrompt` of at least `minLen` characters, ignoring leading/trailing
* whitespace on the boot prompt itself. Short boot prompts (< minLen chars)
* never trigger to avoid suppressing legitimate short BOOT.md-directed
* sends like a literal "good morning".
*/
function containsSubstantialBootEcho(
outboundText: string,
bootPrompt: string,
minLen: number = MIN_ECHO_CHARS,
): boolean {
const haystack = normalizeEchoComparisonText(outboundText ?? "");
const needle = normalizeEchoComparisonText(bootPrompt ?? "");
if (haystack.length < minLen || needle.length < minLen) {
return false;
}
const bootChunks = getBootPromptChunks(needle, minLen);
for (let i = 0; i <= haystack.length - minLen; i += 1) {
if (bootChunks.has(haystack.slice(i, i + minLen))) {
return true;
}
}
return false;
}
/**
* Removes any user-supplied outbound text that substantially echoes the
* active boot prompt. Returns an empty string when an echo is detected so
* the caller can either drop the send entirely or treat the outbound text
* as empty. The boot prompt itself is unchanged.
*/
export function stripBootEchoFromOutboundText(
outboundText: string,
bootPrompt: string | undefined,
): string {
if (!bootPrompt) {
return outboundText;
}
return containsSubstantialBootEcho(outboundText, bootPrompt) ? "" : outboundText;
}