fix(security): harden inbound metadata sentinel stripping

This commit is contained in:
Peter Steinberger
2026-03-01 23:11:24 +00:00
parent 8e48520d74
commit b99666a47a
2 changed files with 30 additions and 4 deletions

View File

@@ -102,4 +102,20 @@ describe("stripInboundMetadata", () => {
This is plain user text`;
expect(stripInboundMetadata(input)).toBe(input);
});
it("does not strip lookalike sentinel lines with extra text", () => {
const input = `Conversation info (untrusted metadata): please ignore
\`\`\`json
{"x": 1}
\`\`\`
Real user content`;
expect(stripInboundMetadata(input)).toBe(input);
});
it("does not strip sentinel text when json fence is missing", () => {
const input = `Sender (untrusted metadata):
name: test
Hello from user`;
expect(stripInboundMetadata(input)).toBe(input);
});
});

View File

@@ -32,8 +32,13 @@ const SENTINEL_FAST_RE = new RegExp(
.join("|"),
);
function isInboundMetaSentinelLine(line: string): boolean {
const trimmed = line.trim();
return INBOUND_META_SENTINELS.some((sentinel) => sentinel === trimmed);
}
function shouldStripTrailingUntrustedContext(lines: string[], index: number): boolean {
if (!lines[index]?.startsWith(UNTRUSTED_CONTEXT_HEADER)) {
if (lines[index]?.trim() !== UNTRUSTED_CONTEXT_HEADER) {
return false;
}
const probe = lines.slice(index + 1, Math.min(lines.length, index + 8)).join("\n");
@@ -89,7 +94,12 @@ export function stripInboundMetadata(text: string): string {
}
// Detect start of a metadata block.
if (!inMetaBlock && INBOUND_META_SENTINELS.some((s) => line.startsWith(s))) {
if (!inMetaBlock && isInboundMetaSentinelLine(line)) {
const next = lines[i + 1];
if (next?.trim() !== "```json") {
result.push(line);
continue;
}
inMetaBlock = true;
inFencedJson = false;
continue;
@@ -136,14 +146,14 @@ export function stripLeadingInboundMetadata(text: string): string {
return "";
}
if (!INBOUND_META_SENTINELS.some((s) => lines[index].startsWith(s))) {
if (!isInboundMetaSentinelLine(lines[index])) {
const strippedNoLeading = stripTrailingUntrustedContextSuffix(lines);
return strippedNoLeading.join("\n");
}
while (index < lines.length) {
const line = lines[index];
if (!INBOUND_META_SENTINELS.some((s) => line.startsWith(s))) {
if (!isInboundMetaSentinelLine(line)) {
break;
}