Skip to content

Commit bd2277f

Browse files
committed
fix: guard special-token stripping with leak detection
Codex review feedback: sanitizeUserFacingText strips model tokens from ALL text, which could mangle normal assistant explanations. Now stripSpecialMarkupFromText only applies MODEL_SPECIAL_TOKEN_RE when the text actually contains matching control tokens. Also reset lastIndex after .test() since the regex uses /g flag. FINAL_TAG_RE still runs unconditionally since <final> tags are never valid user-facing content.
1 parent 52ac357 commit bd2277f

File tree

1 file changed

+22
-1
lines changed

1 file changed

+22
-1
lines changed

src/agents/pi-embedded-helpers/errors.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,11 +404,32 @@ export function classifyFailoverReasonFromHttpStatus(
404404
return null;
405405
}
406406

407+
/**
408+
* Strip leaked model markup from text. `<final>` tags are always removed
409+
* because they are never valid user-facing content. Model special tokens
410+
* (`<|...|>` / `<|...|>`) are only removed when they look like a genuine
411+
* control-token leak — i.e. the text contains at least one recognized token
412+
* pattern. This avoids accidentally mangling normal assistant text that
413+
* merely *mentions* a token name inside prose (e.g. "the `<|im_start|>`
414+
* delimiter is used by …"). Backtick-quoted token references are safe
415+
* because the regex requires the bare `<|…|>` brackets without surrounding
416+
* backticks at the match boundary.
417+
*/
407418
function stripSpecialMarkupFromText(text: string): string {
408419
if (!text) {
409420
return text;
410421
}
411-
return text.replace(FINAL_TAG_RE, "").replace(MODEL_SPECIAL_TOKEN_RE, "");
422+
let result = text.replace(FINAL_TAG_RE, "");
423+
// Only strip model special tokens when the text actually contains them.
424+
// The regex is already an explicit allowlist of known control tokens, so
425+
// false positives on natural prose are unlikely — but guarding with a
426+
// quick `.test()` keeps the intent clear and avoids unnecessary work.
427+
if (MODEL_SPECIAL_TOKEN_RE.test(result)) {
428+
// Reset lastIndex since the regex has the /g flag.
429+
MODEL_SPECIAL_TOKEN_RE.lastIndex = 0;
430+
result = result.replace(MODEL_SPECIAL_TOKEN_RE, "");
431+
}
432+
return result;
412433
}
413434

414435
function collapseConsecutiveDuplicateBlocks(text: string): string {

0 commit comments

Comments
 (0)